5
$\begingroup$

In order to naïvely render sprites, you can send single rectangles with their position, rotation, size, and texture. This is very easy, but given that reducing state changes, draw calls, and data uploads are good ways to improve rendering performance, it's clearly not an efficient solution. Some techniques like passing each sprite as a single primitive and assembling a rectangle in the geometry shader are very appealing but aren't available if targeting a platform like WebGL or even just older hardware.

On top of issues like batching and texture aliases, how do you efficiently submit geometry to render sprites?

I'm interested in answers that are rendering API agnostic, but using a specific API like OpenGL or Direct X is fine for illustration purposes.

$\endgroup$
2
  • $\begingroup$ Are you talking specifically about OpenGL? Specifically about WebGL? Is instancing available for you? $\endgroup$
    – Rotem
    Aug 7, 2016 at 8:06
  • 1
    $\begingroup$ Good question, let me clarify that. I'm not particularly interested in a specific graphics API, although I do want the work done on the GPU. $\endgroup$
    – porglezomp
    Aug 7, 2016 at 14:26

1 Answer 1

5
$\begingroup$

A good way to improve sprite rendering efficiency is geometry instancing. This lets you define a mesh using one vertex/index buffer pair, then render many instances of that mesh in a single draw call using a second vertex buffer whose elements define the instances. This is more widely supported than geometry shaders; in particular, it's is supported in WebGL via the ANGLE_instanced_arrays extension.

To apply this to sprites, one vertex buffer could define the shape of a sprite (i.e. a quad made of two triangles), then another vertex buffer could contain per-sprite information such as position, rotation, size, and so on.

If there are 4 vertices per sprite and N sprites, the vertex shader will be called a total of 4N times, onces for each vertex in each sprite. You'd use the vertex shader to put the information from the two buffers together, transforming each vertex appropriately to the sprite's properties. This allows the GPU to do the work of generating all the vertices for the sprites, and saves on data uploaded from the CPU.

Coincidentally, I published a small OpenGL instancing demo recently.

Using instancing this way does require that all the sprites are drawn with the same state, i.e. the same shader, textures, blending mode and so on. If different states are needed for some sprites, a separate batch of instances will be needed for each state combination.

$\endgroup$
7
  • $\begingroup$ Could point sprites be used in place of 2x triangle vertices? The instance data could be a vec2 to define both the orientation and the size. Although you might need another float in there for rectangular sprites. $\endgroup$
    – PaulHK
    Aug 8, 2016 at 3:00
  • 1
    $\begingroup$ @PaulHK Point sprites are very limited—they sometimes have a small maximum size depending on GPU, and they don't support rotation or any non-square shapes. Also, they get clipped based on their center point, so they pop out too soon when they move off the edge of the screen. I would not bother with them. $\endgroup$ Aug 8, 2016 at 3:32
  • $\begingroup$ Yeah I thought they would be a little inflexible, you could rotate in the frag shader but then you need to over-project the size to fit the diagonal.. $\endgroup$
    – PaulHK
    Aug 8, 2016 at 3:51
  • $\begingroup$ I've seen it commented before that instancing can be slow if you're dealing with very small meshes (like a single pair of triangles). Can you comment on that at all? $\endgroup$
    – porglezomp
    Aug 8, 2016 at 19:14
  • 1
    $\begingroup$ In my own experiments drawing sprites, rendering one mesh with 6k vertices (ie one mesh containing all sprites) was faster than rendering a mesh with 6 vertices and 1k instances. Note that in my instancing I did not use glVertexAttribDivisor but instead read from a buffer object. Using glVertexAttribDivisor may be faster since the gpu could push the data to the shader, while reading a buffer object means the shader has to pull its data. But I didn't benchmark this, so dunno if it matters. I still like instancing, because its a simple clean solution. $\endgroup$
    – user4925
    Aug 16, 2016 at 10:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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