Vertex data in XNA Game Studio 4.0


In previous XNA versions, a VertexBuffer was just a loosely typed bag of bytes. A separate VertexDeclaration object specified how to interpret these bytes.

As of Game Studio 4.0, every VertexBuffer now has an associated VertexDeclaration, which is specified at creation time. VertexBuffer thus becomes a strongly typed container, providing all the information necessary to interpret its contents.

 

Why?

Consider this typical 3.1 rendering code:

    // Create
    VertexDeclaration decl = new VertexDeclaration(device, VertexPositionColor.VertexElements);

    int size = VertexPositionColor.SizeInBytes * vertices.Length;
    VertexBuffer vb = new VertexBuffer(device, size, BufferUsage.None);

    vb.SetData(...);

    // Draw
    device.VertexDeclaration = decl;
    device.Vertices[0].SetSource(vb, 0, VertexPositionColor.SizeInBytes);

    device.DrawPrimitives(...);

I highlighted the problem in red. See how many times I had to repeat what vertex format I am dealing with? The more places this is repeated, the more chances to make a mistake. I cannot be the only one who ever forget to set the right VertexDeclaration, or specified the wrong stride when calling Vertices.SetSource!

This loosely typed design also presented challenges for our runtime implementation. Because a VertexBuffer did not specify the format of its contents, the framework was unable to tweak this data for different platforms or hardware (at least not until the actual draw call, at which point it is too late for efficient fixups).

For example, although we generally prefer to perform Xbox endian conversion as late as possible in the Content Pipeline build process, our ContentTypeWriter<VertexBufferContent> did not have enough information to do this correctly. Instead, we had to specify the TargetPlatform when calling VertexContent.CreateVertexBuffer, because the necessary type information was discarded from that point on.

As we add new platforms, we need more flexibility to adjust for the needs of each, and this requires a deeper understanding of the data we are dealing with. Strongly typed vertex buffers simplify the API, which reduces the chance of error, at the same time as increasing framework implementation flexibility. A classic win-win.

 

VertexDeclaration changes

The VertexElement and VertexDeclaration types still exist, but are used somewhat differently:

  • VertexDeclaration constructor no longer requires a GraphicsDevice
  • Vertex stride is now specified as part of the VertexDeclaration
  • Removed VertexElement.Stream property (see below)
  • Removed VertexElementMethod enum (because no hardware actually supported it)

And the important one:

  • No more GraphicsDevice.VertexDeclaration property

This is no longer necessary, because whenever you set a VertexBuffer, the device can automatically look up its associated declaration. I find it great fun porting from Game Studio 3.1 to 4.0, because I can simply delete everywhere that used to set GraphicsDevice.VertexDeclaration, and everything still magically "just works" β„’.

 

VertexBuffer changes

VertexBuffer has different constructor arguments:

  • The vertex format can be specified by passing either a Type or a VertexDeclaration
  • The buffer size is now specified in vertices rather than bytes

Also:

  • New VertexBuffer.VertexDeclaration property
  • GraphicsDevice.SetVertexBuffer replaces GraphicsDevice.Vertices[n].SetSource

With 4.0, the code example from the top of this article becomes:

    // Create
    VertexBuffer vb = new VertexBuffer(device, typeof(VertexPositionColor), vertices.Length, BufferUsage.None);

    vb.SetData(...);

    // Draw
    device.SetVertexBuffer(vb);

    device.DrawPrimitives(...);

Note how the vertex format is only specified in one place. Less code, and less potential for error.

These changes mean it is no longer possible to store vertices of different formats at different offsets within a single vertex buffer. This was once a common pattern in graphics programming, because changing vertex buffer used to be incredibly expensive (back in DirectX 7). Developers learned to optimize by merging many models into a single giant vertex buffer, and some have been doing that ever since. But changing vertex buffer is cheap these days, so this is no longer a sensible optimization.

 

IVertexType interface

In the previous code example, notice how I pass typeof(VertexPositionColor) when creating my VertexBuffer? How can the VertexBuffer constructor get from this type to its associated VertexDeclaration?

We added a new interface:

    public interface IVertexType
    {
        VertexDeclaration VertexDeclaration { get; }
    }

This is implemented by all the built-in vertex structures, so anyone who has one of these types can look up its associated VertexDeclaration.

If you try to create a VertexBuffer using a type that does not implement the IVertexType interface, you will get an exception. This is a common situation when loading model data, as you may wish to support arbitrary vertex layouts that do not match any existing .NET types, so your vertex data is loaded as a simple byte array. In such cases you should use the alternative VertexBuffer constructor overload which takes a VertexDeclaration instead of a Type.

 

Custom vertex structures

When creating custom vertex structures, it is a good idea to implement the IVertexType interface, so your custom types can be used the same way as the built-in ones. A simple example:

    struct MyVertexThatHasNothingButPosition : IVertexType
    {
        public Vector3 Position;

        public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
        (
            new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
        );

        VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }
    };

 

DrawUserPrimitives changes

DrawUserPrimitives<T> and DrawUserIndexedPrimitives<T> get their vertex data from a managed array, as opposed to a vertex buffer object. With no VertexBuffer, how can they find the necessary VertexDeclaration?

  • If T implements the IVertexType interface, the declaration can be queried from that.

  • If T does not implement IVertexType, you can use new overloads of DrawUserPrimitives and DrawUserIndexedPrimitives, explicitly passing the VertexDeclaration as the last draw call argument.

 

Multiple vertex streams

Prior to version 4.0, a single VertexDeclaration could combine inputs from many vertex streams, using the VertexElement.Stream property to specify the source stream for each element. Multiple vertex buffers were then set onto the device like so:

    device.Vertices[0].SetSource(vb1, 0, stride1);
    device.Vertices[1].SetSource(vb2, 0, stride2);

As of 4.0, each VertexDeclaration now describes the format of a single VertexBuffer, so if you are using multiple buffers, there are also multiple declarations. This made the VertexElement.Stream property unnecessary, so it was removed. Multiple buffers are set onto the device with a single atomic call, similar to (and for the same reasons as) the new SetRenderTargets API:

    device.SetVertexBuffers(vb1, vb2);

 

Content Pipeline changes

We made some minor Content Pipeline tweaks to match the new vertex buffer API:

  • Added a new VertexDeclarationContent type
  • VertexBufferContent now has an associated VertexDeclarationContent
  • VertexBufferContent.Write and VertexContent.CreateVertexBuffer no longer need a TargetPlatform parameter
  • Moved the VertexBuffer and IndexBuffer properties from ModelMesh to ModelMeshPart
  • Removed the ModelMeshPart VertexDeclaration and VertexStride properties

Comments (35)

  1. TomSpilman says:

    > device.SetVertexBuffers(vb1, vb2);

    So under the hood how is that working?  Is it maintaining an internal cache of VertexDeclarations built from the set VBs?  Any interesting technique used to make the lookup of the real declaration fast?

  2. ShawnHargreaves says:

    > So under the hood how is that working?

    It uses a crazy cross-linked tree structure to do the mapping in linear time (proportional to how many VB’s are set simultaneously). Details not particularly interesting to go into here, but yeah, it obviously requires some sensible data structure choices to make such a thing fast.

  3. mike says:

    Thank you! You are definitely not the only one who forget to set the right VertexDeclaration – I’ve wasted hours tracking that mistake down.

  4. jon watte says:

    So how do I cast the declaration of a buffer?

    Specifically, when I select from a large set of morph targets that all have "position" data, and I can’t know the usageindex up front?

  5. Rim says:

    I’m getting old and grumpy I guess, but I’m still not sure I like this general direction of abstracting stuff further and further from D3D. Abstraction is all neat and dandy, but I can’t help but feel like we’re losing flexibility and transparency with each iteration.

    Especially abstracting away vertex declarations seems like a bad idea. I agree that it’s not something you want to deal with if it can be avoided, but for some interesting usecases (like indeed morph targets) I really think it should remain exposed somehow.

    If you promise to fix my latest issue on Connect though (552653, which happens to deal with an unforeseen usecase), I’ll promise to stop being grumpy πŸ™‚

  6. David Black says:

    Well the way D3D does things is itself an abstraction… It is just the XNA team are trying to find better abstractions. For the most part they seem to be suceeding.

    I would however also be interested in more info about jons use case. Multiple blended morph targets does seem like an interesting example.

  7. ShawnHargreaves says:

    > when I select from a large set of morph targets that all have "position" data, and I can’t know the usageindex up front?

    Do you mean an approach where all the morph targets are embedded in a single giant VB, then you just change the decl to select one or more from a larger set of possible positions?

    That kind of type-punning is no longer possible in GS4.

    The best way to do morph targets would be to use multiple streams, with a separate smaller VB for each position set. This approach has a couple of other benefits:

    – More compact data format makes more efficient use of GPU vertex fetch caches (which is rarely a bottleneck, so in practice the perf impact is usually somewhere between zero and minimal, but a more cache efficient data format can’t hurt and might help)

    – Allows an arbitrary number of morph targets. Many GPUs have a max vertex stride of 255 bytes, so the all-in-one-giant-VB approach imposes a max of somewhere between 10 and 15 position sets.

  8. ShawnHargreaves says:

    > I’m still not sure I like this general direction of abstracting stuff further and further from D3D

    But which "D3D"? There is D3D9 on Windows, and D3D10+ on Windows, and D3D on Xbox, and D3D on Windows Phone. All of these are significantly different, in ways that change how application code must be written.

    In the past, XNA has generally followed the Windows D3D9 model, which caused much complexity and performance overhead when we tried to map the resulting API to other D3D variants (plus, D3D9 isn’t exactly the future, so if we had to pick just one to optimize for, that’s not really the most logical choice). A major goal of the 4.0 changes is to find abstractions that do a better job of fitting over all these various back-end implementation layers, so no one platform is left having to jump through hoops trying to conform to an API that was optimized for something different.

  9. default_ex says:

    I’m wondering about setting data into a vertex buffer. Will I still be able to pass an array of floats into the vertex buffer? It’d suck to have to come up with a serialization mechanic that transform an array of floats into a typed array that conforms with the vertex elements.

  10. ShawnHargreaves says:

    > Will I still be able to pass an array of floats into the vertex buffer?

    Absolutely. This is crucially important, for instance when loading model data that could use many different vertex formats, so you want to just read and set the bits as a byte array.

    The important thing for the framework is that when you call SetData, passing a byte[] or float[], we know what the actual format of this data is, so if we need to do things like endian swapping or fixing up formats that aren’t supported on a particular graphics card, we have enough info to do that correctly.

  11. David Black says:

    I think what is unclear with the morph targets example is matching the usage indices specified in the vertex declaration to shader inputs.

    eg a shader might have as input:

    posA: POSITION0

    posB: POSITION1

    posC: POSITION2

    Then we have some morph target position streams, but we want to assign these vertex buffers to any of posA, posB or posC depending on animation settings.

    Is this possible, or am I misunderstanding something here?

  12. ShawnHargreaves says:

    > Is this possible, or am I misunderstanding something here?

    This is not possible in the CTP release, but the plan is to enable this before RTM. Rather than just giving an error if multiple streams try to bind the same usage index, we’ll just offset the usage of subsequent streams so you can bind whatever Position0 inputs you like (in any order), and the shader sees this data as Position0, Position1, etc.

  13. nice api,

    Multiple vertex streams

    Shawn is not possible to have defferent worldmatrix for each vertex streams

    aka

    transform this postion to the first vertex streams

    and

    transform this postion to the second vertex streams

    and the mesh ofcourse share the same index/face data

    is this possible

    then we can setup a defered render of shadows maps and render out 3 deferent shadowsmaps and only use one drawcall per mesh

  14. sorry not the defared render of shadowsmaps i meen a g-buffer

    sorry

    hope you understand

  15. ShawnHargreaves says:

    > hope you understand

    I don’t, sorry πŸ™‚

    Multiple vertex streams are not the same thing as instancing or geometry shaders. The vertex shader still runs in the same way as ever: using multiple streams just sets up a gather operation to read vertex shader input data from several arrays of smaller structures rather than just one array of bigger structures.

  16. amartinez1660 says:

    > Moved the VertexBuffer and IndexBuffer properties from ModelMesh to ModelMeshPart

    I would like to know if the VertexBuffer in the model mesh parts is still the same whole mesh vertex buffer.

    If a mesh compounded by several meshParts doesn’t need to be textured (depth pass, shadow map, etc) or by some reason all the mesh parts happen to have, for example, the same settings, the multiple draw calls for each meshpart can be simplified by adding all the vertices and primitives counts and render it in one draw call.

    If not, does the answer relies here?

    > device.SetVertexBuffers(vb1, vb2)

    device.SetVertexBuffers(meshPartVB1, meshPartVB2, …..);

    DrawPrimitives();

    I, too, have wasted unimaginable amounts of hours tracking vertex declarations, specially at the beginning. Nice to see this has changed.

  17. Rim says:

    > A major goal of the 4.0 changes is to find

    > abstractions that do a better job of fitting

    > over all these various back-end

    > implementation layers

    Yeah I keep forgetting that and I probably shouldn’t have posted here right after filing that infuriating bug on Connect πŸ™‚

    I guess I’m not the typical customer your developing for anyway, being content to just mess with stuff on the PC. Reviewing my gripes, honesty would have me admit they’re mostly either somewhat exotic edge cases or just non-commercial tinkerings.

  18. So what do I do if I’m using a byte[] buffer and a VertexDeclaration that have to be dynamically generated?

    (This is a very common case in my current codebase)

  19. what is meen is this shawn

    if you setup a g-buffer with 3 rendertarget

    and you have 3 vertex streams wicth share the same index / face data

    can you out each of these wertex stream to each rendertarget

    with 3 worldmatrix  each for a vertex stream

    then we can do csm/pssm shadows with one drawcall per. mesh

  20. Ok. I just re-read and I see my question is answered!

    Great!

    *please* tell me the overload will work with any array type other than just byte[]!

    I’m much happier now. I was having a panic that a lot of my code was suddenly going to break badly.

  21. ShawnHargreaves says:

    > *please* tell me the overload will work with any array type other than just byte[]!

    Absolutely.

  22. ShawnHargreaves says:

    > can you out each of these wertex stream to each rendertarget with 3 worldmatrix  each for a vertex stream

    No. That’s not how vertex streams or MRT work.

    Multiple vertex streams just provide different input data for a single vertex shader. And when using MRT, your pixel shader can write multiple color values. These two features are unrelated, and in all cases there is just a single shader invocation and single triangle rasterization.

  23. ShawnHargreaves says:

    > If a mesh compounded by several meshParts doesn’t need to be textured (depth pass, shadow map, etc) or by some reason all the mesh parts happen to have, for example, the same settings, the multiple draw calls for each meshpart can be simplified by adding all the vertices and primitives counts and render it in one draw call.

    If two mesh parts share all the same settings (same vertex format, effect, material parameter values, local transform, etc), they can certainly be merged into a single piece. The built-in Model processing logic doesn’t do this for you automatically, though (it assumes that if you have data split into multiple parts, this might be because you want to draw them independently, move them separately, etc). Collapsing multiple mesh parts into a single bigger part can be a significant content optimization if your source content has many small and collapsible parts (perfect application for a custom model processor subclass). The GS4 API changes don’t alter this situation in any significant ways.

  24. jon watte says:

    My question was Davids question: what if I don’t know the usage induces up front?

    I’m not sure how I feel about auto-renumbering yet. At least let’s hope it actually gets implemented, though! No solution would suck.

    Btw: how about allowing re-defining the format on an existing buffer?

  25. ShawnHargreaves says:

    > My question was Davids question: what if I don’t know the usage induces up front?

    You would do this using auto-renumbering:

    – Create any number of vertex buffers, each of which contains POSITION0 (and maybe NORMAL0) data for a single morph target

    – Create a shader that reads POSITION0, POSITION1 (maybe POSITION2 if you are doing multi-way tweens) etc

    – At draw time, SetVertexBuffers(staticVertexData, morphTargets[previousFrame], morphTargets[currentFrame]);

    And it "just works" ™, with whichever morph target you bound second getting remapped from POSITION0 to POSITION1.

    > At least let’s hope it actually gets implemented, though! No solution would suck.

    Agreed. I finished implementing this last week.

    > Btw: how about allowing re-defining the format on an existing buffer?

    Not possible in GS4. Would be interested to understand more about what scenarios you would need this for?

  26. Oh of course says:

    Just starting out with XNA and let me tell you, this is so nerve racking when most of the tutorials and examples are built around the old type and then it is changed.

    Every time i try to build something i get errors. Pretty much anything i try to rebuild in VS 2010 is totally screwed up. Reminds me of my actionscript days when the developers decided to rename and restructure everything.

    not worth learning might as well just stop now learn how to build my own framework.

  27. Gibber says:

    I'm changing some code over to XNA 4.0 that contains a custom vertex format. I thought I had everything swapped correctly, but on initialization of the Vertex Buffer I get the message "Invalid VertexDeclaration. Elements Position0 and TextureCoordinate0 are overlapping." ? Vertex formate structure looks like:

          public struct VolumeVertex : IVertexType

           {

               public Vector3 Position;

               public Vector3 TexCoord;

               public VolumeVertex(Vector3 position, Vector3 texCoord)

               {

                   this.Position = position;

                   this.TexCoord = texCoord;

               }

               public static VertexElement[] VertexElements =

                {

                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),

                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.TextureCoordinate, 0),

                };

               public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration(VertexElements);

               VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }

               public static int SizeInBytes = sizeof(float) * (3 + 3);

           }

    What am I missing here?

  28. ShawnHargreaves says:

    Gibber: both of your VertexElement structs specify an offset of zero. You need to offset one of them to a different location so they do not overlap in memory.

  29. André Barbosa says:

    Hi.

    I'm trying to convert some code to XNA 4.0 that also contains a custom vertex format, but I'm getting this message "Invalid vertex type. The size of TangentVertex does not match the stride of its vertex declaration."

    The code looks like this:

    public struct TangentVertex : IVertexType

       {

           GraphicsDevice graphicsDevice;

           public Vector3 pos;

           public Vector2 uv;

           public Vector3 normal;

           public Vector3 tangent;

           public static int SizeInBytes

           {

               get

               {

                   // 4 bytes per float:

                   // 3 floats pos, 2 floats uv, 3 floats normal and 3 float tangent.

                   return 4 * (3 + 2 + 3 + 3);

               }

           }

           public float U

           {

               get

               {

                   return uv.X;

               }

           }

           public float V

           {

               get

               {

                   return uv.Y;

               }

           }

           private static readonly VertexElement[] VertexElements =

               GenerateVertexElements();

           public static readonly VertexDeclaration VertexDeclaration =

               new VertexDeclaration(VertexElements);

           VertexDeclaration IVertexType.VertexDeclaration { get { return VertexDeclaration; } }

           private static VertexElement[] GenerateVertexElements()

           {

               VertexElement[] decl = new VertexElement[]

    {

    // Construct new vertex declaration with tangent info

    // First the normal stuff (we should already have that)

    new VertexElement(0, VertexElementFormat.Vector3,

                           VertexElementUsage.Position, 0),

    new VertexElement(12, VertexElementFormat.Vector2,

                           VertexElementUsage.TextureCoordinate,

    0),

    new VertexElement(20, VertexElementFormat.Vector3,

                           VertexElementUsage.Normal, 0),

    // And now the tangent

    new VertexElement(32, VertexElementFormat.Vector3,

                           VertexElementUsage.Tangent, 0),

    };

               return decl;

           }

           public TangentVertex(GraphicsDevice graphicsDevice,

               Vector3 setPos,

               float setU, float setV,

               Vector3 setNormal,

               Vector3 setTangent)

           {

               this.graphicsDevice = graphicsDevice;

               pos = setPos;

               uv = new Vector2(setU, setV);

               normal = setNormal;

               tangent = setTangent;

           }

           public TangentVertex(GraphicsDevice graphicsDevice,

               Vector3 setPos,

               Vector2 setUv,

               Vector3 setNormal,

               Vector3 setTangent)

           {

               this.graphicsDevice = graphicsDevice;

               pos = setPos;

               uv = setUv;

               normal = setNormal;

               tangent = setTangent;

           }

       }

    Can someone help me please?

  30. ShawnHargreaves says:

    AndrΓ©: I would recommend the creators.xna.com forums for this question (once they come back up after current maintenance). Blog comments aren't a great place for posting source code: the formatting gets all messed up so it's too hard to read πŸ™‚

  31. André Barbosa says:

    I used the blog comments because the creators club forum was down.

    But I see what you mean, the code didn't look so ugly before posting πŸ™‚

    Thanks.

  32. Amir Rezaei says:

    Use sizeof(float) instead of 4

  33. you guys really messed it up hey. says:

    Thanks for the hours wasted.

  34. Sarma says:

    Sorry for the late entry…I am new to XNA.

    I am getting the following err:

    Invalid vertex type. The size of VertexPositionColoredNormal does not match the stride of its vertex declaration.

    VertexPositionColoredNormal is my new defined datatype kind.

  35. twobob says:

    I have to say. And I know you guys are long gone now… That the lack of actual working demos in this area versus the plethora of broken ones is tiresome.

    But thanks for the great info anyhow and good luck with your futures