Conveniently managing shader variables

If you use DirectX 11, particularly SlimDX, you have the choice to use the Effect Framework to manage your shaders. Then, instead of setting constant buffers you can set shader settings with the help of EffectVariables. Here is a sample for using those variables:

class MyEffect
{
    Effect fx;
    EffectMatrixVariable WorldVariable;

    public MyEffect()
    {
        fx = //...
        WorldVariable = fx.GetVariableByName("World").AsMatrix();
    }

    // ...
    {
        WorldVar.SetMatrix(World);
    }
}

However, setting up these variables can be pretty annoying because mostly the setup is separated from the declaration and you have to scroll back and forth to keep everything in sync. An alternate approach is using a dictionary or similar data structure and accessing the variables directly by their names. However, you will lose IntelliSense support for the specific types or you need to have separate containers for each sub type.

In this post I will show another approach that lets you specify the necessary data right at the fields which makes it more convenient to define and update variables if neccessary.

The .Net framework comes with a handy feature called attributes. Attributes are data units that can be applied to virtually anything. We will create an attribute to add information to our fields. So the resulting variable declaration will look like this:

[ShaderVariable("World")]
EffectMatrixVariable WorldVariable;

With this code we ensure full IntelliSense support, because the concrete type is used instead of EffectVariable. The attribute definition is pretty straight forward and is based on Visual Studio’s attribute template:

[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
sealed class ShaderVariableAttribute : Attribute
{
    public string Name { get; private set; }

    // This is a positional argument
    public ShaderVariableAttribute(string name)
    {
        this.Name = name;
    }
}

The attribute has a “name” property and can be defined for fields.

What remains is setting up the fields. We provide this code within a common EffectBase class:

abstract class EffectBase : IDisposable
{
    static Dictionary<Type, Func<EffectVariable, EffectVariable>> shaderVariableTypes;

    protected Effect fx;

    public EffectBase(Device device, string shaderCode)
    {
        if (shaderVariableTypes == null)
        {
            shaderVariableTypes = new Dictionary<Type, Func<EffectVariable, EffectVariable>>();
            shaderVariableTypes.Add(typeof(EffectScalarVariable), e => e.AsScalar());
            shaderVariableTypes.Add(typeof(EffectVectorVariable), e => e.AsVector());
            shaderVariableTypes.Add(typeof(EffectMatrixVariable), e => e.AsMatrix());
            shaderVariableTypes.Add(typeof(EffectResourceVariable), e => e.AsResource());
        }
        using (var code = ShaderBytecode.Compile(shaderCode, "fx_5_0", ShaderFlags.Debug, EffectFlags.None, null, null))
        {
            fx = new Effect(device, code);
        }
        SetShaderVariables();
    }

This base class declares a dictionary with functions that map the general EffectVariable to a more specific type. We will use this dictionary to determine which function to call on the created variables.

After that the effect is created. In this version the HLSL code is provided. Of course, it is possible to provide the compiled byte code.

The essential part is the call to SetShaderVariables. This method will check every field. If the field has a ShaderVariable-Attribute the according variable is generated and set. Here is the second part of the class:

    /// <summary>
    /// Sets all variables that represent shader variables
    /// </summary>
    private void SetShaderVariables()
    {
        var type = this.GetType();
        var fields = type.GetFields( BindingFlags.NonPublic | BindingFlags.Instance );
        //Get all fields with the shader variable attribute
        foreach (var field in fields)
        {
            var attr = field.GetCustomAttributes(typeof(ShaderVariableAttribute), false);
            if (attr.Length == 1)
            {
                var shaderVariableInfo = (ShaderVariableAttribute)attr[0];
                try
                {
                    var shaderVariable = fx.GetVariableByName(shaderVariableInfo.Name);
                    //Create specific type if possible
                    if (shaderVariableTypes.ContainsKey(field.FieldType))
                        shaderVariable = shaderVariableTypes[field.FieldType](shaderVariable);
                    field.SetValue(this, shaderVariable);
                }
                catch (Exception)
                {
                    Trace.WriteLine("ShaderVariable " + shaderVariableInfo.Name + " of type " + type.Name + " cannot be assigned.");
                }
            }
        }
    }

    public void Dispose()
    {
        fx.Dispose();
    }
}

Now we can rewrite the simple effect class from the beginning of this post to use this base class:

class MyEffect : EffectBase
{
    [ShaderVariable("World")]
    EffectMatrixVariable WorldVariable;

    public MyEffect() : base(...)
    {
    }

    // ...
    {
        WorldVar.SetMatrix(World);
    }
}

This class is more compact and less scattered than the initial one. Topmost, if you have to update the effect class, you have to do it at one spot only – the declaration.

Advertisements

, ,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: