1
\$\begingroup\$

I am looking ways to improve the efficiency of my draw method in XNA/Monogame and understand how things work. I have just a group of 4 models (bricks), (red, green, blue, yellow) I am drawing multiple times on the screen (100-120 total models, so drawing each model 25-30 times) on the current frame.

Here is how i am drawing 1 of the 4 types of models (the red ones) right now:

Matrix[] transforms = new Matrix[Brick.Red.Bones.Count];
Brick.Red.CopyAbsoluteBoneTransformsTo(transforms);
ModelMesh mesh = Brick.Red.Meshes[0];
BasicEffect effect = (BasicEffect)mesh.Effects[0];
effect.EnableDefaultLighting();
effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
   aspectRatio, 1.0f, 10000.0f); 
foreach (Brick brick in Bricks)
{
    int i = brick.Index / Columns;
    int j = brick.Index % Columns;
    modelPosition = new Vector3(-HalfWidth + j * BrickWidth, 
        HalfHeight - i * BrickHeight, 0.0f);
    effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateScale(scale) * 
        Matrix.CreateTranslation(modelPosition);
    mesh.Draw();
}

This code seems to not perform as well as I expected (since its just 4 models drawn many times on different locations) when I timed it with a StopWatch. Is there something silly in my code that could be done better and i'm missing it ? I can't tell because so far I have only worked with sprites and 2D.

Edit: From other codes I saw that they used effect.Parameters["World"].SetValue(...); to set the BasicEffect values, but that gave me a null exception, and I am not sure what is the difference of that function since in my mind it seems the same with effect.World = ...;

\$\endgroup\$
  • 3
    \$\begingroup\$ This looks like a job for instancing. \$\endgroup\$ – jzx Feb 15 '15 at 5:11
  • \$\begingroup\$ @jzx how could i implement that ? can you elaborate ? \$\endgroup\$ – dimitris93 Feb 15 '15 at 5:28
2
\$\begingroup\$

It looks like hardware instancing isn't supported by MonoGame yet, which is ideal. That leaves us with the answer to MonoGame: Draw thousands of quads without hardware instancing. Still, I've included an explanation of how it would be done if they get around to it.

Hardware Instancing (pure XNA HiDef only for now)

It's been a while since I've done this, so most of my explanation will be taken from Shawn Hargreaves Blog: DrawInstancedPrimitives in XNA Game Studio 4.0 and the MSDN Instanced Model code sample. Moreover, this is just an explanation of how the method works. The way you'll need to do it in practice is far too complicated to explain here, so you'll probably need to refer to additional resources online if you run into any problems.

This form of instancing uses a shader to render the same thing in many places with various parameters. An example of such a shader can be found in the above code sample, but a simple instancing shader would be

float4x4 WorldViewProj;

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

...which we use to create an Effect that accepts, in this example, the many ways the model will be drawn. Of course, it's possible to use additional parameters like color, texture, etc., but to keep things simple I will stick to position.

To get that information into the shader we need a special vertex buffer:

var instanceVertexDeclaration = new VertexDeclaration
(
    new VertexElement(0,  VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0),
    new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 1),
    new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 2),
    new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 3)
);

// You can hang onto this between draw calls:
var instanceVertexBuffer = new DynamicVertexBuffer(GraphicsDevice,
                                                   instanceVertexDeclaration,
                                                   instances.Length,
                                                   BufferUsage.WriteOnly);
// Will need to call this if something has moved.
//     See DynamicVertexBuffer.SetData for more details.
instanceVertexBuffer.SetData(instances, 0, instances.Length, SetDataOptions.Discard);

And finally we can pass our mesh and instance information to the hardware for rendering:

foreach (ModelMesh mesh in model.Meshes)
{
    foreach (ModelMeshPart meshPart in mesh.MeshParts)
    {
        // Tell the GPU to read from both the model vertex buffer
        //     plus our instanceVertexBuffer
        GraphicsDevice.SetVertexBuffers(
            new VertexBufferBinding(meshPart.VertexBuffer, meshPart.VertexOffset, 0),
            new VertexBufferBinding(instanceVertexBuffer, 0, 1)
        );

        GraphicsDevice.Indices = meshPart.IndexBuffer;

        // Set up the instance rendering effect.
        Effect effect = meshPart.Effect;

        effect.CurrentTechnique = effect.Techniques["HardwareInstancing"];

        // (Set view/world/projection matrices on effect. See sample.)

        // Draw all the instance copies in a single call.
        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

            GraphicsDevice.DrawInstancedPrimitives(PrimitiveType.TriangleList,
                                                   0,
                                                   0,
                                                   meshPart.NumVertices,
                                                   meshPart.StartIndex,
                                                   meshPart.PrimitiveCount,
                                                   instances.Length);

        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ DrawInstancedPrimitives does not exist in monogame \$\endgroup\$ – dimitris93 Feb 16 '15 at 0:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.