SpriteBatch sorting part 2

SpriteBatch sorting part 2

Rate This
  • Comments 10

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 is a tilemap containing various arrangements of sand dunes.
  • I have several types of object in the world: palm trees, flags marking the start and finish lines, spectators, a couple of passing kangaroos, and of course the dromedaries themselves.
  • I want the dromedaries to kick up huge clouds of dust, which I'm going to implement as a large number of alpha blended particle sprites that gradually increase in size and rise upwards as they fade away.

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.

  • What is the reason to

    spriteBatch.End();

    be called before

    dustSprites.End(); ?

    Or does the order of placing the ends not matter?

    I didnt find any information on when to use the end() calls if using serveral sprite batches.

    Regards

  • The order of the End calls controls which order the whole contents of one batch will be drawn relative to the other batch, since this is the call that actually sends the sprites to the graphics card. The dust should appear over the top of the solid object sprites, so we End the object batch first, then the dust second.

  • Ah thanks for the answer.

    Now I understand that the placement of the begin call has nothing to do with this (unless it's in immediate mode). Thanks!

  • Hi,

    What happens when one use an effect instead of a spriteBatch ? Because the begin() method that takes 3 arguments doesn't exist ... and I'm having troubles to draw my objects correctly (it's flickering) . In fact, I have some objects overlapping others .... and I couldn't solve the issue ... Thanks in advance

  • MDB: afraid I don't really understand your question. This article is talking about SpriteBatch sorting, which doesn't apply when rendering your own geometry not through SpriteBatch. If you aren't using SpriteBatch, it's up to you to implement whatever other sorting method you like.

    btw. I would generally recommend the forums on creators.xna.com for technical support - blog post comments aren't really the best place for a back and forth conversation!

  • I thought that in DirectX you could specify a combination of both D3DXSPRITE_SORT_DEPTH_BACKTOFRONT and D3DXSPRITE_SORT_TEXTURE, so that it would sort the textures first by layer depth and then by texture. Is this not possible in XNA without doing it in the way you have shown?

    Problem being is that I can't see how I could use that method for a large-scale 2D game. For example lets say I have a CharacterBar class, which draws its own background, then a bar representing health (mana, etc..) and then the numeric values of the bar ontop of that.

    The way I currently do it is by giving the CharacterBar class a layer depth to draw at, and then it goes and adds a tiny amount onto that at which to draw the background, then a bit less to draw the bar, etc..

    But say I have 3 identical bars, that all use the same textures, 2 of which are at the same "base" layer depth, really I would like to draw the two backgrounds, then the two bars, then the two sets of numbers and then followed by the other (entire) bar at the different layer depth. But the SpriteBatch used for all of them is the same.

  • Most of the time, it's best to just draw your sprites in the order you want them to appear with no automatic sorting at all.

    Automatic sorting is always going to be slower than just doing things in the right order in the first place, so you should only use this in complex dynamic situations where a fixed draw order is for some reason not possible.

  • Do you know the rule when you draw 2sprites at the same position and same zdepth?

    I had experienced drawing 2 sets of 2sprites, with same zdepth. The first set on the left of the screen, the second set on the right. But in the left set the 1st sprite was below the 2nd and with the 2nd set it was the contrary.

  • Steph: our sort algorithm is not stable, so if you ask it to sort two sprites with identical depth values, their output order is basically random.

    If you care what order the sprites are sorted in, you need to give each one a different depth value.

  • I hope I get a response

    I have a camera class associated with the spriteBatch class.  I want to implement a UI but I don't want it to zoom in and out.  Could I implement in a separate spritebatch.

Page 1 of 1 (10 items)
Leave a Comment
  • Please add 1 and 2 and type the answer here:
  • Post