DrawInstancedPrimitives in XNA Game Studio 4.0

Originally posted to Shawn Hargreaves Blog on MSDN, Thursday, June 17, 2010

When writing about future features, there is a danger things might change in between the time I write about them and when our product ships. For this reason, I have avoided going into too much detail about a couple of planned Game Studio 4.0 features that were not yet entirely implemented and therefore still at risk.

I finished the new DrawInstancedPrimitives API on Windows a couple of weeks ago, and just now checked in the Xbox implementation, so I figure this is a good time to talk about it.

Mesh instancing is an important performance optimization for many games, but was not exactly consistent across platforms. Our instancing sample shows four approaches:

Yuck.

Game Studio 4.0 still supports variants of all these techniques, but adds a new, easier to use and more portable version of the hardware instancing API.

 

State batching in Game Studio 4.0

Game Studio 4.0 merges Effect.Begin / End and Effect.CommitChanges into a single EffectPass.Apply API. This was not fully optimized in our CTP release, so games that used state batching saw a performance hit. In our final version, EffectPass.Apply is optimized to be smarter about how much device state needs to be updated when the same effect is applied many times in a row, so state batching has the same performance as in previous releases, and some less carefully optimized drawing code now runs faster.

 

Shader instancing in Game Studio 4.0

If you are targeting Windows or Xbox, you can implement shader instancing the same way as before. Because Windows Phone does not support programmable shaders, you cannot use the exact same technique on the phone, but the new SkinnedEffect class can provide the same result with similar performance. Replicate many copies of your vertex and index data, the same as for shader instancing in previous Game Studio versions, adding bone indices and weights vertex channels, with each vertex weighted 100% to a single bone. Pass your instance transforms to SkinnedEffect.SetBoneTransforms, and draw using SkinnedEffect with WeightsPerVertex = 1. Tada! The same result as shader instancing, but this way works on the phone.

 

VFetch instancing in Game Studio 4.0

If your game is exclusive to Xbox, you can still use VFetch instancing, but I don't know why you would want to. The new DrawInstancedPrimitives API is generally a better choice.

 

Hardware instancing in Game Studio 4.0

Our old hardware instancing API, which was exclusive to Windows and shader model 3.0, has been replaced with a new DrawInstancedPrimitives API:

Here is the instancing shader from one of my unit tests (simplified to remove irrelevant things like lighting computations):

    float4x4 WorldViewProj;

    void InstancingVertexShader(inout float4 position : POSITION0,
                                in float4x4 world : TEXCOORD0)
    {
        position = mul(mul(position, transpose(world)), WorldViewProj);
    }

The test creates a simple cube model, and also a DynamicVertexBuffer using a custom vertex type which encodes a 4x4 matrix as a set of four float4 texture coordinates:

    VertexDeclaration instanceDecl = new VertexDeclaration
    (
        new VertexElement(0,  VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 0),
        new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1),
        new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 2),
        new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3)
    );

The instanced drawing code is now pretty simple:

    instanceVertexBuffer.SetData(instanceTransformMatrices, 0, numInstances, SetDataOptions.Discard);
            
    graphicsDevice.SetVertexBuffers(modelVertexBuffer, new VertexBufferBinding(instanceVertexBuffer, 0, 1));
    graphicsDevice.Indices = indexBuffer;
    instancingEffect.CurrentTechnique.Passes[0].Apply();

    graphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0,
                                           modelVertexBuffer.VertexCount, 0,
                                           indexBuffer.IndexCount / 3,
                                           numInstances);
Blog index   -   Back to my homepage