23
\$\begingroup\$

While refreshing my mind on OpenGL ES, I came across glDrawArrays and glDrawElements.

I understand how they are used and sort of understand why they are different.

What I do not seem to understand is that, I fail to see how glDrawElements can save draw calls (saving draw calls is a description that is mentioned by most of the books I have read, hence my mentioning of it here).

Imagine a simple scenario in which I tried to draw a square using 2 triangles.

I would need to have a set of 6 vertices using glDrawArrays while using glDrawElements, I would need only 4 in addition to an index array that has 6 elements.

Given the above, here is what I do not understand:

  1. how could glDrawElements save draw calls, if it still needs to use the index array (6 indices, in case of a square) to index into my vertex array that had 4 elements (6 times)? Other words, does it mean glDrawElements would still need to have a total of 6 draw calls just like glDrawArrays?
  2. how would using glDrawElements save space, if one would still need to have 2 arrays, namely, one for the vertices and one for the indices?
  3. In the case of drawing a square from 2 triangles, for simplicity, how many draw calls does glDrawElements (4 items in vertex array and 6 items in index array) and glDrawArrays (6 items only in vertex array) need, individually?

Thanks.

\$\endgroup\$

2 Answers 2

27
\$\begingroup\$

Each glDraw* is a draw call.

1 glDrawArrays is 1 draw call.
1 glDrawElements is 1 draw call.

It doesn't matter (so far as draw call count is concerned) how many vertices or indices you use, 1 glDraw* is 1 draw call.

The simple cases of drawing quads (assuming the GL version you use hasn't removed quads) or triangles are not a good example for comparing these, because a list of quads or a list of triangles can be drawn with a single call to either, and glDrawArrays will appear more efficient because it doesn't have the additional overhead of indexing.

Let's take a slightly more complex case, but one which is representative of a more real-world scenario. Imagine that you have a model composed of multiple triangle strips and fans:

glDrawArrays (GL_TRIANGLE_FAN, .....);
glDrawArrays (GL_TRIANGLE_STRIP, .....);
glDrawArrays (GL_TRIANGLE_STRIP, .....);
glDrawArrays (GL_TRIANGLE_FAN, .....);
glDrawArrays (GL_TRIANGLE_STRIP, .....);
glDrawArrays (GL_TRIANGLE_FAN, .....);

In this example, using glDrawArrays gives you a total of 6 draw calls for the model. However, both strips and fans can easily be converted to indexed triangles, so add indices and it becomes:

glDrawElements (GL_TRIANGLES, .....);

That's one draw call, for the entire model.


The advantages of glDrawElements over glDrawArrays are more than just memory saving, which is the naive way of measuring it. There is potential for memory saving where vertices can be reused, because an index is typically smaller than a vertex (so even if you have lots of indices on balance they'll be smaller), but other advantages include:

  • Reduced draw call count. Each draw call incurs some overhead from validating state, setting things up, etc, and much of this overhead happens on the CPU side. By reducing the number of draw calls we avoid much of this overhead.

  • Vertex reuse. This is much more than just memory-saving. Your GPU likely has a hardware vertex cache, where recently transformed vertices can be stored; if the same vertex comes in again, and if it's in the cache, it can be reused from the cache rather than having to transform again. Your hardware will check the cache by comparing indices, so in OpenGL terms the only way you get to use the cache is to use glDrawElements.


So to answer your specific questions:

  1. how could glDrawElements save draw calls...? In the example you use, it doesn't. Each has one draw call. As I discuss above, this example is a very bad one for comparing the two.

  2. how would using glDrawElements save space...? Because indices are smaller than vertices. A 16-bit index is two bytes, a position/color/texcoord vertex is 24 bytes. 6 vertices is 144 bytes, 4 vertices plus 6 indices is 108 bytes.

  3. In the case of drawing a square from 2 triangles...? One and one. A glDraw* call is one draw call, the number of vertices or indices used is irrelevant for this measurement. But I must re-emphasise, this is a very bad example for comparing the two.


Finally, and just to complicate matters a little, while glDrawElements with triangles is the optimal path on desktop GPUs, on mobile it might be very different. Some mobile GPUs might prefer glDrawArrays with GL_TRIANGLE_STRIP (you would add degenerate triangles to concatenate primitives in this case), and I haven't even touched on more advanced topics such as primitive restart or multi-draw.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks so much for your feedback; I am just taking a little time reading what you have shared. Just wanted to let you know I acknowledged your response. Thanks again. \$\endgroup\$
    – Unheilig
    Nov 19, 2016 at 2:26
  • \$\begingroup\$ In your example of converting 6 DrawTriangleFans and DrawTriangleStrips calls to 1 single DrawTriangles calls. Does this really improve some performance? To my understanding drawing a triangle strip comprised of 100 triangles is much more efficient than drawing 100 triangles individually, and the benefit would easily offset any penalty occurred from using 6 function calls rather than 1. \$\endgroup\$
    – Samuel Li
    Mar 4, 2019 at 19:15
  • \$\begingroup\$ @SamuelLi 6-to-1 won't be a huge improvement, it's merely intended to illustrate the principle. Also, and as I indicated, strips are typically not a performance win over indexed lists on post-1998 desktop hardware. Check the date on any document advising you to use strips. \$\endgroup\$ Mar 4, 2019 at 21:14
  • \$\begingroup\$ Got your point, thanks for clarification! @MaximusMinimus \$\endgroup\$
    – Samuel Li
    Mar 4, 2019 at 21:21
1
\$\begingroup\$

When you use glDrawArrays you have to choose an indexing pattern, one of GL_TRIANGLES, GL_TRIANGLE_STRIP or GL_TRIANGLE_FAN. This determines the indexing pattern into your buffers for the whole draw call (single call to glDrawArrays). If you choose GL_TRIANGLES the indexing pattern means you must provide data independently for each vertex, even if that vertex is "shared" with another triangle. More importantly, for the same point location shared by two different triangles, you can have an different normal vector. So it would be possible to shade that triangle visibly as an independent triangle, if you were going for a low-poly look for example.

With GL_TRIANGLE_STRIP or GL_TRIANGLE_FAN you are sharing vertex and normal data between triangles, and for normals this means you get smooth shading - you could not create that hard edge between the triangles if you wanted that.

See the indexing patterns for those 3 constants described here.

These indexing patterns used for glDrawArrays are fixed. This means you need to order your vertexes so that these indexes refer to your vertexes in counter-clockwise order (by default, but configurable in OpenGL to clockwise order using glFrontFace). Even if your normals are facing the right way, OpenGL may not draw your triangles if they are not wound the right way. This is called "face culling" and is also configurable. It's disabled by default, and can be set to front or back, but it's encouraged to enable it because it can save useless calls into your shaders. It's complicated because what gets culled is a combination of the order of your vertexes in your vertex buffer, the winding order you set with glFrontFace and the value you set for glCullFace.

With glDrawElements you provide a buffer of indexes to use. This gives you full control of the order that your vertexes are referenced in order to construct a triangle. So your vertexes could be ordered clockwise in your vertex buffer, but you could use an index pattern that constructs your triangle in counter-clockwise order. More importantly, it allows you to be more flexible:

For example, imagine you want a flat shading effect but you want this to be quad-based - each quad should be shaded visibly with hard edges around the quad, but you don't need a visible edge in the diagonal across the quad. You ideally want to only specify 4 vertexes and 4 (identical) normals for each quad. If you choose glDrawArrays with GL_TRIANGLES you would be forced to store 6 vertexes and 6 normals per quad. If you choose GL_TRIANGLE_STRIP you would only need to store 2 vertexes and 2 normals per quad (plus two extra at the start of the strip), but the normals would be shared between quads so you would get smooth shading on the edges between the quads. But if you use glDrawElements you can use an index pattern where the vertexes and normals on each end of the diagonal are shared with the triangles on either side of the diagonal, but vertexes and normals are not shared between each quad.

With glDrawArrays you are choosing an indexing pattern that applies for the whole draw call. However, with glDrawElements you can specify a mixed indexing pattern within the single draw call. So you could have the first X triangles with flat shading, some more triangles with smooth shading, some quads with flat shading, etc - all with a single call to glDrawElements. However, you have to generate, store and load that index buffer onto the GPU, which adds extra time and takes extra space.

\$\endgroup\$

You must log in to answer this question.

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