PDA

View Full Version : Optimizing rendering of rectangles



Kozersky
10-07-2015, 07:39 AM
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.

GClements
10-07-2015, 09:03 AM
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.

Kozersky
10-07-2015, 10:52 AM
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?

hidefromkgb
10-08-2015, 04:27 AM
Just a side note.


const GLushort indices[] =
{
0, 1, 2, // first triangle
3, 3, 2, // second triangle
};
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.

Kozersky
10-08-2015, 05:43 AM
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.

hidefromkgb
10-09-2015, 01:54 AM
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.

Kozersky
10-09-2015, 04:30 AM
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.

hidefromkgb
10-09-2015, 02:17 PM
* don't store colors in array, calculate them in vertex shaderAccording to what I`ve seen in the code above, the color doesn`t need calculation since it`s static, and that gives you many options: you may pass it via uniform, you may use glDrawElementsInstanced (https://www.opengl.org/sdk/docs/man/html/glDrawElementsInstanced.xhtml) (more on that below), and of course you may just hardcode the color into your shader.


* use *_STRIP versionsNot sure if this is relevant to your task.


* place vertices and indices in VBOCertainly 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 (https://www.opengl.org/wiki/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.

GClements
10-09-2015, 02:36 PM
One of these is to use a buffer texture (https://www.opengl.org/wiki/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.
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).

hidefromkgb
10-09-2015, 02:46 PM
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?

GClements
10-09-2015, 04:41 PM
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.