[solved] Framerate very low - MonoGame vs. XNA

Hi community,

I’m working on a minecraft-ish 3D engine with lots, lots, LOTS of cubic voxel objects. Well, basically just three sides of a cube, made out of 6 triangles, but that shouldn’t bother us right now. The point is, everything pretty much depends on how fast primitives can be drawn.

Therefore I ran some tests MonoGame vs. XNA. Very basic tests with just 6 primitives for a start and a frame counter. IsFixedTimeStep = false; graphics.SynchronizeWithVerticalRetrace = false. Let’s see, what my Windows system is capable of…

XNA: 8600 - 8900 fps
Monogame: 300 - 8800 fps

Under XNA the framerate ist relatively constant between 8800-8900 fps most of the time with some sudden drops every ten seconds to 8600 fps, which is the usual garbage collection, I guess. The behaviour under MonoGame (with the exact same code!) is very strange, though. It starts out at 8800-8900 fps, but drops to 300 fps within a second. It stays this low for 3-4 seconds, jumps up to 8800 fps for another seconds, just to fall back to 300 again. And so on…

Testing maximum primitives in 1/60 sec:
With XNA I got 4.2 million primitives on screen with 60+ fps.
With MonoGame I have to reduce that to just a few thousand primitives, to stay above 60 fps :frowning:

  • I removed the framecounter and rotated the object instead to sort out
    any problems with the spritefont or the counter. Same effect: With
    XNA the objects spin is fast and constant, with MonoGame its fast,
    slooooow, fast, slooooooow … Its not the counter.
  • I changed the primitives from indexed vertices to non-indexed. No
    difference.
  • I tested it with other graphic adapters (nVidia, AMD, Intel) on other
    systems (Win 7, Win 8). The numbers change, but the tremendous
    difference stays.
  • I switched over to MonoGame/OpenGL. Not helping.

Does anyone made the same experiences?
Is it me or is it MonoGame?

Here’s my test program. Nothing fancy:

public class Game1 : Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont arial;
    VertexPositionColor[] vertices;
    VertexBuffer vertexBuffer;
    IndexBuffer indexBuffer;
    float angle;
    BasicEffect basicEffect;
    Matrix world = Matrix.CreateTranslation(0, 0, 0);
    Matrix view = Matrix.CreateLookAt(new Vector3(2, -2, 3), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
    Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 480f, 0.01f, 100f);
    // Framerate Counter
    private float felapsed;
    private float fframeRate;
    private float fframes;
    public Game1()
        : base()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsFixedTimeStep = false;
        graphics.SynchronizeWithVerticalRetrace = false;
    }
    protected override void Initialize()
    {
        graphics.PreferredBackBufferWidth = 800;
        graphics.PreferredBackBufferHeight = 600;
        graphics.IsFullScreen = false;
        graphics.ApplyChanges();
        Window.Title = "Hi there!";
        base.Initialize();
    }
    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        basicEffect = new BasicEffect(GraphicsDevice);
        arial = Content.Load<SpriteFont>("Fonts/arial");
        angle = 0;
        SetUpVertices();
    }
    protected override void UnloadContent()
    {
    }
    private void SetUpVertices()
    {
        vertices = new VertexPositionColor[7];
        vertices[0] = new VertexPositionColor(new Vector3(0.00f, 0.00f, 0.00f), new Color(50, 50, 250));
        vertices[1] = new VertexPositionColor(new Vector3(-0.15f, -0.26f, 0.00f), new Color(0, 0, 200));
        vertices[2] = new VertexPositionColor(new Vector3(0.15f, -0.26f, 0.00f), new Color(50, 50, 200));
        vertices[3] = new VertexPositionColor(new Vector3(0.30f, 0.00f, 0.00f), new Color(80, 80, 250));
        vertices[4] = new VertexPositionColor(new Vector3(0.15f, 0.26f, 0.00f), new Color(100, 100, 250));
        vertices[5] = new VertexPositionColor(new Vector3(-0.15f, 0.26f, 0.00f), new Color(50, 50, 200));
        vertices[6] = new VertexPositionColor(new Vector3(-0.30f, 0.00f, 0.00f), new Color(0, 0, 250));
        vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), vertices.Length, BufferUsage.WriteOnly);
        vertexBuffer.SetData<VertexPositionColor>(vertices);
        short[] indices = new short[18];
        indices[0] = 0; indices[1] = 1; indices[2] = 2;
        indices[3] = 0; indices[4] = 2; indices[5] = 3;
        indices[6] = 0; indices[7] = 3; indices[8] = 4;
        indices[9] = 0; indices[10] = 4; indices[11] = 5;
        indices[12] = 0; indices[13] = 5; indices[14] = 6;
        indices[15] = 0; indices[16] = 6; indices[17] = 1;
        indexBuffer = new IndexBuffer(graphics.GraphicsDevice, typeof(short), indices.Length, BufferUsage.WriteOnly);
        indexBuffer.SetData(indices);
    }
    protected override void Update(GameTime gameTime)
    {
        // Framerate counter
        felapsed += (float)gameTime.ElapsedGameTime.TotalSeconds;
        if (felapsed > 1.0f)
        {
            felapsed -= 1.0f;
            fframeRate = fframes;
            fframes = 0;
        }
        else
        {
            fframes += 1;
        }
        KeyboardState keyState = Keyboard.GetState();
        if (keyState.IsKeyDown(Keys.Left))
            angle += 0.01f;
        if (keyState.IsKeyDown(Keys.Right))
            angle -= 0.01f;
        base.Update(gameTime);
    }
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);
        spriteBatch.DrawString(arial, fframeRate.ToString("Frames per Second: " + "0"), new Vector2(20, 20), Color.White, 0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
        spriteBatch.End();
        float angle2 = Convert.ToSingle(Math.Sin(angle));
        float angle3 = Convert.ToSingle(Math.Cos(angle));
        view = Matrix.CreateLookAt(new Vector3(2, angle3 * 5, angle2 * 5), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
        basicEffect.World = world;
        basicEffect.View = view;
        basicEffect.Projection = projection;
        basicEffect.VertexColorEnabled = true;
        GraphicsDevice.SetVertexBuffer(vertexBuffer);
        GraphicsDevice.Indices = indexBuffer;
        RasterizerState rasterizerState = new RasterizerState();
        rasterizerState.CullMode = CullMode.None;
        //rasterizerState.FillMode = FillMode.WireFrame;
        GraphicsDevice.RasterizerState = rasterizerState;
        foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 6);
        }
        base.Draw(gameTime);
    }
}

Here is what i find with recent Development Build (http://www.monogame.net/downloads/)

MG: ~1800 fps
XNA: ~2500 fps
The frame rate is constant in my system, I then moved the rasterizerState initilization to LoadContent(), not much changed but it would help to remove GCs and have more stable reading. Debug from Release builds don’t have much change either.

Profiling the MG project revealed that
SpriteBatch.End() 77,98%
SpriteBatchDrawString() 3,57%
Single.ToString() 1,96%
Only 5,15% + 4,68% goes to DrawPrimitives() and set_RasterizerState()

So, I remove the fps drawing and used fraps to mesure fps.
MG: ~2450 fps
XNA: 800-1000 fps (fraps is not very good at XNA/DX9 surfaces)

Well, MG could improve SpriteBatch, XNA can draw string at 2500fps, the headroom is there. It just takes a lot of experimentation with different approaches to see what works best across all platforms, not an easy task, probably that involves some ungly optimizations in the code.
Drawing Primitives is in par with XNA, that 50fps difference is insignificant and probably is caused by fraps too.

I never saw the eratic behavior you describe. Also your code doesn’t draw anything on screen, I don’t see any spining cube.
I suggest to update to latest build, move the 'new RasterizerState() ’ out from Draw() and repeat the test. Also try to profile your program to find what’s really going on.

1 Like

I found that the problem with SpriteBatch is most likely the same that limit the number of primitives you can draw. Both use a DynamicVertexBuffer that use a slow copy operation. The problem is present on x86 platforms.
Using a modified branch of MG I was able to draw your code at ~2350 with SpriteBratch.DrawString and everything. I cound revising the issue and post a clean solution on my fork on github if you want, but for now can you please try to force a x64 build and see if that’s the case?

3 Likes

Hi nkast,

thanks for your answer!

Sorry, I should have mentioned, that the spinning starts, when the left or right arrow key is pressed :smile:
In the test code it’s 6 triangles forming an hexagon with zero depth. And you look at it from the sideview, so there is nothing visible until you start spinning it. I really should have set the camera angle to a top view.

That’s it! That is really it!

Wow, I guess I have that initilization in my Draw() since I started learning XNA from RB-Tutorials a few years ago… It never did any harm when working with XNA, so I didn’t waste another thought about it. But of course you are absolutly right - and now it feels like I couldn’t see the wood, because of all the trees… :no_mouth:

I moved the rasterizerState init to LoadContent(), and now I have constant 8900 fps with MogoGame!

Thanks nkast, you saved my day!

1 Like

States are graphics resource, besides the GC also involve the GPU driver to create one. It’s very strange we see diferent behavior here, for me the state creation was not a problem while you didn’t see other problems from SpriteBatch, I suspect the issue i had in mind only affects AMD (and older Intel) cpus. Anyways, I am happy you solved this.