Optimized render loop.

Currently my render loop looks something like the pseudocode below. It works fine but is this really the most efficient way to do this? It looks like I’m copying the same data to the GPU over and over again. If the geometry that I’m rendering isn’t changing, can’t I just copy it once with a single set of glBindBuffer/glBufferData calls in the initialization step and then just call glDrawElements, maybe with changes just to the count and offset parameters to render specific objects? What is the best way to do this? Thanks!


glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

for( int i = 0; i < numObjects; i++ ) {
        // Assign vertices
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, vertexCount[i]* sizeof(float) * 3, pVertexData[i], GL_STATIC_DRAW);


        // Assign colors
        glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
        glBufferData(GL_ARRAY_BUFFER, vertexCount[i]* sizeof(unsigned char) * 4, pColors[i], GL_STATIC_DRAW);


        // Assign normals
        glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
        glBufferData(GL_ARRAY_BUFFER, vertexCount[i]* sizeof(float) * 3, pNormalData[i], GL_STATIC_DRAW);


        // Assign indices
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount[i]* sizeof(unsigned int), pIndices[i], GL_STATIC_DRAW);


        // Update shader
        UpdateModelMatrix(renderData[i]);



        // Draw the triangles
        glDrawElements(
        GL_TRIANGLES,
        indexCount[i],
        GL_UNSIGNED_INT,
        (void*)0
        );
}

No.

Yes.

Don’t keep uploading the same data to the buffers repeatedly. Once you put data in a buffer, it will stay there until you replace it or delete the buffer. That’s what buffers are for.

More generally, most OpenGL state doesn’t change unless you explicitly change it. So if you’re setting up the attribute arrays (glVertexAttribPointer, glEnableVertexAttrib array) each frame, you probably don’t need to do that either. If you only have one set of attribute data, you can just set the state once and leave it. If you have multiple sets of attribute data and are using at least OpenGL 3.0, you can create a vertex array object (VAO) for each set of attribute state and switch between them with glBindVertexArray() (note that a VAO also stores any GL_ELEMENT_ARRAY_BUFFER binding).

For drawing subsets of the geometry, you don’t need to rebuild the element array or use multiple draw calls. You can use glMultiDrawElements() to draw multiple sub-ranges of the geometry in a single call.

But the biggest saving will come from not copying the data every frame.

Thanks for the tips! I’m not re-setting my attribute arrays each frame. I just do that once during program start. I’m using OpenGL 4.3, so I’ll experiment with glMultiDrawElements() and just copying the buffer data once. I’ll need to update my shader state variables between draw calls too, but I assume that’s normal for this sort of rendering.

If you’re going to be rendering most/all of those same objects again in subsequent frames, keep in mind that glMultiDrawElements() just kicks the can down the road a bit but doesn’t fully solve the problem (of having to repeatedly pull from arrays of data in client memory – i.e. application CPU memory – rather than pulling everything completely from GPU memory).

While it does support sourcing the vertex attribute data and the index data from bound buffer objects (which likely reside either in GPU memory physically, or in CPU pinned driver memory directly accessible to the GPU), IIRC the “count” and “indices” array parameters you pass into glMultiDrawElements() will be read from app CPU memory every time you launch the draw call, not from GPU memory (i.e. they’re always pointers into app CPU memory).

Because of this, it’s likely that this draw call will be implemented as a CPU loop inside the GL driver, and not as a loop on the GPU.

If you want to address these issues too, check out: glMultiDrawElementsIndirect(). With this, you can not only store the per-draw count and indices parameters in a buffer object, but you can also launch multiple draw calls utilizing Instanced Rendering as well as get the full base vertex and base index addressing functionality of glDrawElementsInstancedBaseVertexBaseInstance() for every draw within the multi-draw.

Finally, another option to consider: you could instead render all this with a single glDrawElements if you just glue all the draws together. Since you’re using indexed TRIANGLES, you don’t even need to use primitive restart to break strip or fan primitives. However, if you need to render subsets of your geometry, glMultiDrawElementsIndirect() or glMultiDrawElements() could be better choices here.

And of course, all 3 of these approaches require that you are storing the vertex attribute and index data for all of the draw calls to be merged in a shared set of buffer objects (so that you don’t need to rebind buffer objects between the individual draws). That’s positive, as reducing buffer binds is good for performance.