5
\$\begingroup\$

I just did a small benchmark. To my surprise batching sprites into a single draw call doesn't give any significant performance boost. Following are my results:

Hardware: iPod touch 4

Extra info: OpenGL ES 2.0. 1 sprite (64x64) having some transparency (smiley face with drop shadow). NO rotation, NO scale. (Texture is bound only once at the start in the init function). Compiled in "Release" mode.

The first column labeled 'N' shows number of sprites being tested. Second column shows FPS when sprites are batched in a huge vertex array (only 1 draw call). Third column shows FPS on multi draw calls, eg: 5000 draw calls for 5000 sprites.

Benchmark 1: Alpha blending off

Benchmark 1: Alpha blending off

Benchmark 2: Alpha blending ON

Benchmark 2: Alpha blending ON

Smiley image used:

64x64 Smiley image

Source code:

For single draw call:

Vertex hugeArray[SPRITE_COUNT*6];
GLsizei stride = sizeof(Vertex);
int drawCallCount = 0;

const GLvoid* pCoords = &hugeArray[0].Position[0];
const GLvoid* pColors = &hugeArray[0].Color[0];
const GLvoid* pTexCoords = &hugeArray[0].Texture[0];

glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);
glVertexAttribPointer(textureSlot, 2, GL_FLOAT, GL_FALSE, stride, pTexCoords);

GLsizei vertexCount = sizeof(hugeArray) / sizeof(Vertex);
glDrawArrays(GL_TRIANGLES, 0, vertexCount);
drawCallCount++;

cout << "DrawCall Count: " << drawCallCount << endl;

For multiple draw calls:

Vertex hugeArray[SPRITE_COUNT*6];
GLsizei stride = sizeof(Vertex);
int drawCallCount = 0;

for(int i = 0; i < SPRITE_COUNT; i++){

    const GLvoid* pCoords = &hugeArray[i*6].Position[0];
    const GLvoid* pColors = &hugeArray[i*6].Color[0];
    const GLvoid* pTexCoords = &hugeArray[i*6].Texture[0];

    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);
    glVertexAttribPointer(textureSlot, 2, GL_FLOAT, GL_FALSE, stride, pTexCoords);

    GLsizei vertexCount = 6;
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);
    drawCallCount++;
}
cout << "DrawCall Count: " << drawCallCount << endl;

I thought that batching sprites into a huge array resulting in a single draw call would give a HUGE performance gain but benchmarks show otherwise. Any ideas what is going on here?

Thanks for reading the question :)

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

Here's some guesses for you to experiment with:

  • Batch count is mainly a CPU load optimization, and not a GPU one. Try measuring CPU performance instead, for example by adjusting the positions so everything get clipped off screen.

  • I wouldn't be surprised if the driver realizes that each batch is actually pointing into a contiguous array and merges them all itself. D3D certainly does that in some cases.

  • Rendering exactly the same thing lots of times has different performance characteristics to rendering that number of different things, due to the cost of render state changes. It might be worth trying to measure rendering something that's a bit more realistic.

\$\endgroup\$
1
\$\begingroup\$

You actually are getting a performance increase, which is most significant around the N = 3000 mark (in the alpha blending off case). The solid 60fps for lower counts is, of course, being caused by vsync which means that your figures actually aren't valid. You'd probably do better with something like:

glFinish ();
startTime = GetTime (); // substitute with your own timing function
DrawABunchOfStuff ();
glFinish ();
endTime = GetTime (); // substitute with your own timing function
moreAccurateTime = endTime - startTime;
SwapBuffers ();

Even in this case, you're still including overhead from other parts of the pipeline; e.g. you may be fill-bound which would cause a limit on the effectiveness of batching (try drawing to a 1x1 viewport to rule this out), or you may be transform-bound which is also a fixed overhead irrespective if you batch or not (try using the most simple, primitive vertex shader possible to rule this out).

The most important point here is that batching only reduces overhead in one part of the pipeline - submitting primitives via draw calls. You're not going to get a magical and dramatic performance increase from it if you're bottlenecked elsewhere, because that other bottleneck will then become your primary limiting factor. When measuring your performance increase from batching it's important to isolate the effects of submitting draw calls so that these other bottlenecks don't skew your figures.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .