Managing input layouts

In my last post I showed how to use .Net attributes to define shader variables. This post will be about the management of input layouts.

Input layouts define how the graphics card should interpret vertex data and how to map fields to the shader’s variables. Usually you send vertices in form of arrays of structs to the GPU. An input layout is needed for each struct and for each shader pass.

A singleton class is used to manage all layouts for all shaders. It has the following methods:

class InputLayoutManager : IDisposable
{
    /// <summary>
    /// Returns the input layout for the provided vertex description and the effect pass. If there is no layout yet, a new one is created.
    /// </summary>
    public InputLayout GetInputLayout(Device D3DDevice, InputElement[] Description, EffectPass Pass) { ... }

    /// <summary>
    /// Returns the vertex description for a struct.
    /// </summary>
    public InputElement[] GetVertexDescription(Type t) { ... }

Instead of hardcoding the vertex description, we, again, utilize attributes. An attribute is applied to each field of a struct that should appear in the input layout. Here is an example vertex structure:

struct VertexPositionNormalTexture
{
    [VertexElement("POSITION", 0, SlimDX.DXGI.Format.R32G32B32_Float)]
    public Vector3 Position; 

    [VertexElement("NORMAL", 0, SlimDX.DXGI.Format.R32G32B32_Float)]
    public Vector3 Normal;

    [VertexElement("TEXCOORD", 0, SlimDX.DXGI.Format.R32G32_Float)]
    public Vector2 TexCoord;
}

We added extra information to the fields using the custom VertexElement attribute. This attribute is defined as follows:

[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
sealed class VertexElementAttribute : Attribute
{
    public string Semantic { get; private set; }
    public int SemanticIndex { get; private set; }
    public SlimDX.DXGI.Format Format { get; private set; }

    public VertexElementAttribute(string semantic, int semanticIndex, SlimDX.DXGI.Format format)
    {
        Semantic = semantic;
        SemanticIndex = semanticIndex;
        Format = format;
    }
}

At this stage we have all information necessary to create the input layouts. First, we create the vertex description for the type by iterating all fields. The fields’ positions will be calculated by the algorithm. Additionally, we use a simple cache for the descriptions, so we do not need to calculate the decription again and again.

Dictionary<Type, InputElement[]> vertexDescriptionCache = new Dictionary<Type,InputElement[]>();
public InputElement[] GetVertexDescription(Type t)
{
    if(vertexDescriptionCache.ContainsKey(t))
        return vertexDescriptionCache[t];

    System.Diagnostics.Debug.WriteLine("Creating new vertex description for type " + t.Name);
    List<InputElement> elements = new List<InputElement>();
    var fields = t.GetFields();
    int currentPosition = 0;
    foreach (var f in fields)
    {
        var attr = f.GetCustomAttributes(typeof(VertexElementAttribute), false);
        if (attr.Length == 1)
        {
            var i = (VertexElementAttribute)attr[0];
            elements.Add(new InputElement(i.Semantic, i.SemanticIndex, i.Format, currentPosition, 0));
        }
        currentPosition += Marshal.SizeOf(f.FieldType);
    }
    var result = elements.ToArray();
    vertexDescriptionCache.Add(t, result);
    return result;
}

Creating the input layouts is pretty easy once we have the description. Again, we use a two-level cache to save resources:

Dictionary<InputElement[], Dictionary<EffectPass, InputLayout>> Layouts
            = new Dictionary<InputElement[], Dictionary<EffectPass, InputLayout>>();
public InputLayout GetInputLayout(Device D3DDevice, InputElement[] Description, EffectPass Pass)
{
    if (!Layouts.ContainsKey(Description))
        Layouts.Add(Description, new Dictionary<EffectPass, InputLayout>());
    var SecondDictionary = Layouts[Description];
    if (!SecondDictionary.ContainsKey(Pass))
    {
        System.Diagnostics.Debug.Print("Created new Input Layout");
        SecondDictionary.Add(Pass, new InputLayout(D3DDevice, Pass.Description.Signature, Description));
    }
    return SecondDictionary[Pass];
}

The usage of this class is pretty simple. Whenever you need an input layout, query the vertex description of the according type and query the layout for the description:

var layoutDescription = InputLayoutManager.Instance.GetVertexDescription(typeof(VertexPositionNormalTexture));
var inputLayout = InputLayoutManager.Instance.GetInputLayout(D3DDevice, layoutDescription, Pass);
D3DContext.InputAssembler.InputLayout = inputLayout;

Here is the complete sourcecode of the input layout manager. Be aware that the singleton implementation is not thread safe.

class InputLayoutManager : IDisposable
{
    static InputLayoutManager instance;
    public static InputLayoutManager Instance
    {
        get
        {
            if (instance == null)
                instance = new InputLayoutManager();
            return instance;
        }
    }

    Dictionary<InputElement[], Dictionary<EffectPass, InputLayout>> Layouts
        = new Dictionary<InputElement[], Dictionary<EffectPass, InputLayout>>();
    Dictionary<Type, InputElement[]> vertexDescriptionCache = new Dictionary<Type,InputElement[]>();

    private InputLayoutManager()
    {
    }

    /// <summary>
    /// Returns the input layout for the provided vertex description and the effect pass. If there is no layout yet, a new one is created.
    /// </summary>
    public InputLayout GetInputLayout(Device D3DDevice, InputElement[] Description, EffectPass Pass)
    {
        if (!Layouts.ContainsKey(Description))
            Layouts.Add(Description, new Dictionary<EffectPass, InputLayout>());
        var SecondDictionary = Layouts[Description];
        if (!SecondDictionary.ContainsKey(Pass))
        {
            System.Diagnostics.Debug.Print("Created new Input Layout");
            SecondDictionary.Add(Pass, new InputLayout(D3DDevice, Pass.Description.Signature, Description));
        }
        return SecondDictionary[Pass];
    }

    /// <summary>
    /// Returns the vertex description for a struct.
    /// </summary>
    public InputElement[] GetVertexDescription(Type t)
    {
        if(vertexDescriptionCache.ContainsKey(t))
            return vertexDescriptionCache[t];

        System.Diagnostics.Debug.WriteLine("Creating new vertex description for type " + t.Name);
        List<InputElement> elements = new List<InputElement>();
        var fields = t.GetFields();
        int currentPosition = 0;
        foreach (var f in fields)
        {
            var attr = f.GetCustomAttributes(typeof(VertexElementAttribute), false);
            if (attr.Length == 1)
            {
                var i = (VertexElementAttribute)attr[0];
                elements.Add(new InputElement(i.Semantic, i.SemanticIndex, i.Format, currentPosition, 0));
            }
            currentPosition += Marshal.SizeOf(f.FieldType);
        }
        var result = elements.ToArray();
        vertexDescriptionCache.Add(t, result);
        return result;
    }

    public void Dispose()
    {
        foreach (var d in Layouts)
        {
            foreach (var l in d.Value)
            {
                l.Value.Dispose();
            }
        }
        Layouts.Clear();
    }
}
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: