Garbage collection again

Hi Guys,

Using MonoGame for an application and I am getting massive frame rate drops every 4 or 5 seconds.

As usual this seems to be the C# garbage collector.

So I did some testing and found that my code was creating 0 garbage.

Yes I know that’s rubbish, but I looked at it in detail and apart from a few strings, it is correct.

When I looked in more detail I found that the problem is rasteriser states. This paste is not going to be pretty, but it has a lot of information.

Anybody have any quick fixes for this?

Type Count Count Diff. Size (Bytes) Total Size Diff. Inclusive Size Inclusive Size Diff. Module


  • WeakReference 17,348 +14,203 416,352 +340,872 416,352 +340,872 mscorlib.dll

  • Microsoft.Xna.Framework.Graphics.RasterizerState 16,899 +14,203 1,487,112 +1,249,864 1,487,472
    +1,249,864 MonoGame.Framework.dll

  • SharpDX.Direct3D11.RasterizerState 16,894 +14,203 1,081,216 +908,992 1,081,216 +908,992 SharpDX.Direct3D11.dll

Do you create any rasterizer state instances in your code? Could you try to create a small sample that shows the issue?

What version of MonoGame are you running?

run time version v4.0.30319.

I do create a rasterizerstate per drawable object, but they are cached

Interesting, your comment made me think and I added a couple of lines of code to my draw methods.

RasterizerState old = device.RasterizerState;

Then at the end

device.RasterizerState = old;

And the garbage has gone away…

Well I have no idea …but it’s fixed for me.

New information about this.

It seems to be more dangerous than I thought.

I ported an app I use a lot to MonoGame from XNA. The app uses a material caching system, so multiple meshes can share materials.

It used to create a new rasteriser state every time the material was applied to the shader. This did not create garbage collection problems in XNA. In MonoGame it made the app unusable.

So I made the rasteriser states part of the material to avoid the garbage issue, and now I cannot render the mesh because I constantly get errors because you cannot modify a rasteriser state after it has been bound to a graphics device.

I tried setting the rasteriser state to a pre-generated default state after each apply of a material, but that made no difference.

I cannot use the trick I used above, because two meshes calling Material.Apply with the same material is perfectly valid. So the old state is the same object as the new state.

I am going to write a work around that builds the rasteriser state in advance, then only changes it when an app setting is changed, but someone needs to look at this.

  1. I never had problems with garbage collection before, it’s constant now with MonoGame.
  2. I never had a binding issue with rasteriser states before.

Something is wrong here. If you can point be at the relevant source code I will have a look for you.

Cheers guys

That’s not a good idea. You should cache all your render state instances since they wrap native resources. Maybe XNA caches the instances.

Why don’t you just initialize the rasterizer state when creating it and don’t try to modify it again?

Can you clarify this? What issue did you run into?

My gut says the problem is not garbage collection. Did you actually profile this?

You mean the error when modifying after binding? That behavior is identical to XNA.

Posting a snippet is always good.

Forgive me if you already know this, but since you mentioned that you got the error cant modify the rasterstate on the graphics object. I thought i should post a example as the syntax for caching renderstates is a little un-intuitive.

Excuse the lack of code casing below.

            // some often used states
            public static class MyStates
            {
                public static RasterizerState rs_regular = new RasterizerState()
                {
                    FillMode = FillMode.Solid,
                    CullMode = CullMode.CullCounterClockwiseFace,
                    MultiSampleAntiAlias = false
                };
                public static RasterizerState rs_solid_nocull = new RasterizerState()
                {
                    FillMode = FillMode.Solid,
                    CullMode = CullMode.None
                };
                public static RasterizerState rs_solid_ccw = new RasterizerState()
                {
                    FillMode = FillMode.Solid,
                    CullMode = CullMode.CullCounterClockwiseFace
                };
                public static RasterizerState rs_solid_cw = new RasterizerState()
                {
                    FillMode = FillMode.Solid,
                    CullMode = CullMode.CullClockwiseFace
                };
                public static RasterizerState rs_wireframe_cullnone = new RasterizerState()
                {
                    FillMode = FillMode.WireFrame,
                    CullMode = CullMode.None
                };
                public static DepthStencilState ds_depthtest_lessthanequals = new DepthStencilState()
                {
                    DepthBufferEnable = true,
                    DepthBufferFunction = CompareFunction.LessEqual
                };
                public static DepthStencilState ds_depthtest_less = new DepthStencilState()
                {
                    DepthBufferEnable = true,
                    DepthBufferFunction = CompareFunction.Less
                };
                public static DepthStencilState ds_depthtest_greater = new DepthStencilState()
                {
                    DepthBufferEnable = true,
                    DepthBufferFunction = CompareFunction.Greater
                };
                public static DepthStencilState ds_depthtest_disabled = new DepthStencilState()
                {
                    DepthBufferEnable = false,
                };
                public static SamplerState ss_point_clamp = new SamplerState()
                {
                    Filter = TextureFilter.Point,
                    AddressU = TextureAddressMode.Clamp,
                    AddressV = TextureAddressMode.Clamp,
                    AddressW = TextureAddressMode.Clamp
                };
            }

then in draw

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.Black);

    GraphicsDevice.DepthStencilState = Gh.MyStates.ds_depthtest_less;
    GraphicsDevice.RasterizerState = Gh.MyStates.rs_solid_cw;

    basicQuadDrawEffect.SetQuadDrawParameters(meshObject.GetTheWorldViewProjection, texture);

    if (keyoptWireframe == 1)
        GraphicsDevice.RasterizerState = Gh.MyStates.rs_wireframe_cullnone;
    else
        GraphicsDevice.RasterizerState = Gh.MyStates.rs_solid_nocull;

            // ^^ there is a bit of overhead to setting the states to the device.
            
            basicQuadDrawEffect.SetQuadDrawParameterTexture(texture2);

            if (keyoptOldMesh == 1)
                DrawMesh(basicQuadDrawEffect.testEffect);
            else
            {
                if (keyoptOldMesh == 3)
                    DrawMesh3(basicQuadDrawEffect.testEffect);
                else
                    DrawMesh2(basicQuadDrawEffect.testEffect);
            }

basically you want to batch i.e. set the state then draw everything that uses that state.

Jjagg,

I do initialise the raster state when I create it, but I also allow dynamic changes to raster state.

I am pretty sure the binding issue is either a bug, or we have a design I hate.

Think of it this way…

Create RasterStateA.
Create RasterStateB.

Bind RasterStateA  and renderer. All good.
BInd RasterStateB  and render. All good.
Modify RasterStateA   CRASH (Cannot modify a rasterstate after being bound to a GraphicsDevice)

Once you bind RasterStateB you should be able to modify RasterStateA. unless they are using the memory block in place instead of doing a copy.I personally hate that practice. I have had to fix so many hard to find bugs in games caused by this practice.

The problem definitely is the garbage collection. Not only can I see the GC in visual studio, I can run reports and see what garbage is being collected.

Willmotil,

Yes that’s basically what I ended up doing