SpriteBatch and custom renderstates in XNA Game Studio 4.0

SpriteBatch and custom renderstates in XNA Game Studio 4.0

  • Comments 6

The theory:

SpriteBatch was designed to provide an easy and efficient way to draw 2D sprites. Everything it can do, you can also do yourself by creating vertices and calling the lower level Draw* APIs, so our goal with SpriteBatch was to make the most common operations easier, not to support every obscure corner case.

In practice:

While testing Game Studio 1.0, we learned that corner cases are common even in simple 2D games! And the SpriteBatch API turned out to be so convenient, it was considerably irritating every time we wanted to do something outside the limited options provided by the SpriteBlendMode enum. We considered adding more state parameters to SpriteBatch.Begin, but our API exposed too many renderstates for that to be practical.

Instead, we added SpriteSortMode.Immediate, which lets you set whatever state you want directly onto the GraphicsDevice. This made many things possible, although not exactly elegant:

  • You have to Begin the SpriteBatch in Immediate mode, then set custom state after SpriteBatch.Begin but before the first Draw
  • Doesn't work if you set state before calling Begin
  • Doesn't work if you change state after the first Draw
  • Can't use custom states with any other SpriteSortMode than Immediate

But hey. This was late in the 1.0 development cycle, so we had to do something simple and low risk.

We can do better:

Game Studio 4.0 makes it possible to specify the entire device state with just four state objects. We changed SpriteBatch.Begin to take advantage of this, removing the SpriteBlendMode enum, and instead directly specifying one or more state objects.

XNA Framework 3.1 XNA Framework 4.0
spriteBatch.Begin(); spriteBatch.Begin();
spriteBatch.Begin(SpriteBlendMode.None); spriteBatch.Begin(SpriteSortMode.Deferred,
                  BlendState.Opaque);
spriteBatch.Begin(SpriteBlendMode.Additive,
                  SpriteSortMode.FrontToBack,
                  SaveStateMode.None);
spriteBatch.Begin(SpriteSortMode.FrontToBack,
                  BlendState.Additive);
spriteBatch.Begin(SpriteBlendMode.AlphaBlend,
                  SpriteSortMode.Deferred,
                  SaveStateMode.None,
                  transformMatrix);
spriteBatch.Begin(SpriteSortMode.Deferred,
                  BlendState.AlphaBlend,
                  null, null, null, null,
                  transformMatrix);

Notes:

  • The order of the first two parameters (blend and sort mode) is reversed

  • The SpriteBlendMode enum is replaced by built-in BlendState objects
    • SpriteBlendMode.AlphaBlend -> BlendState.AlphaBlend
    • SpriteBlendMode.Additive -> BlendState.Additive
    • SpriteBlendMode.None -> BlendState.Opaque

  • SaveStateMode is gone (state objects make this unnecessary)

  • See all those nulls in the last overload? Null means "use the default for this state I didn't bother to specify", but we can use whatever states we like here:
        spriteBatch.Begin(SpriteSortMode.Texture,
                          BlendState.Additive,
                          SamplerState.PointWrap,
                          DepthStencilState.DepthRead,
                          RasterizerState.CullNone);

This is not limited to the built-in state objects, so SpriteBatch can now use any custom blend state, sampler state, etc, with any sort mode.

  • Fantastic! Doing something fairly common in a 2D game such as setting a point texture filter in the sampler state was very frustrating in previous versions. Not on that, the Immediate mode severely limited the flexibility of sprite batches.

    A really common issue that's been an incredible pain for 2D games for as long as they've been in the 3D pipeline is something along these lines:

    * 2D game draws in a specific order (painter's order). Can't think of many complex 2D games that aren't like this.

    * Game takes a huge performance hit due to the number of state changes (texture swaps for each sprite or sprite sheet)

    * Sorting by texture won't work due to painter's order

    * Additive or alpha blending is required due to transparent sprites

    * Effects for a single sprite in the "render list" are a pain - typically I'm going to want to setup a "master" sprite batch in the root of my drawing code and use it for 99% of my painting. When the special sprites where effects are needed, generally the stack is a few layers deep in the drawing logic - can't exactly dispose the root sprite batch, build a new one, dispose that, then recreate the root batch again. Sounds like the new fixes resolve that? :D

    I really, really appreciate the changes for 2D games.

  • That's a great improvement to the API. I've mostly stopped using SpriteBatch due to my laundry list of complaints with it, but this definitely addresses one of the big ones. :)

  • > That's a great improvement to the API.

    I agree.

  • Shawn,

    I like the improvmenets. But when you created the BlendState object -- why not a default of "PreMultiplied" blend? PreMultiplied is actually what most people use outside XNA, because it transparently gives you all kinds of benefits all at the same time:

    1) You can do additive, or subtractive, or both, in the same sprite.

    2) You can more easily avoid fringing around the edges of filtered textures.

    3) You can use Dxt1 transparency.

    Yes, you can create your own BlendState, but a "canned" one seems like it would be worthwhile.

  • > when you created the BlendState object -- why not a default of "PreMultiplied" blend?

    That's exactly what we did!

    http://blogs.msdn.com/shawnhar/archive/2010/04/08/premultiplied-alpha-in-xna-game-studio-4-0.aspx

  • Thank you! Your table saved my day! =D

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