3
$\begingroup$

So I want to render a buttload of quads by using a single 4 point vertex array and change the position of the quad. What is quicker:

Using glBufferSubData to change the positional data or

Using glMapBufferRange to DMA the buffer and glUnmapBuffer to push the data back to the GPU?

When using the vertex buffer data, I need to also call glVertexAttribArray to reassociate the data with the vertexArrayObject.

When using uniform buffers and do it with shader magic I only need to change the uniform buffer.

What is quicker?

$\endgroup$
6
$\begingroup$

A key rule of thumb for high-performance code in general, and graphics in particular, is to batch large amounts of work together rather than doing it in small chunks. In your case, rather than sending one quad's worth of data at a time and drawing one quad at a time, it will be far more efficient to send many quads' data in a large batch, and draw many quads together in a single draw call.

A good way to accomplish both is instancing. You would have a 4-point vertex buffer for the corners of a quad, and a second vertex buffer for the values that are per-quad (what you're putting in your uniform buffer now). This second buffer would be large enough to hold the data for all the quads at once, not just one quad. To populate the vertex buffer, you can map the whole thing, write all the data, and unmap. Then do a single instanced draw call to render all the quads at once.

However, if instancing can't be used for some reason (for instance if each quad needs a different texture, and it's not possible or desirable to put them together in an atlas), you can still upload all the data in one batch. Create a uniform buffer large enough to hold an array of all the quads' data; map/write/unmap it all in one big batch as before; then before each draw call, use glBindBufferRange to select one quad's data from that large buffer.

Batching can also be used when you don't know the number of quads in advance. Just pick a "reasonably large" batch size (say, 1024 quads) and size your buffers for that number. Accumulate quads into the buffer until you reach the size limit, then draw them all, and start over (using the GL_MAP_INVALIDATE_BUFFER_BIT flag with glMapBufferRange to get a fresh copy of the buffer). You'll also need to do a partial draw at the end of this process to handle any left-over quads that didn't reach the size limit.

$\endgroup$
  • $\begingroup$ I am currently using the code to provide 2d rendering support for sprites that position the image pixel perfect to the desired position on the screen in pixel coordinates instead of opengl coordinates. As such I currently do not plan to allow batch rendering of multiple quads but instead use the quad as a "stamp" to render the image to the screen. As such each quad drawn has of course a different texture or different texture coordinates to read from. As such I also never know how many quads are drawn beforehand. $\endgroup$ – salbeira Feb 19 '16 at 19:19
  • 1
    $\begingroup$ @salbeira OK. You could still combine sprites in an atlas to allow instancing. Even if you don't do that, though, you can still batch the uniform buffer updates. And you can batch even while not knowing how many quads are drawn beforehand—I added a paragraph to the answer about that. $\endgroup$ – Nathan Reed Feb 19 '16 at 20:08
  • $\begingroup$ I can imagine how this should work but it still seems a bit like a mystery to me. Like when I supply a method to "render a single spirte" that has a texture attached to it and information about where to find the sprite within that texture, I'd need to pile that data up and store it in a huge prepared array. When I want to draw that I'd still need to call a drawElements for each sprite, and bind each texture that contains that sprite sparately. Do I magically combine each texture with a framebuffer to a huge atlas before rendering? Or should I just say to the application dev to use an atlas? $\endgroup$ – salbeira Feb 20 '16 at 3:04
  • $\begingroup$ @salbeira Yeah, designing the interface for this sort of thing is tricky. You could supply a method to "add a sprite to the render list" and another method to "render all the sprites on the list", so the user would be responsible for calling that when the rendering was finished. If the rendering order of the sprites doesn't matter, you could also sort them by texture, so that you don't have to switch textures so many times and you can use instancing in a group of sprites with the same texture. Then your system would work with or without a user-provided atlas (but faster with). $\endgroup$ – Nathan Reed Feb 20 '16 at 7:45
  • $\begingroup$ I guess this will also not work since I'd like the user of these functions to be able to apply the painters algorithm if needed and also the bound framebuffer, color modifiers and stencil information all need to be applied in order $\endgroup$ – salbeira Feb 21 '16 at 4:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.