5.5 C
New York
Saturday, March 15, 2025

rendering – Analyzing lag spike in Monogame


The sport I’m making is having huge lag spikes as soon as each 10 seconds or so whereas operating. The spikes appeared to correlate with rubbish assortment (though I can not be 100% certain), so I believed my downside was that I allotted too many issues within the Replace() operate (I cache all vertex and index buffers, and do not load or unload something whereas the sport is operating, so there should not be something within the Draw() operate that prompts GC.)

Anyhow, I profiled Replace() and Draw() throughout a spike and acquired this:

I’ve highlighted the spike. As you possibly can see, the entire latency comes from the Draw() operate, and it’s sort ‘unfold’ out over a number of frames. So my query is that this:

  • Can the spike nonetheless be brought on by the rubbish collector? Can I do something silly when drawing in Monogame that requires a variety of rubbish assortment?

  • If it WAS rubbish assortment, wouldn’t it 100% present up as Replace() lag? And wouldn’t it present up throughout only one body, or unfold out like seen within the profiling?

  • What may cause such a lag ‘profile’ aside from GC? If setting shader uniform values induced lag for example, it would not present up as spikes wouldn’t it? It will be fixed throughout frames?

  • I’m utilizing Duties for path discovering (so multithreading) – if the lag was associated to that in some way, can I besides that it will present up as Replace() lag?

Extra information:

The spikes disappear after I take away the drawing of the terrain. Drawing the terrain includes drawing a bunch of SurfaceMeshes:

public class SurfaceMesh
{
    public readonly GraphicsDevice graphicsDevice;
    public readonly int vertexCountX;
    public readonly int vertexCountY;

    readonly int triangleCount;

    readonly VertexBuffer vertexBuffer;
    readonly IndexBuffer indexBuffer;

    public SurfaceMesh ( GraphicsDevice graphicsDevice, int nodeCountX, int nodeCountY )
    {
        Debug.Assert(graphicsDevice != null, $"{nameof(SurfaceMesh)} - GraphicsDevice was null.");
        Debug.Assert(nodeCountX > 0, $"{nameof(SurfaceMesh)} - Dimension X [{nodeCountX}] have to be better than zero.");
        Debug.Assert(nodeCountY > 0, $"{nameof(SurfaceMesh)} - Dimension Y [{nodeCountY}] have to be better than zero.");

        this.graphicsDevice = graphicsDevice;
        vertexCountX = nodeCountX + 1;
        vertexCountY = nodeCountY + 1;

        triangleCount = (vertexCountX - 1) * (vertexCountY - 1) * 2;
        int vertexCount = vertexCountX * vertexCountY;
        int indexCount = triangleCount * 3;

        vertexBuffer = new VertexBuffer(graphicsDevice, typeof(VertexPositionColor), vertexCount, BufferUsage.WriteOnly);
        indexBuffer = new IndexBuffer(graphicsDevice, typeof(ushort), indexCount, BufferUsage.WriteOnly);

        ushort[] indices = new ushort[indexCount];
        int currentVertexCount = 0;
        for (int x = 0; x < vertexCountX - 1; x++)
        {
            for (int y = 0; y < vertexCountY - 1; y++)
            {
                int blIndex = (x + 0) + (y + 0) * vertexCountX;
                int tlIndex = (x + 0) + (y + 1) * vertexCountX;
                int trIndex = (x + 1) + (y + 1) * vertexCountX;
                int brIndex = (x + 1) + (y + 0) * vertexCountX;

                indices[currentVertexCount + 0] = (ushort)blIndex;
                indices[currentVertexCount + 1] = (ushort)tlIndex;
                indices[currentVertexCount + 2] = (ushort)trIndex;
                indices[currentVertexCount + 3] = (ushort)blIndex;
                indices[currentVertexCount + 4] = (ushort)trIndex;
                indices[currentVertexCount + 5] = (ushort)brIndex;

                currentVertexCount += 6;
            }
        }
        indexBuffer.SetData(indices);
    }

    public void SetData ( VertexPositionColor[] vertices )
    {
        Debug.Assert(vertices != null);
        Debug.Assert(vertices.Size == vertexCountX * vertexCountY);

        vertexBuffer.SetData(vertices);
    }

    public void Draw ()
    {
        graphicsDevice.SetVertexBuffer(vertexBuffer);
        graphicsDevice.Indices = indexBuffer;
        graphicsDevice.RasterizerState = new()
        {
            CullMode = CullMode.CullCounterClockwiseFace,
        };
        graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, triangleCount);
    }
}

SetData() is ONLY known as the primary body, so all the things on this class is pretty much as good as static. No SurfaceMesh is created or destroyed whereas the sport is operating.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles