Vertex and Index buffers in xen (Overview)

Index and especially Vertex buffers are one of the most subtly complex aspects of XNA.
Most XNA code is written in C#, a high level language - yet having to manually specify byte offsets, sizes, strides and data formats in multiple places just doesn't feel right.

With all the power of a fully managed language, there must be a better way?
There is, and it all comes down to reflection.


Consider the following example:

It's quite common to want to use your own custom vertices in XNA. Such as the following vertex structure:

struct CustomVertex
{
	public Vector3 Position;
	public Vector3 Normal, Binormal, Tangent;
	public Vector2 Tex0;
	public float Texcoord1;
	public Vector3 Texcoord2;
}

CustomVertex[] customVertexData = new CustomVertex[] 
{
	...
};
	
ushort[] customIndexData = new ushort[]
{
	...
};

XNA requires quite a bit of work to get this going:
  • Data declaration:
VertexBuffer vb;
IndexBuffer ib;
VertexDeclaration declaration;
  • Data setup:
int vec3size = Marshal.SizeOf(typeof(Vector3));
int vec2size = Marshal.SizeOf(typeof(Vector2));
int vec1size = Marshal.SizeOf(typeof(float));
	
VertexElement[] elements = new VertexElement[]
{
	new VertexElement(0,0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position,0),
	new VertexElement(0,(short)(vec3size), VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal,0),
	new VertexElement(0,(short)(vec3size*2), VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Binormal,0),
	new VertexElement(0,(short)(vec3size*3), VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Tangent,0),
	new VertexElement(0,(short)(vec3size*4), VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate,0),
	new VertexElement(0,(short)(vec3size*4+vec2size), VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate,1),
	new VertexElement(0,(short)(vec3size*4+vec2size+vec1size), VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate,2),
};
	
declaration = new VertexDeclaration(device, elements);
	
vb = new VertexBuffer(device, typeof(CustomVertex), customVertexData.Length, BufferUsage.WriteOnly);
vb.SetData(customVertexData);
	
ib = new IndexBuffer(device, customIndexData.Length * 2, BufferUsage.WriteOnly, IndexElementSize.SixteenBits);
ib.SetData(customIndexData);
  • Draw code:
device.Indices = ib;
	
device.VertexDeclaration = declaration;
	
device.Vertices[0].SetSource(vb, 0, Marshal.SizeOf(typeof(CustomVertex)));
	
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, customVertexData.Length, 0, customVertexData.Length / 3);

That's a lot of code! And to make things worse, it's buggy code. There is at least one bug in there that will cause a crash on some hardware, and there are 2 subtle bugs that only occur in very rare situations - these last two are not mistakes, they are bugs where extra code is needed to compensate for unexpected situations. And that's assuming I wrote it correctly!
Consider the effort that is required to change the structure layout, and how easily it could be done incorrectly.

So, how does this work in xen?

  • Data declaration:
IVertices vb;
IIndices ib;
  • Data setup:
vb = new Vertices<CustomVertex>(customVertexData);
	
ib = new Indices<ushort>(customIndexData);
  • Draw code:
vb.Draw(state, ib, PrimitiveType.TriangleList);


How does this work?
Reflection is the key.

When an unrecognised vertex structure is used, that structure is reflected. A single VertexDeclaration is created for that structure type.
The runtime knows what types the graphics card can read, and it knows the types the structure is using; If you put an integer in the structure, chances are you will hear about it.

By using reflection, it's easy to calculate things like offsets, vertex stride, etc. This extends to safe multi-stream rendering too ('VerticesGroup').

Reading the names of each field gives a hint as to the intended use. "Position" is pretty obvious. "Tex0" is a bit more complex, but still clear.
If a field named "TweakFactor" was encountered, an exception would be thrown - (Attributes can be used to clear up cases like this).

Xen handles the grunt work for you.

Read about state management in xen

Home Current Release Documentation Screenshots

Last edited Apr 8, 2010 at 11:55 PM by StatusUnknown, version 28

Comments

No comments yet.