Bitfield renderstates

Bitfield renderstates

Rate This
  • Comments 1

To manage renderstates in the MotoGP engine, I used the "every man for himself" approach described in my previous post.

To make this fast, I had the following goals:

  • Drawing methods should be able to specify exactly what states they want, using a compact, efficient representation
  • It should be trivially easy to detect one when a drawing method asks for the same states that are already set
  • If only a few states have changed, it should be easy to work out which ones need to be updated on the graphics device

A list containing the values of all possible states would be bulky, inefficient to pass around, and awkward to compare against the previous state settings. It occurred to me that if I could pack the values for multiple states into a bitfield, I could replace this list with a simple integer value.

Trouble is, there are just too many possible states! Consider alpha blending, for instance:

  • AlphaBlendEnable: 1 bit
  • BlendFunction: 5 possible values = 3 bits
  • SourceBlend: 15 possible values = 4 bits
  • DestinationBlend: 15 possible values = 4 bits
  • SeparateAlphaBlendEnabled: 1 bit
  • AlphaBlendOperation: 5 possible values = 3 bits
  • AlphaDestinationBlend: 15 possible values = 4 bits
  • AlphaSourceBlend: 15 possible values = 4 bits
  • BlendFactor: 32 bits

That is already 56 bits for the alpha blending state alone. There is obviously no way the entire graphics device state is going to fit into a single integer.

The trick is to realize that most games don't actually use every possible combination of states. For instance that bulky 32 bit BlendFactor setting is almost always irrelevant: MotoGP only used it in one place (while drawing the reflections if I remember right), and then always wanted it set to 50% gray.

If you make a list of the state settings your game actually uses, you will typically find this is quite small, easily able to fit into a single integer along the lines of:

    enum RenderState : uint
    {
        // Alpha blending states.
        Opaque = 0,
        Translucent = 1,
        Cutout = 2,
        Additive = 3,
        PremultipliedAlpha = 4,
        ParticleAccumulationBuffer = 5,
        ShadowDarkening = 6,
        ThatCrazyBlendModeWeUsedForTheReflections = 7,
        BlendModeMask = 7,

        // Depth buffer states.
        DisableDepth = 0,
        EnableDepth = 8,
        DepthTestButNoWrites = 16,
        StencilShadowMode = 24,
        DepthBufferMask = 24,

        // etc.
    }

Note that these flags are incredibly game specific. A different game, which didn't use the same particle accumulation buffer or reflection rendering techniques as MotoGP, would need a completely different list. This makes it impossible to come up with a single standardized representation, so this bitfield technique is not suitable for generalized engines or frameworks.

For any given game, though, you can work out exactly what states you want to use, then come up with a bitfield representation customized for that specific game. Once you have this encoding, you can write something like:

    void SetRenderStates(RenderState state)
    {
        // See what states have changed.
        RenderState changes = state ^ previousState;
        
        if (changes != 0)
        {
            // Have any alpha blending states changed?
            if ((changes & RenderState.BlendModeMask) != 0)
            {
                switch (state & RenderState.BlendModeMask)
                {
                    case RenderState.Opaque:
                        // todo: set states on the graphics device.
                        break;

                    case RenderState.Translucent:
                        // todo: set states on the graphics device.
                        break;

                    // etc.
                }
            }

            // Have any depth buffer states changed?
            if ((changes & RenderState.DepthBufferMask) != 0)
            {
                // todo.
            }

            // etc.

            // Remember these new state settings for next time.
            previousState = state;
        }
    }

This has some nice properties:

  • All renderstates can be set by one single call
  • It takes only a single integer comparison to detect when nothing needs to be changed
  • Even when some things have changed, bit masks can efficiently identify which areas those are, so there is no need to bother with the alpha blending switch statement if only the depth state has changed

With this system in place, I would call SetRenderStates at the top of every drawing method. For instance the rider shadow rendering would ask for something like:

    SetRenderStates(RenderState.ShadowDarkening |
                    RenderState.StencilShadowMode |
                    RenderState.NoCull |
                    RenderState.CharacterSkinning |
                    RenderState.DisableLighting);

This makes every piece of drawing code entirely self contained, without sacrificing efficiency. If I draw ten character shadows in a row it can efficiently detect that the requested states are already set, but if I interleave my shadows with some debug text rendering, I can still be 100% sure everything will be set up correctly.

  • Nice articles. Keep'em coming, mate!

    As said in your previous article, this could be optimize if you group your entities by category (maybe with an autolist), so you can do the checking once per group, then called draw, reset, and so on ...

    ... which in turn lets you, say, take advantage of instancing, where needed.

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