Bitfield renderstates

Originally posted to Shawn Hargreaves Blog on MSDN, Friday, October 26, 2007

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:

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:

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:

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.

Blog index   -   Back to my homepage