PSM Tutorial #3 : Improving PSM Rendering Performance

2013-04-16 8:21 AM

Introduction

Recently in a newer update of PSM (V1.11) I started to have a lot of performance issues. After a series of discussions with people from the PSM development team and PSM developer forum members I managed to get a profound increase in performance. Before implementing any changes my render function was taking 40-50msec to complete. Now it takes approximately 16msec and stays in that area even if I add more geometry (I suspect that it might actually be faster but VSYNC is keeping it from going under 16msec). First I would like to thank the following forum members and PSM team members for all their help:

  • Nullzero98
  • antumbral
  • mshkla04
  • Morkfang

I’ve taken to a practice when developing engines and games to write my code in a way that gets me what I want happening as fast as possible with little regard for performance overall BUT I implement it with an interface that can easily be swapped out down the line with a better performing algorithm. There are 2 reasons I do this.

It allows me to stay motivated when working on a game rather then get frustrated on some small part of a larger system and lose sight of the whole project. - I can mark many places that can be optimized later so when performance issues do happen I have many easy places to start optimizing. Since the source of my performance issues were allrelatedto my rendering I could very easily track my source of performance issues to a single render method that took all my sprite objects and sent their data to the GPU. It really helped to have made a couple simple debugging classes that consisted of a simple console and timer class. Over the course of this tutorial I will show 3 versions of the rendering code I use, each iteration improves on the performance of the last.

Part 1 : The 40 msec Render Loop

resources/images/2013/04/step1.png

This was my initialunder-optimizedversion of the render loop. What is happening here is I have a map/dictionary of <Texture,List> which allows me to save a set of sprite lists for each texture used in the render pass.

Typically in OpenGL it’s good to keep your render calls as low as possible and generally when you want to change a texture you have to stop rendering, change the texture and start rendering again. Organizing by texture allows me to reduce the number of calls to the number of textures I’m using. The drawback to this is that in order to render the sprites in the correct order I have to use a depth buffer.

This is normally fine but using a depth buffer disables you from being able to use semi-transparent pixels in your artwork. Since my current game doesn’t use semi-transparent pixels it’s not an issue for me.

You may also notice the render loop runs twice. This is because some sprites are marked to either use the camera or not use the camera (GUI elements). Between the camera and non-camera sprite rendering the shader projection/view matrices are recalculated and I restart the rendering. This render method worked fine for a while but changes to the PSM SDK 1.11 exposed performance issues with this render loop.

The renderloop isgenerallyquite unoptimized so this is not really a big surprise. Here is the code for this version of the render loop.

public void Render()
                {
                        shader.CameraEnabled = true;
                        shader.UpdateCamera();
                        RsColor clearColor = RsEngine.Get().RenderSettings.ClearScreenColor;
                        Context.SetClearColor (clearColor.R, clearColor.G, clearColor.B, clearColor.A);
                        Context.Clear ();

                        foreach(List list in images.Values)
                        {
                                vertices = new float[list.Count * 3 * 4];
                                uvs = new float[list.Count * 2 * 4];
                                colors = new float[list.Count * 4  * 4];
                                indicies = new ushort[ (int)(vertices.Length / 2)];
                                texture = RsEngine.Get().TextureManager.LoadTexture(list[0].TextureName);
                                foreach(RsImage sprite in list)
                                {
                                        //counter clockwise vertex addition

                                        //topleft
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopLeft.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopLeft.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopLeft.Z;
                                        //bottomleft
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomLeft.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomLeft.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomLeft.Z;
                                        //bottomright
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomRight.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomRight.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomRight.Z;
                                        //topright
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopRight.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopRight.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopRight.Z;

                                        //toptriangle
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);
                                        //topleft
                                        uvs[uvCounter++] = sprite.UV.TopX;
                                        uvs[uvCounter++] = sprite.UV.TopY;
                                        //bottomleft
                                        uvs[uvCounter++] = sprite.UV.TopX;
                                        uvs[uvCounter++] = sprite.UV.BottomY;
                                        //bottomright
                                        uvs[uvCounter++] = sprite.UV.BottomX;
                                        uvs[uvCounter++] = sprite.UV.BottomY;
                                        //topright
                                        uvs[uvCounter++] = sprite.UV.BottomX;
                                        uvs[uvCounter++] = sprite.UV.TopY;
                                        //colors
                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;

                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;

                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;

                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;
                                }
                                VertexBuffer currentBuffer = new VertexBuffer(vertexCounter/3, indexCounter, VertexFormat.Float3, VertexFormat.Float2, VertexFormat.Float4);

                                Context.SetShaderProgram(shader.GetShader());
                                Context.SetTexture(0, texture);

                                shader.SetMVP();

                                //Setup vertex buffer
                                currentBuffer.SetVertices(0,vertices);
                                currentBuffer.SetVertices(1,uvs);
                                currentBuffer.SetVertices(2,colors);
                                currentBuffer.SetIndices(indicies);
                                //endtest
                                Context.SetVertexBuffer(0, currentBuffer);
                                Context.DrawArrays(DrawMode.Triangles, 0, indexCounter);

                                //RsUtilities.DisposeAndNullify(ref currentBuffer);
                                currentBuffer.Dispose();
                                vertexCounter = 0;
                                indexCounter = 0;
                                uvCounter = 0;
                                colorCounter= 0;
                        }
                        shader.CameraEnabled = false;
                        shader.UpdateCamera();

                        foreach(List list in UIImages.Values)
                        {
                                vertices = new float[list.Count * 3 * 4];
                                uvs = new float[list.Count * 2 * 4];
                                colors = new float[list.Count * 4  * 4];
                                indicies = new ushort[ (int)(vertices.Length / 2)];
                                texture = RsEngine.Get().TextureManager.LoadTexture(list[0].TextureName);
                                foreach(RsImage sprite in list)
                                {
                                        //counter clockwise vertex addition

                                        //topleft
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopLeft.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopLeft.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopLeft.Z;
                                        //bottomleft
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomLeft.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomLeft.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomLeft.Z;
                                        //bottomright
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomRight.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomRight.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.BottomRight.Z;
                                        //topright
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopRight.X;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopRight.Y;
                                        vertices[vertexCounter++] = (int)sprite.QuadData.TopRight.Z;

                                        //toptriangle
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);
                                        //topleft
                                        uvs[uvCounter++] = sprite.UV.TopX;
                                        uvs[uvCounter++] = sprite.UV.TopY;
                                        //bottomleft
                                        uvs[uvCounter++] = sprite.UV.TopX;
                                        uvs[uvCounter++] = sprite.UV.BottomY;
                                        //bottomright
                                        uvs[uvCounter++] = sprite.UV.BottomX;
                                        uvs[uvCounter++] = sprite.UV.BottomY;
                                        //topright
                                        uvs[uvCounter++] = sprite.UV.BottomX;
                                        uvs[uvCounter++] = sprite.UV.TopY;
                                        //colors
                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;

                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;

                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;

                                        colors[colorCounter++] = sprite.Color.R;
                                        colors[colorCounter++] = sprite.Color.G;
                                        colors[colorCounter++] = sprite.Color.B;
                                        colors[colorCounter++] = sprite.Color.A;
                                }
                                VertexBuffer currentBuffer = new VertexBuffer(vertexCounter/3, indexCounter, VertexFormat.Float3, VertexFormat.Float2, VertexFormat.Float4);

                                Context.SetShaderProgram(shader.GetShader());
                                Context.SetTexture(0, texture);

                                shader.SetMVP();

                                //Setup vertex buffer
                                currentBuffer.SetVertices(0,vertices);
                                currentBuffer.SetVertices(1,uvs);
                                currentBuffer.SetVertices(2,colors);
                                currentBuffer.SetIndices(indicies);
                                //endtest
                                Context.SetVertexBuffer(0, currentBuffer);
                                Context.DrawArrays(DrawMode.Triangles, 0, indexCounter);

                                //RsUtilities.DisposeAndNullify(ref currentBuffer);
                                currentBuffer.Dispose();
                                vertexCounter = 0;
                                indexCounter = 0;
                                uvCounter = 0;
                                colorCounter= 0;
                        }

                        // Present the screen
                        Context.SwapBuffers ();
                        Clear(); //clears the sprite lists and resets counters
                }

Part 2 : The 32 msec Render Loop

In this version of the render loop I have had some help from the community as the PSM developer forums.

One of the first issues identified with my code is the constant re-allocation of arrays each frame for holding data such as vertices, uv’s, colors and indices. I tend to do a lot of C++ programming in OpenGL where typically you just pass a pointer to the start of an array then say how long that array is to opengl andconstantlywrite over those values when you need to change. I didn’t fully understand PSM’s OpenGL implementation at the time and was suffering an exception that says my array wasn’t the correct size so to alleviate that issue I merely re-created the array each frame to be the correct size.

This is generally a bad thing to do. You typically want to avoid dynamic allocation at runtime as much as possible. Especially if it’s happening every frame. I also re-created my vertex buffer object each frame then disposed of it when I was finished drawing. It would be better to reuse my vertex buffer object rather than re-create it each frame.

After converting my data arrays and vertex buffer to static collections created at the initialization of the program I saw approximately an 8msec boost in performance. It was pretty good but still unacceptable. Frankly I expected it to be better. Still it was a welcome improvement in performance.

     public void Render()
                {

                        //currentBuffer = new VertexBuffer(MAX_SPRITES, MAX_INDICIES, VertexFormat.Float4, VertexFormat.Float2, VertexFormat.Float4);
                        shader.CameraEnabled = true;
                        shader.UpdateCamera();
                        RsColor clearColor = RsEngine.Get().RenderSettings.ClearScreenColor;
                        Context.SetClearColor (clearColor.R, clearColor.G, clearColor.B, clearColor.A);
                        Context.Clear ();

                        foreach(List list in images.Values)
                        {
                                texture = RsEngine.Get().TextureManager.LoadTexture(list[0].TextureName);
                                currentSpriteVertex = 0;
                                vertexCounter = 0;
                                indexCounter = 0;
                                foreach(RsImage sprite in list)
                                {
                                        //counter clockwise vertex addition
                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopLeft.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopLeft.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopLeft.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomLeft.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomLeft.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomLeft.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomRight.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomRight.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomRight.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopRight.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopRight.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopRight.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);

                                }

                                Context.SetShaderProgram(shader.GetShader());
                                Context.SetTexture(0, texture);

                                shader.SetMVP();

                                //Setup vertex buffer
                                currentBuffer.SetVertices(spriteData);
                                currentBuffer.SetIndices(indicies);
                                //endtest
                                Context.SetVertexBuffer(0, currentBuffer);
                                Context.DrawArrays(DrawMode.Triangles, 0, indexCounter);

                                //RsUtilities.DisposeAndNullify(ref currentBuffer);
                                //currentBuffer.Dispose();

                        }
                        shader.CameraEnabled = false;
                        shader.UpdateCamera();
                        currentSpriteVertex = 0;
                        foreach(List list in UIImages.Values)
                        {
                                texture = RsEngine.Get().TextureManager.LoadTexture(list[0].TextureName);
                                currentSpriteVertex = 0;
                                vertexCounter = 0;
                                indexCounter = 0;
                                foreach(RsImage sprite in list)
                                {
                                        //counter clockwise vertex addition
                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopLeft.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopLeft.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopLeft.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomLeft.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomLeft.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomLeft.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomRight.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomRight.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomRight.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopRight.X;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopRight.Y;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopRight.Z;vertexCounter++;
                                        spriteData[currentSpriteVertex].Pos.W = 1;

                                        spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
                                        spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

                                        spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
                                        spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
                                        spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
                                        spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

                                        ++currentSpriteVertex;

                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
                                        indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);
                                }
                                Context.SetShaderProgram(shader.GetShader());
                                Context.SetTexture(0, texture);

                                shader.SetMVP();

                                //Setup vertex buffer
                                currentBuffer.SetVertices(spriteData);
                                currentBuffer.SetIndices(indicies);
                                //endtest
                                Context.SetVertexBuffer(0, currentBuffer);
                                Context.DrawArrays(DrawMode.Triangles, 0, indexCounter);

                                //RsUtilities.DisposeAndNullify(ref currentBuffer);
                                //currentBuffer.Dispose();

                        }

                        // Present the screen
                        Context.SwapBuffers ();
                        Clear(); //clears the sprite lists and resets counters
                }

 }

Part 3 : The 16 (or less) msec Render Loop

The changes in this version of the render loop were by far the most profound when it came to performance improvement.After I made these changes my game ran perfectly.

Generally the rule of thumb I’ve gone by for PSM is that it’s important to avoid DrawArrays calls as much as possible. I’ve done this but it didn’t have as profound an impact as I’d hoped.

After discussing with the PSM developers and forums members it turns out the true culprit is SetVertices. This is the call that transfers your data to the GPU for processing. So ideally to get the best rendering performance you would use a combination of reduced calls to SetVertices and an interleaved array of data.

This means that you will need to pack the data you plan to send to your shader into a struct then pack a set of these structs into one single array so that you only need to call SetVertices once (assuming you are only using one or more shaders with the exact same inputs) per draw call. In my case I’m drawing sprites which are 2D quads that have a color, text, and position for each vertex. Thus I made a struct that looks like this:

internal struct SpriteVertex
{
public Vector4 Pos;
public Vector2 UV;
public Vector4 Color;
}

Note that you will need to create a VertexBuffer that fits the format of the structure you created. As you can see the last 3 parameters mirror the size of the elements of my structure.

    currentBuffer = new VertexBuffer(MAX_SPRITES, MAX_INDICIES, VertexFormat.Float4, VertexFormat.Float2, VertexFormat.Float4);

Now the next issue is that I use multiple textures (like almost any real-world use case) and I need the ability to switch textures yet all my data is in one big array.

The solution is to keep track of some information about the data in my vertex buffer about which vertices use which texture (or shader) then call DrawArrays multiple times with range parameters and in between each of those calls I call SetTexture or SetShader as needed. For this I introduce another struct which is used to keep track of information for groups of vertices.

internal struct SpriteOffsetTextureCameraTriplet
{
public int Marker;
public string Texture;
public bool CameraEnabled;
}

The marker represents the position in the vertex buffer to switch the texture.

The texture name is the path to the texture I use to switch textures (textures are already pre-cached, the path is actually a key into a dictionary of textures) and the CameraEnabled bool is a indicator of whether we need to re-calculate the shaders uniform matrices to handle world elements or UI elements.

I declare and define a list of SpriteOffsetTextureCameraTriplet’s and as I go through all the sprites I add a SpriteOffsetTextureCameraTriplet for each time something about the sprites changes (be it a texture or a CameraEnabled change).

Here is the new version of the render method that takes into account the changes:

public void Render()
{
//write our sets of quads affected by the camera
foreach(List list in images.Values)
{
foreach(RsImage sprite in list)
{
//counter clockwise vertex addition
spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);
}
//record the range of quads and the texture they use
SpriteOffsetTextureCameraTriplet triplet;
triplet.Texture = list[0].TextureName;
triplet.Marker = indexCounter;
triplet.CameraEnabled = true;
this.drawOffsets.Add(triplet);
}
//write our sets of quads not affected by the camera
foreach(List list in UIImages.Values)
{
texture = RsEngine.Get().TextureManager.LoadTexture(list[0].TextureName);
foreach(RsImage sprite in list)
{
//counter clockwise vertex addition
spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);
}
//record the range of quads and the texture they use
SpriteOffsetTextureCameraTriplet triplet;
triplet.Texture = list[0].TextureName;
triplet.Marker = indexCounter;
triplet.CameraEnabled = false;
this.drawOffsets.Add(triplet);
}
//clear the screen
RsColor clearColor = RsEngine.Get().RenderSettings.ClearScreenColor;
Context.SetClearColor (clearColor.R, clearColor.G, clearColor.B, clearColor.A);
Context.Clear ();
//set our shader program
Context.SetShaderProgram(shader.GetShader());
//since we know the first set of quads use the camera default it to true in the shader and update
//the transform matrices
shader.CameraEnabled = true;
shader.UpdateCamera();
shader.SetMVP();
//move all data via SetVertices
currentBuffer.SetVertices(spriteData);
currentBuffer.SetIndices(indicies);
//Set vertex buffer
Context.SetVertexBuffer(0, currentBuffer);
//Go through all the previously recorded triplets of array markers, texture names and camera usage booleans
//and drawarrays for each time this happens
for (int i = 0; i < drawOffsets.Count;i++) { SpriteOffsetTextureCameraTriplet pair = drawOffsets[i]; int range; int pos; if (i > 0)
{
pos = drawOffsets[i - 1].Marker;
range = pair.Marker - drawOffsets[i - 1].Marker;
}
else
{
pos = 0;
range = pair.Marker;
}
if (shader.CameraEnabled != pair.CameraEnabled)
{
shader.CameraEnabled = pair.CameraEnabled;
shader.UpdateCamera();
shader.SetMVP();
}
Context.SetTexture(0, RsEngine.Get().TextureManager.LoadTexture(pair.Texture));
Context.DrawArrays(DrawMode.Triangles, pos, range);
}
Context.SwapBuffers ();
Clear(); //clears the sprite lists and resets counters
drawOffsets.Clear();
}

As you can see the Render method is quite different now. SetVertices isguaranteedto only be called once. The performance improvement has been profound. I’m not exactly pushing the envelope even for a 2D game but currently I have no performance issues.

So in closing the main thing you want to take away from this is that when working on PSM the number one thing you want to do to keep performance high is to keep the number of SetVertices calls as low as possible. DrawArrays is an expensive call as well but SetVertices seems to really be the killer.

For reference here is the entire class I use for rendering sprites:

using System;
using System.Collections.Generic;
using Sce.PlayStation.Core.Graphics;
using Sce.PlayStation.Core;

namespace RushLib
{
internal struct SpriteVertex
{
public Vector4 Pos;
public Vector2 UV;
public Vector4 Color;
}

internal struct SpriteOffsetTextureCameraTriplet
{
public int Marker;
public string Texture;
public bool CameraEnabled;
}

public class RsImageRenderer
{
public GraphicsContext Context;
//VertexBuffer vertexBuffer;

RsShader shader;

Dictionary<string,List> images = new Dictionary<string, List>();
Dictionary<string,List> UIImages = new Dictionary<string, List>();

//TODO : These values are wonky. Check them on paper
const int MAX_SPRITES = 4096;
const int MAX_VERTICES = MAX_SPRITES * 4;
const int MAX_VERTEX_FLOATS = MAX_VERTICES * 3;
const int MAX_INDICIES = MAX_VERTEX_FLOATS / 2;
const int MAX_COLOR_FLOATS = MAX_VERTICES * 4;
const int MAX_UV_FLOATS = MAX_VERTICES * 2;

//float[] vertices = new float[MAX_VERTEX_FLOATS];
//float[] uvs = new float[MAX_UV_FLOATS];
//float[] colors = new float[MAX_COLOR_FLOATS];
ushort[] indicies = new ushort[MAX_INDICIES];

SpriteVertex[] spriteData = new SpriteVertex[MAX_SPRITES];
VertexBuffer currentBuffer;
List drawOffsets = new List();

//float[] vertices = new float[MAX_VERTICES_FLOAT_VALS];
//float[] uvs = new float[MAX_UVS];
//float[] colors = new float[MAX_COLORS];
//ushort[] indicies = new ushort[MAX_INDICIES];

int vertexCounter = 0;
int indexCounter = 0;
int currentSpriteVertex = 0;
Texture2D texture;

//List buffers;
//int currentBufferCounter = 0;

public RsImageRenderer (RsCamera camera)
{
//Context = new GraphicsContext(10,10,PixelFormat.Rgba,PixelFormat.Depth16,MultiSampleMode.None);
Context = new GraphicsContext();
currentBuffer = new VertexBuffer(MAX_SPRITES, MAX_INDICIES, VertexFormat.Float4, VertexFormat.Float2, VertexFormat.Float4);
shader = new RsShader(this,camera);
camera.X = camera.X + Context.Screen.Width/2;
camera.Y = camera.Y + Context.Screen.Height/2;

  //Fix for zDepth but doesnt work with alpha test. Instead using discard in the pixel shader.
Context.Enable(EnableMode.DepthTest);

//Context.SetDepthFunc(DepthFuncMode.Always, true);
//Context.Enable(EnableMode.Blend);
        //Context.SetBlendFunc(BlendFuncMode.Add, BlendFuncFactor.One, BlendFuncFactor.OneMinusSrcAlpha);
//Context.SetBlendFuncAlpha( BlendFuncMode.Add, BlendFuncFactor.One, BlendFuncFactor.OneMinusSrcAlpha );
}

public void AddImage(RsImage sprite)
{
if (sprite.UsesCamera)
{
if (sprite.ZDepth < 1)
{
sprite.ZDepth = 1;
}
List sprList;
if (sprite.TextureName == null)
{
return;
}
if (images.ContainsKey(sprite.TextureName))
{
sprList = images[sprite.TextureName];
sprList.Add(sprite);
}
else
{
sprList = new  List();
sprList.Add(sprite);
images.Add(sprite.TextureName,sprList);
}
}
else
{
if (sprite.ZDepth > 1)
{
sprite.ZDepth = 1;
}
List sprList;
if (sprite.TextureName == null)
{
return;
}
if (UIImages.ContainsKey(sprite.TextureName))
{
sprList = UIImages[sprite.TextureName];
sprList.Add(sprite);
}
else
{
sprList = new  List();
sprList.Add(sprite);
UIImages.Add(sprite.TextureName,sprList);
}
}
}

public void Render()
{
//write our sets of quads affected by the camera
foreach(List list in images.Values)
{
foreach(RsImage sprite in list)
{
//counter clockwise vertex addition
spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);
}
//record the range of quads and the texture they use
SpriteOffsetTextureCameraTriplet triplet;
triplet.Texture = list[0].TextureName;
triplet.Marker = indexCounter;
triplet.CameraEnabled = true;
this.drawOffsets.Add(triplet);
}
//write our sets of quads not affected by the camera
foreach(List list in UIImages.Values)
{
texture = RsEngine.Get().TextureManager.LoadTexture(list[0].TextureName);
foreach(RsImage sprite in list)
{
//counter clockwise vertex addition
spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomLeft.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomLeft.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomLeft.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.TopX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.BottomRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.BottomRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.BottomRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.BottomY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

spriteData[currentSpriteVertex].Pos.X = (int)sprite.QuadData.TopRight.X;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Y = (int)sprite.QuadData.TopRight.Y;vertexCounter++;
spriteData[currentSpriteVertex].Pos.Z = (int)sprite.QuadData.TopRight.Z;vertexCounter++;
spriteData[currentSpriteVertex].Pos.W = 1;

spriteData[currentSpriteVertex].UV.X = sprite.UV.BottomX;
spriteData[currentSpriteVertex].UV.Y = sprite.UV.TopY;

spriteData[currentSpriteVertex].Color.R = sprite.Color.R;
spriteData[currentSpriteVertex].Color.G = sprite.Color.G;
spriteData[currentSpriteVertex].Color.B = sprite.Color.B;
spriteData[currentSpriteVertex].Color.A = sprite.Color.A;

++currentSpriteVertex;

indicies[indexCounter++] = (ushort)((vertexCounter/3)-4);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-1);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-3);
indicies[indexCounter++] = (ushort)((vertexCounter/3)-2);
}
//record the range of quads and the texture they use
SpriteOffsetTextureCameraTriplet triplet;
triplet.Texture = list[0].TextureName;
triplet.Marker = indexCounter;
triplet.CameraEnabled = false;
this.drawOffsets.Add(triplet);
}
//clear the screen
RsColor clearColor = RsEngine.Get().RenderSettings.ClearScreenColor;
Context.SetClearColor (clearColor.R, clearColor.G, clearColor.B, clearColor.A);
Context.Clear ();
//set our shader program
Context.SetShaderProgram(shader.GetShader());
//since we know the first set of quads use the camera default it to true in the shader and update
//the transform matrices
shader.CameraEnabled = true;
shader.UpdateCamera();
shader.SetMVP();
//move all data via SetVertices
currentBuffer.SetVertices(spriteData);
currentBuffer.SetIndices(indicies);
//Set vertex buffer
Context.SetVertexBuffer(0, currentBuffer);
//Go through all the previously recorded triplets of array markers, texture names and camera usage booleans
//and drawarrays for each time this happens
for (int i = 0; i < drawOffsets.Count;i++) { SpriteOffsetTextureCameraTriplet pair = drawOffsets[i]; int range; int pos; if (i > 0)
{
pos = drawOffsets[i - 1].Marker;
range = pair.Marker - drawOffsets[i - 1].Marker;
}
else
{
pos = 0;
range = pair.Marker;
}
if (shader.CameraEnabled != pair.CameraEnabled)
{
shader.CameraEnabled = pair.CameraEnabled;
shader.UpdateCamera();
shader.SetMVP();
}
Context.SetTexture(0, RsEngine.Get().TextureManager.LoadTexture(pair.Texture));
Context.DrawArrays(DrawMode.Triangles, pos, range);
}
Context.SwapBuffers ();
Clear(); //clears the sprite lists and resets counters
drawOffsets.Clear();
}

private void Clear()
{
images.Clear();
UIImages.Clear();
currentSpriteVertex = 0;
vertexCounter = 0;
indexCounter = 0;
}
}
}

Tags: psm_tutorial