State objects in XNA Game Studio 4.0

State objects in XNA Game Studio 4.0

  • Comments 30

The most-often-linked-to article I ever wrote is about renderstates, so it should come as no surprise that we tried to improve this area in Game Studio 4.0.

There are fundamentally only two sane ways to manage renderstates:

  1. Assume nothing: explicitly set everything you depend on before drawing

  2. Assume fixed defaults: if you change anything, you must put it back when you are done

Previous versions of Game Studio supported both approaches, but neither worked particularly well:

  1. Our API exposed over 70 different states, so explicitly setting them all was awkward and slow

  2. We provided StateBlock and SaveStateMode to help with the "put it back when you are done" approach, but these were extremely slow

Luckily for us, our colleagues over in the native DirectX team grappled with this very issue a few years earlier, and came up with a great solution. For Game Studio 4.0, we basically just borrowed the same state objects design that is used in DirectX 10 and 11.

 

Change Summary

  • Replaced RenderState class with three new state objects: BlendState, DepthStencilState, and RasterizerState

  • Replaced the old SamplerState class with a new SamplerState state object (same name, different behavior)

  • Removed StateBlock and SaveStateMode

This new API makes it easy and efficient to explicitly set all state before every drawing operation. We don't provide built-in support for the "save and restore state" pattern, but state objects make it easy to implement that yourself if you want it. Generally, though, we believe that explicitly setting all state is a better and more robust approach than saving and restoring.

 

Using State Objects

First, we create a state object:

    BlendState blendSubtract = new BlendState();

Then we set its properties:

    blendSubtract.ColorSourceBlend = Blend.SourceAlpha;
    blendSubtract.ColorDestinationBlend = Blend.One;
    blendSubtract.ColorBlendFunction = BlendFunction.ReverseSubtract;

    blendSubtract.AlphaSourceBlend = Blend.SourceAlpha;
    blendSubtract.AlphaDestinationBlend = Blend.One;
    blendSubtract.AlphaBlendFunction = BlendFunction.ReverseSubtract;

Finally, we tell the graphics device to use this custom state:

    GraphicsDevice.BlendState = blendSubtract;

The first time we bind a state object to the device, it becomes immutable, so we cannot change its properties after we have rendered with it. If we later want a different combination of properties, we must create a different state object instance.

 

Using State Objects Wisely

The advantage of state objects is that a single object can atomically specify a whole family of related state settings. This is handy for the developer, and also for the graphics runtime because it gives us the chance to process these state values just once (the first time the state object is bound to the device), rather than having to repeat this work on every draw call.

To get great performance from state objects, you should create all the objects you are going to need ahead of time, so you drawing code is just setting existing state objects onto the graphics device. C# object initializer syntax comes in handy for this:

    static class MyStateObjects
    {
        public static BlendState BlendSubtract = new BlendState()
        {
            ColorSourceBlend = Blend.SourceAlpha,
            ColorDestinationBlend = Blend.One,
            ColorBlendFunction = BlendFunction.ReverseSubtract,

            AlphaSourceBlend = Blend.SourceAlpha,
            AlphaDestinationBlend = Blend.One,
            AlphaBlendFunction = BlendFunction.ReverseSubtract,
        };
    }

Avoid:

  • Creating new state objects every frame
  • Creating many duplicate state object instances that all contain the same property settings

 

Built-in State Objects

Certain combinations of states are so common that we built them right into the framework, saving you the need of creating custom state objects at all. These built-in states are:

  • BlendState
    • Opaque
    • AlphaBlend
    • Additive
    • NonPremultiplied

  • DepthStencilState
    • None
    • Default
    • DepthRead

  • RasterizerState
    • CullNone
    • CullClockwise
    • CullCounterClockwise

  • SamplerState
    • PointWrap
    • PointClamp
    • LinearWrap
    • LinearClamp
    • AnisotropicWrap
    • AnisotropicClamp

Using these built-in states, the graphics device can be reset to default values with just four lines of code:

    GraphicsDevice.BlendState = BlendState.Opaque;
    GraphicsDevice.DepthStencilState = DepthStencilState.Default;
    GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
    GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

 

BlendState

The BlendState object controls how colors are blended into the framebuffer: source and destination blend mode, BlendFunction, BlendFactor, ColorWriteChannels, and MultiSampleMask.

You may notice that there is no more AlphaBlendEnable property. This was redundant, as you can achieve the same result just by setting source = 1 and destination = 0.

There is also no more SeparateAlphaBlendEnabled property. Again, this was redundant: if you want the color and alpha channels to use the same blend mode, you can just configure their properties with the same values.

We renamed some of the blend control properties from previous versions:

Game Studio 3.1 Game Studio 4.0
BlendFunction ColorBlendFunction
SourceBlend ColorSourceBlend
DestinationBlend ColorDestinationBlend
AlphaBlendOperation AlphaBlendFunction
AlphaSourceBlend AlphaSourceBlend
AlphaDestinationBlend AlphaDestinationBlend

This fixes a common confusion, where people would think the Alpha* properties referred to alpha blending as a whole, not realizing these are only for the alpha channel, while there is another set of properties controlling the blend behavior of the red, green, and blue channels.

We removed the Blend.BothSourceAlpha and Blend.BothInverseSourceAlpha enum values, because these were not especially useful and not universally supported by hardware.

We also removed the alpha test renderstates, mostly because alpha test is no longer supported by DirectX 10 and above. As of Game Studio 4.0, alpha testing can be implemented in the pixel shader, or by using the built-in AlphaTestEffect.

 

DepthStencilState

The DepthStencilState object controls the behavior of the depth buffer and stencil buffer. Its properties are obvious and directly match the old renderstates, so this is a short paragraph!

 

RasterizerState

The RasterizerState object controls how triangles are turned into pixels: CullMode, FillMode, DepthBias, MultiSampleAntiAlias, and ScissorTestEnable.

We removed the FillMode.Point enum value and point size renderstates, because Game Studio 4.0 does not support point sprites.

We also removed the Fog* and Wrap* states, which are not useful (and in some cases not even supported) on modern shader hardware.

 

SamplerState

The SamplerState object controls how data is fetched from textures: AddressU, AddressV, AddressW, Filter, MaxAnisotropy, MaxMipLevel, and MipMapLevelOfDetailBias.

We removed the TextureAddressMode.Border and TextureAddressMode.MirrorOnce enum values, because these are not consistently supported by all our target hardware.

The old MinFilter, MagFilter, and MipFilter properties are collapsed into a single Filter property, which specifies all three options using a single TextureFilter enum value. The old API allowed too many permutations of filter values, many of which were not actually legal. For instance you cannot set MinFilter = None and MipFilter = Anisotropic, but that didn't stop many beginners from trying! With the new API, the TextureFilter enum only contains legal filter settings, so it is no longer possible to get this wrong.

 

High Frequency States

There are three special state values, which are duplicated as properties directly on the GraphicsDevice:

  • BlendFactor
  • MultiSampleMask
  • ReferenceStencil

These states are unique because it is often useful to animate them over a range of continuously varying values, for instance to fade out an object by changing BlendFactor. It would be ridiculous if we had to create hundreds of otherwise identical BlendState objects in order to animate this one value! Instead, we can write:

    GraphicsDevice.BlendState = BlendState.AlphaBlend;
    GraphicsDevice.BlendFactor = CurrentFadeAmount;

Assigning to GraphicsDevice.BlendState overrides the current GraphicsDevice.BlendFactor setting with the BlendFactor value from the specified state object, so order is important. Set the state object first, and the high frequency property second.

  • Ok, how about fixing the XNA 4.0 samples on http://msdn.microsoft.com/en-us/library/bb200104(XNAGameStudio.40).aspx

    and maybe clean up the explaination part of the XNA.  

    The team did a great job on XNA 3.1, except for one of the categories, which never worked.

    The XNA 4.0 Game Studio component is a mess.

  • > "Generally, though, we believe that explicitly setting all state is a better and more robust approach than saving and restoring."

    I agree.

    Any chance that you extend the EffectPass class with properties (public getters and setters) for each of the new state objects? That way could be easy to implement that approach.

  • There is a trick I use with some bit-fu to handle redundant state changes that might benefit XNA in some form. Essentially I use the nibbles (single hexadecimal digits) of uint32 and uint64 to store the states, putting the most important on the left hand side, least important on the right hand side. For say blending this would mean AlphaBlendEnabled is in the left most bit. It first compares the full mask against the one on the device (or in my case the device manager), then starts to move in from left most to right most, exiting out at the first point where the bits are similar enough to not affect visuals. It works well since not one state enum is larger than 15 possible values, however for literals like reference alpha it still requires a 1:1 check.

    If your interested I don't mind sending one of my state block code files to clarify what I mean, after all I would love to see better state management built right into the API itself. Though it does sound like ya'll are taking a step in the right direction, never much cared for D3D9's naive non-grouping of states.

  • I use something similar with GetHashCode(): like the new rationale, I split my state objects by category and create a hash code based on main values. Then I check hashcodes to compare current to next group of states and if different, apply the proper changes (only what changes).

    The only problem here is that I must avoid setting changes on the shader code or I could run into weird situations if I forgot to handle it.

  • Btw Shawn, about my previous suggestion to the EffectPass class, implementing into it a public "Tag" property (of type "Object") may suffice. Thoughts?

  • Nice!. Have been wrapping up all the renderStates changes in one single method that takes 3 enums... Good to know that this has been improved.

    I have a question about AlphaBlending:

    > "You may notice that there is no more AlphaBlendEnable property"

    Does it means is it always on? "No alpha blending" is a hack of alpha blend all of the source and none of the destination. If it is, isn't it a waste of bandwidth to be reading the DestinationBuffer for no alpha blending?, something like reading the depth buffer and no z testing is done.

    Keep the posts coming!

  • Also, about the spriteBatch. Does it still control the state changes when using it?

  • > Ok, how about fixing the XNA 4.0 samples

    Hi surf4fun,

    Game Studio 4.0 is currently only available as a CTP, which means it is not entirely finished yet. CTP releases are provided to give our customers an advance peek at what kind of changes are coming in the next version, and the chance to get a head-start at creating games for new platforms such as Windows Phone.

    If you want a platform that is 100% finished, stable, tested, documented, etc, I would recommend waiting until the final release, and sticking with the previous version (3.1) until that is available. If you choose to use a CTP version, you have to expect a few rough edges!

  • > Any chance that you extend the EffectPass class with properties (public getters and setters) for each of the new state objects?

    That's not part of the design, but if you want to set all state every time you apply a pass, you could do this directly inside the .fx file, or you could override the new Effect.OnApply virtual to implement your own state management system.

    Generally, though, I find that state changes happen at a somewhat lower frequency than EffectPass.Apply (per category of objects rather than per object instance) so I'm not sure it always make sense to tie these together.

  • >> "You may notice that there is no more AlphaBlendEnable property"

    > Does it means is it always on?

    Not at all. There is no need for a separate boolean to indicate on/off state because the source and dest blend factors can indicate the same thing by specifying 1 and 0.

  • An object tag property will save me from overriding the OnApply op. I know I can do it on the shader side itslef but I prefer to control it with my game's code. I could even use the content pipeline to set the value of the tag.

  • Hey Shawn,

    I had two comments:

    If alpha test is no longer supported at a hardware level will devices like the windows phone be able to use techniques such as signed distance fields?

    I didn't see you mention anything about the SRGBTexture property in the sampler state object, is support for that going away?

  • How to set render state in .fx file now? Should I write it use the Dx10 syntax like:

    RasterizerState rsWireframe { FillMode = WireFrame; };

    technique10

    {

       pass p1

       {

       ....

           SetRasterizerState( rsWireframe );

       }

    }

    or the old Dx9 way?

    Vote for adding a getter property to Effect so we can know what render state is been setting on. In fact,i'm never a fan of setting render state in .fx file, too many times people write wrong state in .fx and i can hardly find the problem. It would be very useful for debug.

    BTW,nice post Shawn,looking forward to see something about the new Effect/Shader system:)

  • This makes it so much easier! Not is it only faster to set new states in the GD, but also easier because they are put in renderstate-"domains". That way we don't have to search for specific states.

    It's a shame that FillMode.Point is gone though..I'm sure many people relied on this. But if it's not supported in DX10 and above, then this is probably the better approach.

  • > If alpha test is no longer supported at a hardware level will devices like the windows phone be able to use techniques such as signed distance fields?

    Windows Phone does not support custom shaders in this first release, so the only way to do alpha testing is via the built-in AlphaTestEffect.

    On Windows and Xbox, you can use the HLSL clip() intrinsic to test whatever computed values you like.

    > I didn't see you mention anything about the SRGBTexture property in the sampler state object, is support for that going away?

    What SRGBTexture property? Unless I'm totally confused, that wasn't part of previous Game Studio versions either.

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