Automatic Vertex Declarations

Some time ago, Michael Popolski contributed an article on gamedev.net in which he presented a system that could automatically determine the vertex declaration of any structure, thus eliminating the need for keeping the vertex structure and your vertex element list in sync manually: Designing a Flexible Vertex Element System for XNA Using Attributes

The Nuclex Framework contains a helper class that is based on his idea, but has even lower performance overhead, works on the XBox 360 and can be used with multiple vertex streams if you so wish.

Manual Vertex Description

Normally, your vertex structure would look like this:

/// <summary>Vertex used to render models animated with bone weights</summary>
[StructLayout(LayoutKind.Sequential)]
private struct ModelVertex {

  /// <summary>Describes the elements that make up this vertex</summary>
  public static readonly VertexElement[] VertexElements = new VertexElement[] {
    new VertexElement(
      0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default,
      VertexElementUsage.Position, 0
    ),
    new VertexElement(
      0, 12, VertexElementFormat.Vector3, VertexElementMethod.Default,
      VertexElementUsage.Normal, 0
    ),
    new VertexElement(
      0, 24, VertexElementFormat.Vector2, VertexElementMethod.Default,
      VertexElementUsage.TextureCoordinate, 0
    ),
    new VertexElement(
      0, 32, VertexElementFormat.Single, VertexElementMethod.Default,
      VertexElementUsage.BlendWeight, 0
    ),
    new VertexElement(
      0, 36, VertexElementFormat.Single, VertexElementMethod.Default,
      VertexElementUsage.BlendWeight, 1
    )
  };

  /// <summary>Number of bytes a single vertex uses</summary>
  public const int SizeInBytes = 40;

  /// <summary>Position of the vertex relative to the model's center</summary>
  public Vector3 Position;

  /// <summary>Normal vector at the vertex to use for lighting</summary>
  public Vector3 Normal;

  /// <summary>Texture coordinates at this vertex</summary>
  public Vector2 TextureCoordinates;

  /// <summary>Blend weight for the first bone influencing the vertex</summary>
  public float BlendWeight1;

  /// <summary>Blend weight for the second bone influencing the vertex</summary>
  public float BlendWeight2;

}

Not only is this a pain to maintain, it's dangerous because just switching two fields in the structure or doing some other minor change will cause strange behavior, such as erratic animation, strange lighting or corrupted textures. And it violates the DRY principle: Don't Repeat Yourself.

Automatic Vertex Description

The VertexDeclarationHelper greatly simplifies things and eliminates the risk of the vertex element list and the vertex structure going out-of-sync. You just add the [VertexElement] attribute to your vertex fields to describe their contents (but not their offsets and other data that is already implied by the structure). The VertexDeclarationHelper then automatically creates the VertexElements list from your structure. This also keeps the declared usage of a field close together with the field itself, further increasing readability.

The same vertex structure as in the above example (literally equivalent, no other code needs to be changed) using the VertexDeclarationHelper:

/// <summary>Vertex used to render models animated with bone weights</summary>
[StructLayout(LayoutKind.Sequential)]
private struct ModelVertex {

  /// <summary>Describes the elements that make up this vertex</summary>
  public static readonly VertexElement[] VertexElements =
    VertexDeclarationHelper.BuildElementList<ModelVertex>();

  /// <summary>Number of bytes a single vertex uses</summary>
  public static readonly int SizeInBytes =
    VertexDeclarationHelper.GetStride<ModelVertex>();

  /// <summary>Position of the vertex relative to the model's center</summary>
  [VertexElement(VertexElementUsage.Position)]
  public Vector3 Position;

  /// <summary>Normal vector at the vertex to use for lighting</summary>
  [VertexElement(VertexElementUsage.Normal)]
  public Vector3 Normal;

  /// <summary>Texture coordinates at this vertex</summary>
  [VertexElement(VertexElementUsage.TextureCoordinate)]
  public Vector2 TextureCoordinates;

  /// <summary>Blend weight for the first bone influencing the vertex</summary>
  [VertexElement(VertexElementUsage.BlendWeight)]
  public float BlendWeight1;

  /// <summary>Blend weight for the second bone influencing the vertex</summary>
  [VertexElement(VertexElementUsage.BlendWeight, UsageIndex=1)]
  public float BlendWeight2;

}

The cost of auto-generating your vertex descriptions is very small and paid when the application starts up (initializing the static fields of non-nested classes, another topic in .NET worthy of reading up on :D).

Last edited Sep 19, 2009 at 10:40 AM by Cygon, version 5

Comments

Cygon Mar 25, 2010 at 11:13 AM 
That is an option, but it requires an external application to process the HLSL file, which adds complexity to your build system. Anyone using it would also have to learn your declaration language and there would be no IDE support (eg. auto-completion).

Apart from that, I think yours really is the way to go. Especially if one is pragmatic about DRY. I see that you have really worked this out - even the possibility of feeding a shader from multiple vertex streams.

Now I have something to think about. Whether I prefer a code-generator-free build or a zero redundancy vertex structure definition. If I decide for the latter and don't come up with another solution, expect this page and related source code to be revised soon :)

skytigercube Mar 4, 2010 at 2:23 PM 
You say "don't repeat yourself" but your HLSL vertex structures also have to match - why not use a decorated version of your HLSL structure:

/* GenerateVertexStructure namespace GameSpace class GameBase

// struct VertexPSO Stream0
float2 Position : POSITION0; // Type(HalfVector4) // ndc position to write this instance data
float4 posTrunc : TEXCOORD1; // Type(HalfVector4) // trunc(pos) world space position of instance
float4 posFrac : TEXCOORD2; // Type(HalfVector4) // frac(pos) world space position of instance
float4 orientation : TEXCOORD3; // Type(HalfVector4) // orientation quaternion

*/

so all I design the HLSL vertex structure to meet the shader's needs - then decorate and use the same syntax to auto-generate the structure
which are inserted into any partial class I pick - reusable structures are inserted into library classes

plus this way you type much less
the best way to write code is a straight line between two points - your approach only goes half way