Optimizing rendering of rectangles

I need to draw couple of millions of rectangles, and I would like to get some advice on optimization, because currently it gets very slow (can notice lag when starting, zooming or dragging). The code is as follows:


    const GLfloat vertexes[] =
    {
        x,              y,              0.0,
        x + x_delta,    y,              0.0,
        x,              y + y_delta,    0.0,
        x + x_delta,    y + y_delta,    0.0,
    };

    int vertexLocation = m_program.attributeLocation("aVertex");
    m_program.enableAttributeArray(vertexLocation);
    m_program.setAttributeArray(vertexLocation, GL_FLOAT, vertexes, 3);

    const GLfloat colors[] =
    {
        0.2, 0.6, 1.0, 0.3,
        0.2, 0.6, 1.0, 0.3,
        0.2, 0.6, 1.0, 0.3,
        0.2, 0.6, 1.0, 0.3,
    };
    int colorLocation = m_program.attributeLocation("aColor");
    m_program.enableAttributeArray(colorLocation);
    m_program.setAttributeArray(colorLocation, GL_FLOAT, colors, 4);

    const GLushort indices[] =
    {
        0, 1, 2, // first triangle
        3, 3, 2, // second triangle
    };

    glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(indices[0]), GL_UNSIGNED_SHORT, indices);

    m_program.disableAttributeArray( vertexLocation );
    m_program.disableAttributeArray( colorLocation );

These rectangles are actually very small and they appear as lines (code for drawing lines is almost the same), so to certain zoom level I draw only lines, still is very slow. I am not sure if such performance is ok when drawing so many of rectangles. Maybe I am expecting too much, or there is a mistake in code. Anyway, I would like to hear some hints on performance optimization.

Already using glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE);, drawing only in 2D. All rectangles are of unique dimensions.

Thanks you in advance.

Calling glDrawElements() once per rectangle or line is very inefficient.

Store the data for all of the rectangles (or lines) in one set of arrays and draw them all with a single glDrawElements() call.

Ok, code has been changed to (called only once):


    int vertexLocation = m_program.attributeLocation("aVertex");
    m_program.enableAttributeArray(vertexLocation);
    m_program.setAttributeArray(vertexLocation, m_Vertexes.data());

    int colorLocation = m_program.attributeLocation("aColor");
    m_program.enableAttributeArray(colorLocation);
    m_program.setAttributeArray(colorLocation, m_Colors.data());

    glDrawElements(GL_TRIANGLES, m_Indices.size(), GL_UNSIGNED_SHORT, m_Indices.data());

    m_program.disableAttributeArray( vertexLocation );
    m_program.disableAttributeArray( colorLocation );

And m_Vertextes, m_Colors, m_Indices are as follows (called every time data should be populated):


    m_Vertexes[m_VertexesCounter++] = (QVector3D(static_cast<float>(x), static_cast<float>(y), 0.0));
    ...
    m_Colors[m_ColorsCounter++] = (QVector4D(0.5f, 0.5f, 0.5f, 1.0f));
    ...
    m_Indices[m_IndicesCounter++] = 0;
    ...

It works faster, though I don"t see rectangles now. What can I be missing?

Just a side note.[QUOTE=Kozersky;1279713]


    const GLushort indices[] =
    {
        0, 1, 2, // first triangle
        3, 3, 2, // second triangle
    };

[/QUOTE]Is this really what you want from the second triangle? 3, 3, 2 looks like a degenerate triangle which may or may not look like a line, depending on renderer.

Ah, sorry, that was a typo, code in fact looks like this:


    const GLushort indices[] =
    {
        0, 1, 2, // first triangle
        1, 3, 2, // second triangle
    };

But thanks for noticing.

If I understand correctly, your task is quite similar to mine, and the technique you may need is per-polygon (per-rect in your case) attributes.
What version of OpenGL do you intend to use? The ease of use of what I`m going to describe largely depends on whether GL 3.0++ is available.

[UPD:] However, in case your geometry doesn`t change each frame, a much simpler and 3.0-independent approach is possible.

I intend to use latest OpenGL version, this is a new project.

PS: there is still a problem:

It works faster, though I don"t see rectangles now. What can I be missing?

I have also found some information regarding optimization, posting it here, hopefully someone can comment/find it useful:

  • don’t store colors in array, calculate them in vertex shader;
  • use *_STRIP versions;
  • place vertices and indices in VBO.

[QUOTE=Kozersky;1279746] * don’t store colors in array, calculate them in vertex shader[/QUOTE]According to what Ive seen in the code above, the color doesnt need calculation since it`s static, and that gives you many options: you may pass it via uniform, you may use glDrawElementsInstanced (more on that below), and of course you may just hardcode the color into your shader.

[QUOTE=Kozersky;1279746] * use *_STRIP versions[/QUOTE]Not sure if this is relevant to your task.

[QUOTE=Kozersky;1279746] * place vertices and indices in VBO[/QUOTE]Certainly the most useful advice.
If your scene has static geometry, you may upload it to the VRAM during initialization and then just tell the GPU to use it over and over each frame. If not, there are still many other approaches.
One of these is to use a buffer texture to store dimensions for rectangles, one pixel for one rectangle: R = horizontal pos, G = vertical pos, B = width, A = height. Then, a single-rect VBO is created, with unit square dimensions and the desired color, and finally, glDrawElementsInstanced is called, with the number of copies equalling the number of rectangles desired.
Inside the vertex shader, gl_InstanceID-th element of the buffer texture is read and proper dimensions are computed for a given rectangle.

[QUOTE=hidefromkgb;1279749]One of these is to use a buffer texture to store dimensions for rectangles, one pixel for one rectangle: R = horizontal pos, G = vertical pos, B = width, A = height. Then, a single-rect VBO is created, with unit square dimensions and the desired color, and finally, glDrawElementsInstanced is called, with the number of copies equalling the number of rectangles desired.
Inside the vertex shader, gl_InstanceID-th element of the buffer texture is read and proper dimensions are computed for a given rectangle.[/QUOTE]
There’s no reason to use a buffer texture if you’re using instancing. You can just use an instanced vertex attribute, which will be more efficient than a texture lookup.

Buffer textures can be used to achieve the same effect as instancing without actually using instancing, e.g. because you need to support OpenGL versions which lack instancing, or because the overhead of using instancing turns out to be worse than the overhead of a texture lookup (which may be the case here; you’d have to test and compare).

GClements, granted, I forgot that part about IVAs =(
By the way, does the GPU really issue a «true» lookup when dealing with a buffer texture?

That would depend upon what you mean by “true”. But AIUI, a fetch from a buffer texture would commence at the point that the shader executes the texelFetch() call, whereas attribute values should be copied to the processing core’s local storage prior to the shader’s execution.