5
\$\begingroup\$

I'm currently have an application that has ~10k draw calls. I implemented a batching scheme where I group all objects that share material, vertex format, etc and pre-transform them by their world matrices and put all those objects in one vertex buffer / index buffer, and then draw them with DrawIndexedPrimitive (I do this every frame, since some objects might have moved / changed material etc). However, this method is always slower than if I just had multiple draw calls. Using VTune, it seems most of my time is spent computing the transformed vertices and copying the vertex/index data into the buffers.

My question is: Is this expected? Do systems that perform batching typically cache the batched data or are there some that get away with batching every frame like I am attempting to do (and if so, are there any tips for what I might be doing poorly)?

My application is in DirectX 9, but I'm curious about solutions in other platforms as well (OpenGL or DirectX 11).

\$\endgroup\$
2
  • \$\begingroup\$ Have you considered instanced drawing? I don't know much about DirectX, but I think that at least DirectX 11 should support instanced drawing. \$\endgroup\$
    – Herp
    Feb 7, 2014 at 9:57
  • \$\begingroup\$ I've considered it, but for the most part I don't think I have many cases where instancing will work. Most of my draw calls are drawing different things. \$\endgroup\$
    – default
    Feb 7, 2014 at 13:53

2 Answers 2

2
\$\begingroup\$

Batching will often lead to a performance gain, but this is only if you had a bottleneck being created by the multitude of draw calls being made to begin with.

In this particular case you may have simply introduced a new bottleneck.

I think it's likely that the CPU is now having to work overtime to transform and batch all that data. Keep in mind that all those vertices are being transformed individually on the CPU now, instead of on the GPU in the vertex shader (assuming your using shaders ofcourse). Also, you may find that your code is doing a lot of rearranging of memory at the same time.

You also have to keep in mind that when you are using a dynamic vertex buffer, the new buffer has to be copied to the GPU constantly for it to be used. In the case of a static buffer, because you have promised never to change anything, the graphics card will, where possible, keep a cached copy so that the data doesn't need to be transferred over the bus. So in this case, you may actually find that you have a databus bottleneck.

In general batch where you can, but you want to keep the updating of dynamic vertex buffers to a minimum. In this case you might find that the easiest way to get a slight performance boost is to batch all the static objects and forget about anything with a changing world matrix. That will remove those updates and you can batch all that data into a static vertex buffer.

\$\endgroup\$
1
\$\begingroup\$

Batching the way you're thinking about it is usually only used for static objects. In other words, everything that doesn't move and shares a material, transformation, etc. gets grouped up into a common buffer. That way, you don't have to update your vertex buffers every frame.

It seems like you had the right idea for grouping things based on material, vertex format, etc. This produces an optimization by reducing the number of state changes, so that you don't have to keep sending data to different parts of GPU memory.

In other words, it's good to change loops that look like:

for(GameObject obj : objects) {
  setMaterial(obj.material);
  draw(obj);
}

to

for(Material mat : materials) {
  setMaterial(mat);
  for(GameObject obj : mat.objects) {
    draw(obj);
  }
}

As far as I know, most game engines don't do dynamic batching the way you've described it because of the problems you're running into. The rule of thumb is to make sure that everything is grouped so that you have as little actual processing (generating vertex buffers, loading textures, etc) during your render loop as possible, and to do this you can usually get away with having one or two state changes to switch a material or have a few extra draw calls.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .