user5024425
user5024425

Reputation: 427

Currently fastest way to draw a large number of quads with OpenGL ES 3.1?

Good evening, following scenario:

I'm implementing a label rendering algorithm. The input data consists of a number of strings and a texture atlas that has texture data for every possible string glyph. Every string is supposed to generate a number of quads, one for every instance of every glyph, whose texture coordinates reference the texture atlas.

The only thing that can change between the frames is the on screen position of the labels and their glyphs. So, my first thought was to create a huge buffer of quads that make up the glyphs in local coordinates, then just read the position from a separate buffer that I update every frame.

Now my question: What would be the currently fastest method to do this in OpenGL ES 3.1? My first thought was using glDrawElements with GL_TRIANGLE_STRIP using a primitive restart index, then convert the index to some entry in my buffer inside the vertex shader and read the position from that.

However, I wonder if a faster method has popped up in the meantime.

Thank you in advance!

Upvotes: 0

Views: 1596

Answers (2)

Nicholas Hall
Nicholas Hall

Reputation: 11

I've written a small sprite shader which is a clone of the sprite shader from here http://learnopengl.com/#!In-Practice/2D-Game/Rendering-Sprites

The only difference between straightforward OpenGL and OpenGL ES 3.1 would be a few lines in the shaders. You need to change the version in both shaders from:

#version 330 core

to

#version 300 es

And, to the fragment shader, add the line

precision mediump float;

somewhere near the top.

Then, assuming your shader has a model and projection matrix uniforms, you just use a 2d vector position to create a 4d vector, substituting 0.0 and 1.0 for the last two parameters in your vertex shader:

gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0);

I feel this is explained better in Joey's LearnOpenGL tutorials than I can explain in detail here.


Your draw function might look something like this (if you use C++11):

mShader( [ & ] ( ) // Same as glUseProgram( shaderID ) but automatically un-Uses it when it goes out of scope
{
    glm::mat4 model;
    model = glm::translate( model, glm::vec3( xPosition, 0.0f ) );
    model = glm::translate( model, glm::vec3( 0.5f * xSize.x, 0.5f * xSize.y, 0.0f ) );
    model = glm::rotate(    model, xRotate, glm::vec3( 0.0f, 0.0f, 1.0f ) );
    model = glm::translate( model, glm::vec3( -0.5f * xSize.x, -0.5f * xSize.y, 0.0f ) );
    model = glm::scale(     model, glm::vec3( xSize, 1.0f ) );

    mShader.getUniforms( ).setUniformMatrix4fv( "model", model );
    mShader.getUniforms( ).setUniformVector3f( "spriteColor", xColor );
    glActiveTexture( GL_TEXTURE0 );

     xTexture( [ & ]( ) // Does the same as glBindTexture but automatically unbinds when out of scope
     {
        glBindVertexArrayOES( mQuadVAO );
        glDrawArrays( GL_TRIANGLES, 0, 6 );
        glBindVertexArrayOES( 0 );
    } );
} );

The only difference in my implementation from the tutorial that is I use SDL to get an OpenGL ES 3.0 context (for portability and other functions, such as keyboard, mouse). I can confirm that Joey's tutorial runs on Android, embedded Linux Intel integrated graphics and Windows with the AMD GLES3 SDK with just the few minor changes I mentioned above.

You might want to augment the implementation by using a PBO; an OpenGL version is in the same repo.

It uses PBOs. This can be used to update the quad textures more rapidly than glTexImage2D. I don't know if PBOs are supported in OpenGL ES but they might be by extension. If you just need to load a bunch of quads ahead of time and render them, you don't need to go that far.

To update the quad in the straightforward way, it's just:

glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE );
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, mW, mH, GL_RGBA, GL_UNSIGNED_BYTE, xData );

Using the PBOs, you can use two PBOs to enable a "double buffering feature" where you clear a PBO and map the other PBO each turn. See the OpenGL ES specifications for glBindBufferARB, glBufferDataARB, glMapBufferARB, glUnmapBufferARB for more information. Apparently, as of 3.0, OpenGL ES does support PBOs so if you need to update the textures rapidly, this is the way to go.

Upvotes: 1

codetiger
codetiger

Reputation: 2779

OpenGL ES based devices are mostly limited on number of draw calls for each frame. So you need to batch all triangles of every character into one single mesh and draw it once. This will give you the best performance in most cases.

  • Batching all vertices into one mesh
  • Construct the mesh with indexed triangles and use VAO
  • Add an extra index in vertex attribute to represent the index of character.
  • Pass the Model matrix of each character as uniform array.
  • In Vertex Shader, multiply the vertex position with model matrix of that index.

There are some limitations in this method.

  • The number of characters you can batch depends on the hardware. (The size of buffer allowed for one shader at a time.)
  • If the number of vertex is very huge, the you might easily hit a bottleneck in vertex shader.

Upvotes: 0

Related Questions