Standardized Representation RenderStates

February 9, 2010 by admin
Filed under: Xbox360 News 

I came across a great article by Shawn Hargreaves regarding using bit-fields to set RenderStates: Shawn Hargreaves Blog: Bitfield renderstates
 
After reading it, something peaked my interest: "This makes it impossible to come up with a single standardized
representation, so this bitfield technique is not suitable for
generalized engines or frameworks."

This is true, but I decided to attempt this.  Whether I fail or not is besides the point, it's the knowledge that comes between extremes. The purpose of this class is to give a general technique across all frameworks.

Since there are more than 60 RenderStates, an integer, which represents 32 bits, isn't enough. So a long, 64 bits, would be more desirable. Obviously I could have used a BigInteger or equivalent type.
Bit-fields will be using bit-wise operations for checks and modifications, so we represent the RenderStates as a enumeration using the flag atrribute.

[Flags] 
public enum RSVar : ulong 
    AlphaBlendEnable = 1, 
    AlphaBlendOperation = AlphaBlendEnable << 1, 
    AlphaDestinationBlend = AlphaBlendOperation << 1, 
    … 

We'll need a previous state bit-field that will store changes made. When the user calls the Set functionality, changes will be checked and then we run through each bit position and determine which one has been activated:

// See what states have changed. 
ulong ulChanges = (ulong)_Variables ^ m_prevState; 
 
… 
 
// Run through and search which bit has been activated. 
while ((ulChanges != 0) && nBitPos < (sizeof(ulong) * 8)) 
    // Determine if the bit is on. 
    ulong nShiftBitPos = (ulong)(1 << nBitPos); 
    if (IsBitOn((ulong)_Variables, nShiftBitPos)) 
    { 
        // TODO: Set RenderStates Here. 
        … 
    } 
    // Increment to the next bit position. 
    ++nBitPos; 
    // Clear this bit to shorten the loop. 
    ulChanges ^= (nShiftBitPos & (ulong)ulChanges); 
 
// Store the previous state. 
m_prevState = (ulong)_Variables; 

Once we find a bit that has been requested, we set the appropriate RenderStates, then increment to the next bit position. We toggle the bit from the bit-field to shorten the loop, otherwise we will always loop sizeof(ulong) * 8 bits = 64 times. Finally, we store the previous state for next time.

At first glance, this looks adequate but there are some problems.

1) We are hard-coding the RenderStates within the loop. We'll need to take in some parameters to make it more flexible.

Using a Variable Argument List or Variadic Function will give the flexibility but a minor problem.  The function looks like this:

public void Set(RenderState _RenderState, 
                RSVar _Variables, 
                params object[] _Values) 
     … 

The first parameter is the current RenderState that we will be setting. The second is the list of enumerations representing a bit-field. And third is the list of values to set.  The problem is, since the list of enumerations is a bitfield, our functionality of checking each bit position from least to most significant needs to be respected. For example:

RSHelper.Set(GraphicsDevice.RenderState, 
             RSHelper.RSVar.AlphaBlendEnable | 
             RSHelper.RSVar.CullMode | 
             RSHelper.RSVar.DestinationBlend, 
             new object[] { true,  
                            CullMode.CullCounterClockwiseFace, 
                            Blend.One, }); 

Is the correct way.  AlphablendEnable will be set to true, CullMode will be set to CullMode.CullCounterClockwiseFace and DestinationBlend is set to Blend.One. If the third parameter were reversed or mixed, it would cause a crash.

Unfortunately there is no easy solution. So we just mention alphabetically for the second which will make it easier to create a sequence for the third parameter.

Other problems:
2) If the function is called again but different parameters are used.
3) If the function is called again but same parameters are used and modified.

We will need to add more to our current functionality to solve these:

… 
// Run through the list of renderstates to check if values have changed. 
while (m_RSList.Count > 0 && UpdateList.Count < _Values.Length && NewList.Count < _Values.Length &&  
       nBitPos < (sizeof(ulong) * 8)) 
    // Shift to the next bit position and check if the bit is on. 
    ulong nShiftBitPos = (ulong)((ulong)1 << nBitPos); 
    if (IsBitOn((ulong)_Variables, nShiftBitPos)) 
    { 
        // Run through the list of variables. 
        foreach (RSData data in m_RSList) 
        { 
            // Run through the list of values. 
            foreach (object value in _Values) 
            { 
                // Check if the render state has already been activated. 
                if (data.ShiftData == nShiftBitPos) 
                { 
                    // Check the data type and value if they have changed. 
                    if (data.ObjectType.GetType() == value.GetType() && 
                        data.ObjectType.ToString() != value.ToString()) 
                        UpdateList.Add(nShiftBitPos); 
 
                    // Break out of the loop. 
                    bBreak = true
                    bNoChange = false
                    break
                } 
            } 
            // Break out of the loop. 
            if (bBreak) 
                break
        } 
        // There is a new render state. 
        if (!bBreak) 
            NewList.Add(new RSData(nIndex, nShiftBitPos, _Values[nIndex])); 
        // Reset break flag and increment the next index. 
        bBreak = false
        ++nIndex; 
    } 
 
    // Increment to next bit position. 
    ++nBitPos; 
 
// Determine if there are new renderstates. 
if (NewList.Count > 0) 
    // TODO: Set RenderStates Here. 
    … 
// 
// Determine if there has been a change to an already activated renderstate. 
else if (UpdateList.Count > 0) 
    // TODO: Set RenderStates Here. 
    … 
// Check if there have been changes. 
else if (bNoChange) 
    …

We will need a list to store the current RenderStates being used and two temporary lists for Updated and New RenderStates that need to be modified and set.

The loop at the beginning will determine for us whether any RenderStates need to be updated or new ones need to be added. Then the if checks will set up those RenderStates respectively.

4) What if the user decides to reset all the RenderStates.

Since we have the list of current RenderStates, we just simply set back the ones being used:

// Check if there were any changes. 
if (m_RSList.Count > 0) 
    // Run through the list of changes. 
    foreach (RSData data in m_RSList) 
    { 
        // Determine the index reference. 
        switch ((RSVar)data.Index) 
        { 
            // TODO: Set RenderStates To Defaults. 
            … 
 
            default
                break
        } 
    } 
 
    // Clear the list and trim excess. 
    m_RSList.Clear(); 
    m_RSList.TrimExcess(); 
    m_prevState = 0; 

The RSData is a helper structure that contains information about the current RenderState such as the Index, ObjectType and ShiftBitData.

You will call this after you render the scene and want to reset things to default:

RSHelper.Reset(GraphicsDevice.RenderState); 

Now we have a working class that sets/resets RenderStates easily for us. How is this more efficient then just setting them yourselves? Well lets say you have multiple classes, each setting a RenderState, as the number of classes grow, it will be hard to keep track of which ones are already activated and finally which ones need to be reset.  With this implementation, it automatically figures these out.

Of course, this class is pointless for more specific game types or simple applications.

Hopefully this was helpful for some, it was definitely an exercise for the brain. There are room for optimizations and improvements, but it's a start.

Here is the link for the bit-field version: RSHelper
 Here is the link for the non-bit-field version: RSHelper
 
Resources: Masks and flags using bit fields in .NET

Comments

Feel free to leave a comment...
and oh, if you want a pic to show with your comment, go get a gravatar!