Limit the number of draw calls

I know where the error lies in my code at this point, and I know what I need to do to fix it, I just don't know how. My problem comes with the 120,000+ draw calls I get each frame. This is caused by my main method which draws each cube in a list cubes.

     public void DrawMap(GameTime gameTime)
{
foreach (Cube block in cube.cubes)
{
Matrix translate = Matrix.CreateTranslation(block.cubePosition);
cube.Draw(effect);

}
}


Now obviously there is more than just this in the method, but these are the two lines I'm focused on. I need the translate matrix to place cubes where they're supposed to go, and the cube.Draw for actually drawing the cube. My goal was to create a chunk of cubes to limit the number of draw calls, but what I have isn't accomplishing that.

What I was thinking is that I need to just use the cube.Draw method. So I tried this

   public void Draw(BasicEffect effect)
{
device.SetVertexBuffer(vertexBuffer);
device.Indices = indexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
no++;
pass.Apply();

device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
}
}


There was more to it than that, but you get the idea. This only created one cube, although the index and vertex buffers have more than one cubes indices and vertices stored in it as illustrated by the code below seen in many of my past questions.

  for (x = 0; x < 50; x++)
{
for (z = 0; z < 50; z++)
{
for (y = 0; y <= map[x, z]; y++)
{
SetUpIndices();
SetUpVertices();
cubes.Add(new Cube(device, new Vector3(x, map[x, z] - y, z), grass));
}
}
}

vertexBuffer = new VertexBuffer(device, typeof(VertexPositionTexture), vertices.Count, BufferUsage.WriteOnly);
vertexBuffer.SetData<VertexPositionTexture>(vertices.ToArray());

indexBuffer = new IndexBuffer(device, typeof(short), indices.Count, BufferUsage.WriteOnly);
indexBuffer.SetData(indices.ToArray());


What I learned through using a breakpoint is that there were actually 2500 cubes being drawn in one place. I had only about 150 draw calls a frame at that time, which is a lot better than what I did have.

Is there something I can change that sets the vertices and indices to the position of my cube in order to draw the cubes in the right place?

    private void SetUpVertices()
{
//front left bottom corner ok
vertices.Add(new VertexPositionTexture(new Vector3(0, 0, 0), new Vector2(1, 0)));
//front left upper corner
vertices.Add(new VertexPositionTexture(new Vector3(0, 1, 0), new Vector2(1, 1)));
//front right upper corner ok
vertices.Add(new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(0, 1)));
//front lower right corner
vertices.Add(new VertexPositionTexture(new Vector3(1, 0, 0), new Vector2(0, 0)));
//back left lower corner ok
vertices.Add(new VertexPositionTexture(new Vector3(0, 0, -1), new Vector2(0, 1)));
//back left upper corner
vertices.Add(new VertexPositionTexture(new Vector3(0, 1, -1), new Vector2(1, 1)));
//back right upper corner ok
vertices.Add(new VertexPositionTexture(new Vector3(1, 1, -1), new Vector2(1, 0)));
//back right lower corner
vertices.Add(new VertexPositionTexture(new Vector3(1, 0, -1), new Vector2(0, 0)));
}

