State objects in XNA Game Studio 4.0


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.


Comments (30)

  1. surf4fun says:

    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.

  2. Pete says:

    > "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.

  3. default_ex says:

    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.

  4. Pete says:

    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.

  5. Pete says:

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

  6. Alejandro says:

    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!

  7. Alejandro says:

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

  8. ShawnHargreaves says:

    > 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!

  9. ShawnHargreaves says:

    > 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.

  10. ShawnHargreaves says:

    >> "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.

  11. Pete says:

    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.

  12. Padraic says:

    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?

  13. Clayman says:

    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:)

  14. YoYoFreakCJ says:

    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.

  15. ShawnHargreaves says:

    > 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.

  16. ShawnHargreaves says:

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

    Effect state setting still uses the DX9 syntax, unfortunately. We wanted to move effects over to the same state objects as the managed API, but turned out there were too many moving pieces to get that done in time.

  17. Michael Wilson says:

    I was wondering about sampler states actually. In HLSL, all the Windows PCs I have available for test allowed mixed texture addressing e.g. AddressU = Wrap, AddressV = Clamp. On the X360, this sometimes seems to silently fail and function as AddressU = Clamp, AddressV = Clamp instead – but sometimes it works fine! I ended up putting frac() calls into my shader code where necessary, but not in all cases where mixed addressing is used. The inconsistency is a little worrying. Am I running into a subtle compiler or hardware limitation?

  18. ShawnHargreaves says:

    Michael – independent addressing mode per sampler axis is supported on Xbox just the same as on Windows.

    If you have a case where this isn’t working, please file a bug (along with repro app) on the Connect website.

  19. i allways beleave doint set the renderstate or smaplestate in c#

    we have to do it the the shader,

    but sens this is only possible on winndows and xbox360 and think it is ok for the phone

    and in the new api , this is the best solution

    to get red of all the samplerstate and renderstate that are not supportes any more ,course of directx 11 and 10

    nice work..

    Michael Hansen

  20. Jamie says:

    I think this change will help a lot of people. Renderstates is a big “hurting” point for my students. To get what all the different states are, how they affect drawing, etc took a long time  and we had to cover this if we were doing anything but the most basic games. This also took about 60% of the time we had available to teaching the students. The grouping of these into BlendState, DepthStencilState, RasterizerState, SamplerState makes this a lot more approachable. This grouping is generally how I teach it and now the system directly matches that flow!

  21. Dale Freya says:

    In regard to Michael Wilson's comment regarding texture addressing on Xbox, I have had problems with this as well.  I logged a bug on Microsoft Connect about it and it was indicated that the behavior is "by design".  My workaround is as follows:

      static int TextureAddressModeToInt(TextureAddressMode textureAddressMode)

      {

    #if XBOX

         // Hack: On xBox the behaviour is different.

         //       Bug Logged on Microsoft Connect

         //       connect.microsoft.com/…/effect-parameter-textureaddressmode-behaviour-inconsistant-on-different-platforms

         return (int)textureAddressMode – 1;

    #else

         return (int)textureAddressMode;

    #endif

      }

      public void SetAddressModeParameters(Effect effect, TextureAddressMode u, TextureAddressMode v)

      {

         effect.Parameters["DiffuseAddressModeU"].SetValue(TextureAddressModeToInt(u));

         effect.Parameters["DiffuseAddressModeV"].SetValue(TextureAddressModeToInt(v));

      }

  22. ShawnHargreaves says:

    Dale: this is not the same issue that Michael Wilson was talking about.

    Setting state values via integer parameter values that are passed from C# -> FX and then FX -> D3D (rather than directly from C#, or directly from FX code) is borderline unsupported in XNA. You can set them, but the specific meaning of the enum values is not the same on different platforms. The managed enum values do not always match the native D3D ones used by FX on any platform, so you're really on your own when it comes to figuring out what values to use.

    This is rarely a necessary thing to do, btw: any time you are setting these values on the effect from C#, why not change that C# code to just set them directly on the graphics device instead?

  23. Dale Freya says:

    Thanks Shawn.  I was stuck in the mindset of communicating all data to the effect files through parameters.

  24. deadlydog says:

    Hey Shawn, I used to use PrimitiveType.Point to draw pixels to the screen.  How can I accomplish this now that that option is gone?  Thanks.

  25. ShawnHargreaves says:

    Dan: you can find more info about point sprite removal here: blogs.msdn.com/…/point-sprites-in-xna-game-studio-4-0.aspx

    Depending on what you are doing, either SpriteBatch with a 1×1 texture or DrawUserPrimitives with a series of small triangles might be suitable replacements. Check out the 4.0 versions of our Primitives and Particle3D samples to see what we did with a couple of things that previously used point sprites.

  26. RMGK says:

    Got a model of grass using an alpha texture on a set of jumbled planes. Some render the alpha correctly and some ignore the parts of the model behind and just draw the background for alpha. I used to use a hard alpha for this but now i'm stuck. What is the best way to render models with alpha so that it doesn't matter what order I render them in?

  27. ShawnHargreaves says:

    RMGK: perhaps you want to be using alpha test? Thi article might be useful: blogs.msdn.com/…/depth-sorting-alpha-blended-objects.aspx

  28. Soapy Beast says:

    How much do render states cost? can you get away with setting them every time you render a model or will it seriously affect performance?

  29. Hi Shawn!!

    If I write:

     GraphicsDevice.BlendState = BlendState.AlphaBlend;

     object.Draw();

     GraphicsDevice.BlendState = BlendState.AlphaBlend;

     object.Draw();

    I want to know if the second state set provokes a communication between the GPU and the CPU or if it just ignores it because it knows that the state is already set in the GPU.

    Thanks!!

  30. If anyone want to know I made a simple test and this code:

               for (int i = 0; i < 100000; i++)

               {

                   SystemInformation.Device.BlendState = BlendState.Opaque;

                   SystemInformation.Device.BlendState = BlendState.Opaque;

               }

    is much much faster than:

               for (int i = 0; i < 100000; i++)

               {

                   SystemInformation.Device.BlendState = BlendState.Opaque;

                   SystemInformation.Device.BlendState = BlendState.AlphaBlend;

               }

    So, we can easily conclude that if the state is the same not CPU-GPU communication will occur.

    Sorry for ask this silly question before.

    Bye.