SpriteBatch sorting part 2

Originally posted to Shawn Hargreaves Blog on MSDN, Thursday, December 14, 2006

Continuing from yesterday...

Let's consider a practical example of how to sort sprites for good performance. Imagine I am making a game called Super Dromedary Racer Extreme, which requires a top down view of a piece of desert. My graphical elements are:

The ground obviously needs to be drawn first, so that it will appear below all the other objects. When I loop over my tile map, I will be drawing the different sand dune tiles in a basically random order, but there is a useful trick that can avoid the need for me to bother sorting these sprites. Rather than using a different texture for each type of tile, I can arrange all the possible tile designs into different areas of a single larger texture. In my SpriteBatch.Draw call I can then use the sourceRectangle parameter to specify which part of that large texture should be used for each tile. Since all my ground sprites are now using the same texture, I can draw them without sorting and still get perfect batching, using the fastest possible immediate sort mode:

    spriteBatch.Begin(SpriteBlendMode.Opaque, SpriteSortMode.Immediate, SaveStateMode.None);

// draw all the ground tiles

spriteBatch.End();

Now to draw my game objects. Ideally I would like to be able to do this with some code along the lines of:

    foreach (GameEntity dude in gameEntities)
{
spriteBatch.Draw(dude.Texture, dude.Position, Color.White);

if (dude.HasDustTrail)
{
foreach (DustParticle particle in dude.DustTrail)
{
spriteBatch.Draw(dustTexture, particle.Position, null,
Color.White, 0, Vector2.Zero, 1,
SpriteEffects.None, particle.LayerDepth);
}
}
}

But there are problems with this approach. Because my game entities are not necessarily sorted by their texture, it will cause poor batch performance. Worse, it won't even render the correct thing! Because my alpha blended dust particles are overlapping each other (and could even be merging with particles from a different dust cloud if several dromedaries are racing near each other) these will need to be sorted by depth to get a correct result. It seems impossible to get this right: I need to sort by texture to get good performance, but also by depth to get correct alpha blending, and I can't sort by two different things at the same time! Whatever is a poor graphics coder to do?

I could draw all the entity sprites first using SpriteSortMode.Texture, then end that SpriteBatch and start a new one using SpriteSortMode.BackToFront for drawing the dust particles. But then I would have to loop over all my entity objects twice, which seems wasteful.

The solution is actually very simple:

    spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Texture, SaveStateMode.None);
dustSprites.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None);

foreach (GameEntity dude in gameEntities)
{
spriteBatch.Draw(dude.Texture, dude.Position, Color.White);

if (dude.HasDustTrail)
{
foreach (DustParticle particle in dude.DustTrail)
{
dustSprites.Draw(dustTexture, particle.Position, null,
Color.White, 0, Vector2.Zero, 1,
SpriteEffects.None, particle.LayerDepth);
}
}
}

spriteBatch.End();
dustSprites.End();

This relies on the fact that as long as you aren't using SpriteSortMode.Immediate, SpriteBatch doesn't actually do any drawing until you call the End method. That means you can begin several overlapping batches with different settings, draw sprites in any order using any combination of the batch instances, and control what order the entire contents of each batch get drawn by the ordering of your End calls.

The code is simple, the sorting gives correct alpha blending results, and the batching gives good performance. Hurrah!

Of course, this only works because all the dust particles are using the same texture. If we had several different kinds of dust, sorting these by depth would leave the textures in a random order and thus cause poor batching performance. In that case we could use the same trick as for the ground tiles: arrange all the possible dust particles onto a single larger texture, so we can sort the particles however we like and still avoid having to switch textures too often.

Blog index   -   Back to my homepage