private void SetUpIndices()
{


• It sounds like the problem is with the code you are using to put data into your buffers. Post that. – Andrew Russell Jul 14 '13 at 9:09
• Hmm I suppose what I'm doing is just creating buffers at the same position. – Christian Frantz Jul 14 '13 at 9:22

Your code for SetupVertices is not taking into account the position of each cube in the chunk. The vertex buffer is for the whole chunk - and individual vertices must be stored in "chunk" space (relative to to the position of the chunk). Right now you're just creating a bunch of cubes at the origin. You need to translate them (just use +) to the correct position.

Then your SetupIndices method is not taking into account the location in the vertex buffer where each cube starts. So, effectively, each time you're adding a cube, you're really telling the GPU to render the first cube in the buffer - and the later cubes are just being ignored.

Here is a modified version of your code. I haven't tested it or checked your numbers. This is just to illustrate how you must do the offsetting for both vertices and indices. This code replaces SetupVertices and SetupIndices with a single method that takes the coordinates of each cube relative to the chunk.

private void SetUpVerticesAndIndicies(int x, int y, int z)
{
short verticesStart = (short)vertices.Count;
Vector3 cubePosition = new Vector3(x, y, z);

// Create the appropriate vertices for the cube:

//front left bottom corner ok
vertices.Add(new VertexPositionTexture(cubePosition + new Vector3(0, 0, 0), new Vector2(1, 0)));
//front left upper corner
vertices.Add(new VertexPositionTexture(cubePosition + new Vector3(0, 1, 0), new Vector2(1, 1)));
//front right upper corner ok
vertices.Add(new VertexPositionTexture(cubePosition + new Vector3(1, 1, 0), new Vector2(0, 1)));
// ... etc ...

// Create the appropriate indices for the cube:
// (these are indexes into the vertices list)

// ... etc ...
}


Hopefully you can see how this would translate to a method to add individual faces, as we discussed in your previous questions.

• I'm having trouble getting this to compile because of the types of values. vertices.Count returns an int, while indices is a short and won't work as an int. Do I need to use some kind of casting for the verticesStart variable? – Christian Frantz Jul 14 '13 at 12:45
• I got it to compile, but no values are being added to the vertex or index buffer – Christian Frantz Jul 14 '13 at 13:25
• Ah, yes - forgot about 16-bit indices. I fixed my answer - glad you figured it out. However, I can't see anything in your code or mine that would cause no values to be added to the lists. Try stepping through it with the debugger and seeing what happens. – Andrew Russell Jul 14 '13 at 14:03
• I'm not seeing the problem either lol. With a breakpoint at the last bracket of the for loop that contains SetUpIndicesAndVertices, it shows both vertices and indices lists with values of zero – Christian Frantz Jul 14 '13 at 14:13
• Oh I forgot to add my CreateMap() method in LoadContent. Now it runs but draws a giant white spot in the corner which doesnt move or change – Christian Frantz Jul 14 '13 at 14:45

I don't really know what you're actually drawing, but you should start with some simple culling.

In essence, don't draw stuff you can't see anyway.

This can be a bit tricky in 3D and it's partially done by the graphics card anyway, but it will still have to try to draw things, which will still cause work to be done.

Also try to avoid creating new objects in your drawing code, unless they're really small/primitive.

I assume this is supposed to become some kind of minecraft like block landscape.

• So, at first, I'd cull by distance: Group blocks into larger chunks of blocks. For explanation I'll only explain this in one dimension. In the end you'd apply it to all dimensions, essentially collecting cubes within a grid of cubes.
• Let's assume you've got 100 cubes to draw with x coordinates ranging from 0 to 100. You create chunks or containers every 10 units: The 10 cubes with coordinates between 0 and 10 are associated to the first chunk, the cubes from 10 to 20 belong to the second chunk, etc.
• Based on your view/drawing distance, now you only draw chunks being closeby. For example, let's assume the drawing distance is 5 and your camera is at 47: This would mean you'd be drawing the chunk from 40 to 50 as well as the chunk from 50 to 60. so instead of drawing 100 cubes, you'd actually draw a maximum of 20 cubes. You're saving 80% of draw calls!
• Further optimizations: Cache the render data for each chunk. For example, assign each chunk it's own vertex buffer and stuff instead of drawing cube by cube. Then just draw the chunks. You only update that data if a cube inside the chunk changes (e.g. is destroyed).

Regarding your last note with positioning: Your actual vertex coordinates should always be local. E.g. their origin should be at the cube's (or chunk's) origin. Then adjust your world matrix to "move" these into position. This way you don't have to touch vertex data again, resulting in another performance gain.

Overall, in DirectX there are three matrixes determining how/where something is rendered. Right now I'm not 100% sure how abstracted away these are in XNA, so I might be a bit off here:

• World Matrix: This determines the actual translation/rotation of some vertex in the actual world. It's essentially the global coordinate of an object.
• View Matrix: This determines the camera's position, orientation, etc.
• Projection Matrix: This determines how the camera actually "looks" into the world. For example, this determines the aspect ratio/opening, the actual projection (orthogonal vs. perspective), etc.