Indirect Rendering : “A way to a million draw calls”

Hello !
This time I am going to talk about the Multi Draw Indirect (MDI) rendering. This feature allows you to enjoy both the purpose of multiDraw and indirect drawing.

Where does the overhead comes from?

Issuing a lot of commands

Issue a drawcall in GPU based rendering is a really heavy operation for the CPU. Knowing this, drawing a lot of models could be really expensive.  A naive draw loop could be seemed like that:

The problem is solved using glMultiDraw.
The new code is:

Unknown data

Now, admit you want to use culling to improve performance. You know that if you perform it on the GPU side, you will be more efficient than if you use the CPU, but you don’t know how to use the result without passing data from the GPU to the CPU…  This is where indirect drawing is efficient.

Your old code is

Using MDI, you could have something like that

And you don’t have to get the result from the CPU.

ARB (MULTI) DRAW INDIRECT

Data and functions

This extension provides two structures to perform a drawCall. One for glDrawArrays and one for glDrawElements.

count specifies the number of elements (vertices) to be rendered
primcount specifies the number of instances to be rendered (in our cases, it will be 0 or 1)
first specifies the position of the first vertex
firstIndex specifies the position of the first index
baseVertex specifies the position of the first vertex
baseInstance specifies the first instance to be rendered (a bit tricky, but I am going to explain that later).

How to Use it

These structures should be put into an OpenGL Buffer Object using the target GL_DRAW_INDIRECT_BUFFER.
Admit you have a big scene with, for 5000 distinct objects and 100 000 meshes. You must have:

  1. 5 000 matrices in a SSBO
  2. 5 000” materials (not really true, but you understand the idea) in a SSBO
  3. 100 000 commands in your indirect buffer
  4. A SSBO which contains bounding boxes data by meshes (to perform culling for each meshes).

Now, what you want is RENDER all the scene. The steps to do that are :

  1. Fill matrices / materials / bouding boxes / indirect buffer
  2. make a dispatch using a compute shader to perform culling
  3. Issue a memory barrier
  4. render

The first step is straightforward.
The second is easy, you use the indirect buffer as a SSBO in the compute shader and set the primCount value to 0 if the mesh is not visible or 1 instead
You are intending to issue an indirect command…
render.

Beautiful ! But how do I know which data I have to use?

  1. The first way is to use gl_DrawIDARB which is pretty explicit.
  2. The way we are going to see and the one I am advising, is to use the baseInstance from structures seen prior.

Why gl_DrawIDARB is not convenient? Simply because it is slower than the second way on most implementations, and because we will not be able to use ARB INDIRECT PARAMETERS with it.

So, for the second way, we must add one or several buffers to the prior list (two in our cases,  one for indexing the matrix buffer, and one for indexing the material buffer). These buffers will contain integer values (the index of the matrix / material in their SSBO). Because they will be used through baseInstance, you understand that these buffers will be vertex buffers using a divisor through glVertexBindingDivisor.

A Caveat?

As you noticed, when you remove a command setting primCount to 0, the command is not really removed… Here is coming the extension ARB INDIRECT PARAMETERS. Instead of settings the primCount to 0, you let it to one, but if the mesh is not visible, you don’t add to the really used buffer command, using an atomic counter, you know exactly how many meshes should be rendered.
You have to bind the atomic buffer to GL_PARAMETER_BUFFER_ARB and use the functions

References

Indirect Parameters
Multi Draw Indirect
Surviving without drawID

Leave a Reply