0
\$\begingroup\$

I'm creating a game in modern OpenGL, C++ but I'm having problems with optimization. Whenever I render the sprites the CPU usage starts to increase.

Code:

void GameController::Render() {
    shaders.Use();
    glBindVertexArray(vao);

    for (const auto& layers : chuncks) {
        for (const auto& layer : layers) {
            const bool isVisible =
                (layer.position.x + layer.size.x >= camera->pixelRect.min().x) &&
                (layer.position.x - layer.size.x <= camera->pixelRect.max().x) &&
                (layer.position.y + layer.size.y >= camera->pixelRect.min().y) &&
                (layer.position.y - layer.size.y <= camera->pixelRect.max().y);

            if (isVisible) {
                textures[layer.id - 1]->Usage(GL_TEXTURE0);

                shaders.SetUniform("textureSampler", 0);
                shaders.SetUniform("projection", camera->projectionMatrix);
                shaders.SetUniform("translate", layer.position);
                shaders.SetUniform("scale", layer.size);

                glDrawArrays(GL_QUADS, 0, 4);

                textures[layer.id - 1]->Unused();
            }
        }
    }
    glBindVertexArray(0);
    shaders.Unuse();
}

CPU usage is starting to get unusually high. I've thought about implementing separate threads, one for updating and one for rendering, but I don't know if it will be efficient.

\$\endgroup\$
2
  • \$\begingroup\$ It looks like you're not doing any batching here, just one draw call per single quad? Is it possible for a whole chunk to be off screen, or does the chunks collection contain only visible chunks? \$\endgroup\$
    – DMGregory
    Nov 11, 2023 at 12:22
  • 1
    \$\begingroup\$ What are you doing to measure CPU usage? \$\endgroup\$
    – Adam
    Nov 11, 2023 at 20:29

1 Answer 1

3
\$\begingroup\$

Without refactoring the code the first thing would be to avoid repeatedly setting uniforms that don't change such as:

shaders.SetUniform("textureSampler", 0);
shaders.SetUniform("projection", camera->projectionMatrix);

And put those outside the loop after the shader is selected.


But to really fix the performance issue you need to write all sprites to a VBO rather than issue a draw call for each single sprite, either pre-calculate the position & scale or write them as vertex attribute for the vertex shader to calculate, and let the GPU deal with clipping.

You will also need to put all your individual sprite textures into a single texture atlas (or multiple if they won't fit) you can also use texture arrays for more options https://www.khronos.org/opengl/wiki/Array_Texture

If you have under 100 million quads you can easily let a modern GPU clip them out and still reach 60 fps. Entirely deleting the isVisible check. Frustum culling is only worth doing on the CPU for entire level section batches for 2D sprites.

You can use Instancing to write the position and scale to a separate VBO that will repeat 4 (or 6 for 2 triangles) times so you only need to write each value once.

glVertexAttribDivisor(x, 6);  

This way you can reduce the amount of data transferred to the GPU each frame by making the updated VBO smaller.

See the official OpenGL site for more : https://www.khronos.org/opengl/wiki/Vertex_Specification

glDrawElementsInstanced - https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDrawElementsInstanced.xhtml

glDrawArraysInstanced - https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDrawArraysInstanced.xhtml

These functions are specifically meant to draw multiple copies of identical (or similar) objects efficiently.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your help, I will study what you told me and update the code \$\endgroup\$ Nov 19, 2023 at 14:13

You must log in to answer this question.

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