I’m having an issue with random stutters in framerate in a newly started 2D game project using spritebatch. I’ve set various stopwatches, and as far as I can tell, my “Draw” method on average takes less than 1 millisecond to complete, but at irregular intervals of about 10-60 seconds, it will randomly take 80-120 milliseconds to complete, causing a noticeable stutter. This can happen even if nothing in the game state changes between calls to Draw, so it is drawing the exact same scene.
I don’t think it is due to my draw code being inefficient, as I can put a loop around it and draw the whole scene 50 times consecutively without any perceptible drop in performance, and when the stutters happen, they are still 80-120 ms even when drawing the scene 50 times.
I’m also pretty sure it isn’t due to garbage collection, as I’ve tracked that and it doesn’t coincide with the performance drops. There isn’t much garbage to collect anyway, as I’ve barely started on the project and am currently just drawing a tile grid to the screen and moving it around.
I’m using monogame for the first time since 2016 and I don’t remember having any problems with it back then. I also don’t remember having this problem in XNA 12+ years ago, and I’m pretty sure my code quality was significantly worse back then.
Does anyone know what the cause of this might be, and how to fix it? I’m using MonoGame.Framework.DesktopGL 220.127.116.111 in Visual Studio 2022.
The problem doesn’t seem to happen if I call SpriteBatch.Begin/End for every sprite, or if I use SpriteSortMode.Immediate. So something about drawing a tile layer in a single pass is causing sporadic performance problems.
I’m currently making around 250-300 calls to SpriteBatch.Draw(), all using the same plain square texture. This doesn’t seem like it should be too many.
I have no idea what’s happening, but my first suggestion would be to move from “I’m pretty sure it isnt due to garbage collection” to “I’m absolutely sure”, because it really sounds a lot like a GC issue. I think the GC can triggered pretty muched anywhere (inside SpriteBatch.End, for example), so sometimes it’s better to be absolutely sure it’s not the GC.
Just print GC.CollectionCount (0) , GC.CollectionCount (1) and GC.CollectionCount (2) somewhere (screen, Debug.WriteLine, …) and check if when the hiccup happens, those values are incremented.
It’s a 1 minute test and you’ll discard the GC for sure.
I’ve had this same problem. It had nothing to do with garbage collection for me. Even a near empty project that does nothing else but loading in a single Texture2D and moves its x position also caused significant stuttering. It occured in either a fixed or variable time step with or without using delta time in movement. Here’s what I found that may help:
Try running the game in a DirectX project rather than OpenGL. The same code ran perfectly smooth in DirectX for me (of course, I don’t want to make a DirectX only game either, but it’s just a way to troubleshoot what’s causing the stutter).
Try running the game in full screen mode and/or disabling vsync, this fixes the stutter for me. Now the stutter only ever occurs when the game is windowed.
Also check your monitor’s refresh rate. My main pc’s monitor has an awkward 59.951hz refresh and I get stutter, but the same game tested on another laptop with a 60hz refresh was smooth in both window and fullscreen.
So for me the problem was a sucky monitor with a sucky refresh rate. The only solution I can think of is to allow the option to toggle fullscreen and toggle vsync in the game’s settings. That and save up for a new monitor.
My initial test wasn’t very thorough - I just saw that collections were happening without any problem, and assumed that meant it wasn’t the GC. But I did another test now, and while most garbage collections do not cause any performance drop, the performance drops do seem to coincide with garbage collections. I guess it happens once every 7-8 collections on average.
I’m somewhat confused as to why this should be happening, though. My project currently has hardly anything in it, and after the initial setup, there isn’t a single new allocation, and nothing falls out of scope, so I have no idea what it is collecting. I’m starting to think it must be garbage generated internally by the spritebatch.
If it is the garbage collector, what can I do about it? There’s hardly anything in the project now, so I can only see this problem getting worse once I start populating the game world with stuff. This is starting to feel like an argument against using C# for game dev.
I think this must be a different issue, as GL or DX makes no difference for me, and neither does windowed or full screen. According to windows my monitor refresh rate is 60.000Hz, so if that’s accurate then there’s no problem there.
GC can be annoying sometimes because you have to code having it constantly in mind, but it’s not that hard once you get used to it.
The collection count 0 is very quick, and collection count 2 is the slowest. It is probable that the big hiccup happens on CC 2. CC0 are quite inocuous (however the less you have, the best) and CC2 should be avoided at all costs.
The best way to find where the garbage is created is running the profiler: If Visual Studio, “Debug → Performance profiler” then choose NET object allocation tracking (I’m not an expert with it because I use Scitech’s). Sometimes it’s something stupid like creating a new SamplerState object every frame, or things like this. Strings are also quite garbage-prone. It may be a SpriteBatch problem but afaik there are no leaks in the latest version, and if there’s one it may be a bug.
The main reason I suspect SpriteBatch is that the problem doesn’t happen if I remove the calls to SpriteBatch.Draw or if I set SpriteSortMode.Immediate, and when the performance drop happens it is ALWAYS during SpriteBatch.End() and never at any other time. I don’t think it’s my garbage being collected because I’m not creating any objects during either the Update or Draw and only creating a few ints, vector2s, and a rectangle on the stack. If I remove just the calls to SpriteBatch.Draw() and put a for loop around everything else in my Game.Draw() and run it 100 times per frame, the performance drop doesn’t happen.
The annoying thing is that I’m not doing anything I haven’t done before. 10+ years ago I used to make terrible beginner games in XNA on a cheap 2006 laptop and never had performance issues like this.
I’m not doing anything fancy, just iterating through the visible tiles in an array and drawing the same square texture to the screen for each one, plus a rectangle for the player character collision box.
I think the GC might be a red herring. I’ve had a couple of tests now where the performance drop has happened without there being a GC collection. Is it possible that whatever is causing the performance hiccups is also usually triggering a garbage collection, so the GC is a symptom rather than the cause?
I am suspecting that the GC happens because the framework has to occasionally allocate/increase the internal buffer. To rule this out you’d have to initially draw a larger number of sprites on startup.
In the past I’ve found that some calls produce garbage and hiccups in Debug mode but doesn’t in Release mode (or profiler mode). And I mean not only to MonoGame, but is seems more of later Visual Studio versions (I started to notice it with VS2017, but was not on 2012 for sure). The problem is that right now I can’t remember what caused it…
Does running the project in release also produce those hiccups?
Also nkast points a valid issue too. SpriteBatch probably uses an array that is reallocated as it grows. To put a naive example, if you start drawing 100 and as you move through the game the count goes to 200, that array of 100 is deallocated and a new array of 200 is reallocated → garbage.
Multiplying by 5 the number of draws won’t do the trick. You should multiply by 5 the number of draws in the first frame only. (I don’t know if you’ve done it this way, the message you wrote seems to say you’re multiplying by 5 every frame) Then the array would grow to (in example) 500 at the start, and afterwards it’d be kept in a safe zone without reallocations.
Or you could also use the constructor to SpriteBatch that allows you to define the initial capacity (I’ve never used, and actually I didn’t know it existed until I’ve found it today seaching for answers)
I’ve done some further testing, and it seems I can call SpriteBatch.Draw() up to 41 times between SpriteBatch.Begin() and SpriteBatch.End() without triggering the problem, but any more than that, and I get the stuttering.
I commented out everything in my update loop, so it’s basically just drawing the scene and doing nothing else. This time there was no garbage collection during the test, but the draw stutter still happened. So I guess this means it can’t be GC-related.
The stutter does coincide a lot with the GC, but sometimes happens without it and vice versa. I think there’s an interaction there, but it can’t be the actual cause of the problem.