Alex McGilvray

PSM Tutorial #2-5 : Wrapping it Up

February 26th, 2013

In the last tutorial we discussed vertex buffers. The last major concept we will go over in this series.

At this point we’ve gone over all the major topics regarding sprite rendering in modern OpenGL. The lessons you’ve learned so far will apply far beyond sprite rendering. Vertex buffers and shaders are the heart of modern rendering techniques.

This final tutorial in the series will be going over the commands in the main render loop. You already know all the important concepts. We are merely activating them at this point. Nevertheless this is meant to be a thorough study of the theoretical portions of OpenGL and PSM/Vita rendering so let’s go 🙂

Now we will look at the render loop. As always I will start by including the full code sample.

public class AppMain
	{
		static protected GraphicsContext graphics;
		static ShaderProgram shaderProgram;
		static Texture2D texture;

		static float[] vertices=new float[12];

		static float[] texcoords = {
			0.0f, 0.0f,	// 0 top left.
			0.0f, 1.0f,	// 1 bottom left.
			1.0f, 0.0f,	// 2 top right.
			1.0f, 1.0f,	// 3 bottom right.
		};

		static float[] colors = {
			1.0f,	1.0f,	1.0f,	1.0f,	// 0 top left.
			1.0f,	1.0f,	1.0f,	1.0f,	// 1 bottom left.
			1.0f,	1.0f,	1.0f,	1.0f,	// 2 top right.
			1.0f,	1.0f,	1.0f,	1.0f,	// 3 bottom right.
		};

		const int indexSize = 4;
		static ushort[] indices;

		static VertexBuffer vertexBuffer;

		// Width of texture.
		static float Width;

		// Height of texture.
		static float Height;

		static Matrix4 unitScreenMatrix;

		public static void Main (string[] args)
		{
			Initialize ();

			while (true) {
				SystemEvents.CheckEvents ();
				Update ();
				Render ();
			}
		}

		public static void Initialize ()
		{
			graphics = new GraphicsContext();
			ImageRect rectScreen = graphics.Screen.Rectangle;

			texture = new Texture2D("/Application/resources/Player.png", false);
			shaderProgram = new ShaderProgram("/Application/shaders/Sprite.cgx");
			shaderProgram.SetUniformBinding(0, "u_WorldMatrix");

			Width = texture.Width;
			Height = texture.Height;

			vertices[0]=0.0f;	// x0
			vertices[1]=0.0f;	// y0
			vertices[2]=0.0f;	// z0

			vertices[3]=0.0f;	// x1
			vertices[4]=1.0f;	// y1
			vertices[5]=0.0f;	// z1

			vertices[6]=1.0f;	// x2
			vertices[7]=0.0f;	// y2
			vertices[8]=0.0f;	// z2

			vertices[9]=1.0f;	// x3
			vertices[10]=1.0f;	// y3
			vertices[11]=0.0f;	// z3

			indices = new ushort[indexSize];
			indices[0] = 0;
			indices[1] = 1;
			indices[2] = 2;
			indices[3] = 3;

			//												vertex pos,               texture,       color
			vertexBuffer = new VertexBuffer(4, indexSize, VertexFormat.Float3, VertexFormat.Float2, VertexFormat.Float4);

			vertexBuffer.SetVertices(0, vertices);
			vertexBuffer.SetVertices(1, texcoords);
			vertexBuffer.SetVertices(2, colors);

			vertexBuffer.SetIndices(indices);
			graphics.SetVertexBuffer(0, vertexBuffer);

			unitScreenMatrix = new Matrix4(
				 Width*2.0f/rectScreen.Width,	0.0f,	    0.0f, 0.0f,
				 0.0f,   Height*(-2.0f)/rectScreen.Height,	0.0f, 0.0f,
				 0.0f,   0.0f, 1.0f, 0.0f,
				 -1.0f,  1.0f, 0.0f, 1.0f
			);

		}

		public static void Update ()
		{

		}

		public static void Render ()
		{
			graphics.Clear();

			graphics.SetShaderProgram(shaderProgram);
			graphics.SetTexture(0, texture);
			shaderProgram.SetUniformValue(0, ref unitScreenMatrix);

			graphics.DrawArrays(DrawMode.TriangleStrip, 0, indexSize);

			graphics.SwapBuffers();
		}
	}

 

	public static void Render ()
	{
		graphics.Clear();

		graphics.SetShaderProgram(shaderProgram);
		graphics.SetTexture(0, texture);
		shaderProgram.SetUniformValue(0, ref unitScreenMatrix);

		graphics.DrawArrays(DrawMode.TriangleStrip, 0, indexSize);

		graphics.SwapBuffers();
	}

First we clear the screen so that we have a blank canvas to draw on. In many cases you will be doing this at the start of every render loop. Next we set our shader program. If you need a refresher on how shaders work you can go back to the second tutorial on this series which explains shaders.

Next we set the active texture. A general rule you can go by is that you can only have one active texture at a time in OpenGL (in reality you can have a few more with more advanced use of shaders but we will leave that for a later tutorial series). So when drawing what you need to do is set the active texture then draw your geometry that uses that texture. You may notice that this implies that you will need a different set of vertex buffers for each texture you use. This is true. Also sending many vertex buffers via DrawArrays to the GPU is a performance intensive process. As a result you need to keep this in mind. Many people will put all their art into one very large image (sometimes referred to as a texture atlas) in order to reduce DrawArrays calls. There are other methods you can also use to reduce draw calls. We will investigate this in a later tutorial series.

Next we set the uniform data in the shader program. If you recall from the second tutorial in the series, uniform data is extra data you send to a shader for various purposes such as passing the camera transformation matrix.

Next we call DrawArrays. This takes the currently bound vertex buffer, shader and texture and does the final act of actually processing and drawing everything. This is essentially the final call that brings everything together.

You may notice if you omit the last call SwapBuffers you will not see anything on the screen. This is because when we draw we are drawing to the back buffer. In the case of PSM we use double buffering.

Think of this of this as 2 pieces of paper. We have one that is showing on the screen and another that you can’t see which we are actually drawing on. Every time we call SwapBuffers we swap the 2 pieces of paper so the one on the screen goes out of sight and the one that we’ve been drawing on in the background comes into view. The reason for this is to prevent something called screen tearing. This is the case where we might be drawing to a surface and only half finish by the time the next frame is scheduled to draw. What happens is we have a tearing effect on the final rendered image. By swapping the 2 surfaces we can ensure that this does not happen because because the images are never swapped until the back buffer is finished drawing.

You can see an example of screen tear here.

So that’s it for this tutorial series. I hope you found it useful for your studies. If there is anything that’s not clear or if you found anything that might be considered inaccurate please let me know in the comments or by email and I’ll do my best to rectify the situation.

I’m currently considering what to do for the next Vita tutorial series. Most likely it will be something more fun such as a simple game. I’ve also been working on a 2D engine for the last few months for PSM. I may consider doing a tutorial on making a game with the engine. If there’s a topic related to OpenGL or Vita/PSM you are interested in learning leave a comment or send a message and let me know.

Thanks for reading!