Reflected Vertex Declarations

Something I have found needlessly difficult while developing graphics abstractions in C++ was offering a simple, elegant and effortless way to declare new vertex structures, and ensure all required attributes are bound automatically.

One way I found around this problem is to not have a vertex structure; instead require the developer to fill a separate array for each parameter they wanted in the vertex declaration. The system could then determine which arrays were filled, assert that they are all the same size, create a vertex declaration, and interleave the data. The benefits of this system is that it can (although didn’t in my trivial implementation) support any combination of fields within the vertex declaration; the obvious drawback is that it is up to the programmer to ensure each array is the correct size, and often requires splitting already nicely-interleaved vertices to separate arrays, just to be re-interleaved later in the pipeline.

Another option only really possible on desktop systems with ample memory and bandwidth, is to just ignorantly declare a mammoth vertex structure containing all the typical required fields, something like the following:

Vector3 Position
Vector3 Normal;
Color Color;
Vector2 TexCoord;
int BoneIndices[4];
float BoneWeights[4];

Then leave unrequired fields as their default. This approach is very easy to implement, but of course has pretty bad memory repercussions, always taking 17*4 bytes for every vertex. It also will not allow the developer to create custom fields, or rename the existing fields, resulting in, for example, having to force the tangent vector into the first 3 floats of BoneWeights.

Now that I have moved to a managed language, with support for reflecting data fields within a type and allowing attributes on fields, a lot of the work required for generating vertex declarations can be automated. The following is an example of a typical vertex structure:

[StructLayout(LayoutKind.Sequential)]
struct VertexPositionColor2 {
    public Vector3 Position;
    public Color Color;
    [VertexAttribute(VertexElementFormat.Fixed, sizeof(byte), 4)]
    public uint Color2;
}

This structure gets reflected when used in a VertexBuffer<T>, and the fields in the struct are reflected to determine how the field should be serialized/bound, and a VertexDeclaration object is created caching the fields and their descriptions for the next time this structure is used. Vector3 and Color have default attributes, which tells the VertexDeclaration, when it reflects, how the data is laid out in the type, and how it should be bound to a shader. Color2 shows an example of overriding the default with its own format/layout.

Each field has the following metadata associated with it to aid in binding:
Name: Used as the semantic name when binding to a shader
Format: Float, Integer, or Fixed (fixed referring to normalized types with fixed precision)
Size: Size in bytes of a component within this field (ie. sizoef(float), sizeof(byte))
Offset: The offset in bytes from the start of the vertex structure, to where this field begins
Count: The number of components within this field

One issue remains with this declaration; signed bytes used for normals need to be normalized, but retain their sign. The current meta-data does not yet store if a Fixed type is signed or unsigned, and is assumed to be unsigned. Two possible fixes for this is to add a Signed boolean, or extend the Format to include FixedSigned. I am waiting to see how this system is used to determine which would be the preferable option (the former is more flexible; allowing unsigned Integer and Float types (where possible), the latter would minimise the fields in VertexAttributes to 3).

The implementation of this system is mostly trivial in C#, but one part which was less intuitive was getting C# to serialize to a byte array. To do so, you’ll need to make use of the Marshal class, specifically AllocHGlobal, StructureToPtr, Copy, and FreeHGlobal. The vertex declarations also unfortunately require an extra StructLayout attribute be applied to them. The procedure is:
– Allocate some unmanaged memory of sufficient size
– Loop through all vertices
– Copy the vertex data to this unmanaged memory (StructureToPtr)
– Copy the unmanaged memory to a byte array (Copy)
– Free the unmanaged memory

Advertisements

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