I have newbie question. What is the “ideal” usage of SpriteBatches in MonoGame? Currently, each entity displayed in my game has its own SpriteBatch (character, tiles for the environment, etc), but I realized that I just made my life pretty difficult with that if I want the rendering to happen in a specific order (the depth param is ignored because of this).
I have 2 ideas to solve it:
1, Using only 1 spritebatch for everything on my screen, that would make ordering very easy as I can simply pass the depth for the draw command. Not sure about how it would perform though.
2, Using 1 spritebatch for each “layer” in my game (I group my entities into “layers”, like characters, environment, background layers that scroll with different speed, etc), having a priority set to these layers (Draw would be called on the highest priority layer first, then the second…) and just use the depth paramter if I need ordering within a layer (for example if my character overlaps with an enemy).
Which one would be preferable in terms of graphics performance in MonoGame? Having 1 spritebatch for all, or the second approach?
It depends on what I’m writing, but I usually do one of those. You really only need a new spritebatch if you want to apply a new effect (ie, pixel shader) as I don’t think you can mix them. When you do that, sometimes you still want to layer that sprite batch effect with other rendering, and so it’s maybe helpful to just have a spritebatch per sprite layer you want to render.
If you don’t render with any shaders, you really only need one. Maaaaaaaybe a second for any render target rendering you do before your actual draw pass.
I’m sure others have different approaches, that’s just the one that’s tended to work fairly well for me
Thank you very much for your answer again !
In the meantime, I made an experiment branch to do some measuring. With about 6000 objects on my screen, the my findings were the following:
1, My original code performed terribly, each object on the screen had it’s own spritebatch. I got around 50 FPS, but this code meant around 6000 Begin() and End() calls per draw cycle which ate up my (non-potato) hardware.
2, I changed my code so that my layers had their own spritebatches (2-3 big layers and another 2-3 smaller layers), each having their own spritebatches with Begin() and End() once per every Draw cycle, rendering the contained objects in bigger batches. This performed very well, I maxed out my framerate again.
3. I did a test by using one spritebatch and one Begin()/End() call for all 6000 objects, this performed the same as test 2, but this solution would tie my hands a bit too much.
Hope others will find this information useful!
Make sure you organise your tile sheets well.
Each time spritebatch has to use a new texture it will internally do another spritebatch call.
So if you had 6000 objects to draw in one call and they are constantly flicking between different texture2ds you will loose a lot of performance.
You can also increase performance by sorting your layer calls manually by drawing them in order using spritesortmode.deferred
If your using front to back or back to front with a layer value entered it will have to sort them each time it batches a draw call
That was a super useful comment, a million thanks! I already made code changes based on this information
The vast majority of that 6000 textures comes from just a few big spritesheets and they were all separate Texture2Ds, so now I changed my code to have one Texture2D for each spritehseet, and just pass this Texture2D with different sourcerectangles for the different objects. Later on I will optimize and draw “big sprites” as one image instead of drawing them as 16x16 tiles, right now this was the fastest way to import and display something from LDTK level editor (https://ldtk.io/) to display nice on my screen instead of placeholder boxes
“You can also increase performance by sorting your layer calls manually by drawing them in order using spritesortmode.deferred” this is nice, but then this will screw up my planned Y sorting logic, can you suggest a better way to do it? (to be able to order sprites based on the Y axes, so for example my character can walk “behind” and “in front of” a tree). Your comment made me think about a solution of making a separate layer only for objects that must be “Y-sorted” (for this, I’m using monogames SpriteSortMode.FronToBack), and so sorting will only happen to objects where it’s absolutely necessary. But any better way to solve this problem is very welcome
@Lajbert This response on stackoverflow should answer your most recent question: https://stackoverflow.com/a/26142489/168235
In short, put your sort order into depth and use BackToFront not deferred (if you want to make your object draws explicitly ordered in some way but not call draw in any particular order).
Just agreeing with and adding to previous info presented;
Best results I’ve found so far:
- Deferred but manually setup to draw in correct order
- uvatlas/spritesheet sourcerects
- I use minimal number of spriteBatches (usually 1) and pass in constructors to be kept as a reference - I figure this reduces use of video-card memory (or other)
- Also have a quadBatch and spriteBlast classes which mimic SpriteBatch but contain different specialized draws.
a) QuadBatch accepts a spriteSheet in Begin and after that the draws only take in sourceRects or pre-setup UV coords and also can use depth-buffer and not need sorting (altho I would still sort due to possible transparency artifacts along edges caused by depth-rejection testing + non-nearest filtering)
This customized approach for me worked about 12.5% faster.
b) spriteBlast actually predicts and removes redundant calculations and has drawing methods where all calculations are performed in GPU. Test I did showed to be about 21% faster. Hard to say for sure tho because at 100,000 sprites of around 64x64 it seemed to reach a point where it was the pixel-fill on the GPU that was causing it to tap out rather than anything else.
So SpriteBatch alone should do the trick if managed right. I think you’re definitely on the right track with the previous suggestions.
Very useful tips, thank you very much