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:

foreach(object) {
    writeUniformData(object, uniformData);

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

    writeUniformData(object, uniformData[i]);

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

cullingOnCPU(allObject); // quiet slow

        writeUniformData(object, uniformData[i]);

Using MDI, you could have something like that


    writeUniformData(object, uniformData[i]);

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


Data and functions

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

typedef  struct {
    GLuint  count;
    GLuint  primCount;
    GLuint  first;
    GLuint  baseInstance;
} DrawArraysIndirectCommand;

typedef  struct {
    GLuint  count;
    GLuint  primCount;
    GLuint  firstIndex;
    GLint   baseVertex;
    GLuint  baseInstance;
} DrawElementsIndirectCommand;

void glMultiDrawArraysIndirect(GLenum mode,
                           const void *indirect,
                           GLsizei drawcount,
                           GLsizei stride);

void glMultiDrawElementsIndirect(GLenum mode,
                             GLenum type,
                             const void *indirect,
                             GLsizei drawcount,
                             GLsizei stride);

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…

glBindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuffer);

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

void MultiDrawArraysIndirectCountARB(enum mode,
                                     const void *indirect,
                                     intptr drawcount,
                                     sizei maxdrawcount,
                                     sizei stride);

void MultiDrawElementsIndirectCountARB(enum mode,
                                       enum type,
                                       const void *indirect,
                                       intptr drawcount,
                                       sizei maxdrawcount,
                                       sizei stride);


Indirect Parameters
Multi Draw Indirect
Surviving without drawID

calendar September 16, 2016 category OpenGL (, , , , )

1 Response

Leave a Reply

Scroll Top