OpenGL slow cubes/voxels rendering (3M triangles at 40 fps). Seems like sending a whole scene to GPU every frame

Viewed 67

I've implemented basic textured cubes rendering engine (Minecraft style) in C++ and OpenGL. A static scene containing 3 million triangles with no lighting renders at 38 fps, 25ms frame time. It seems very slow. Also running on integrated Intel GPU is faster than dedicated Nvidia, which is a bad sign. This is my first project in graphics programming and OpenGL.

Description

Cubes are joined into chunk VAOs and those are rendered. Chunk size is 32x32x32, only visible cube faces are added to vertices/elements buffers. Empty chunks skip rendering entirely. Back face culling and texture atlas are used, other optimizations are not (and I don't want to, IMHO it should work faster even without those). Shaders are plain simple. World size is 11x11x11 chunks.

Investigation

  • Reducing scene size in cubes proportionally changes frame time - 2x faster for 2x less cubes
  • Profiling with Intel VTune shows ~95% CPU utilization in glfwSwapBuffers - rendering is a bottleneck
  • Window size and disabling textures do not affect frame time - fragment shader is not a bottleneck
  • Changing chunk size (while keeping same world size in cubes) from 32x32x32 to 16x16x16 (8x more VAOs, from 808 to 4393) does not affect frame time
  • Profiling with Nvidia Nsight Frame profiler shows 25ms of GPU in glDrawElements - looks like a problem
  • Nvidia Nsight GPU trace profiler is not available to me as it requires Turing+ GPU
  • (!) Running on an integrated Intel HD Graphics 630 is 4 fps faster than Nvidia Geforce GTX 1050 - might be a memory bottleneck as an integrated card uses PC memory which might be faster for buffer transfers.
  • Scene contains 6,393,968 vertices and 3,196,984 triangles, 5 floats per vertex (pos + uv), 3 ints per triangle = 166 MB per scene. Not that much, unless I send it every frame. But I don't do that.
  • Search shows Nvidia GTX 660 send bandwidth to be 7 GB/s, fine for an estimate, it will take me 23 ms to transfer the scene. Oops.

But I don't do this in code, what is going on and how to fix it? Is it possible that OpenGL decided to store buffers in local memory? Maybe I missed something in the docs? Maybe the problem is not with the bandwidth at all?

Materials

Loading chunk mesh (once per chunk, at init):

    materialShader.use();

    glGenBuffers(1, &verticesId);
    glBindBuffer(GL_ARRAY_BUFFER, verticesId);
    glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) meshData.verticesSize, meshData.vertices, GL_STATIC_DRAW);

    materialShader.attrib("aPos", 3, GL_FLOAT, GL_FALSE, 0, nullptr);

    glGenBuffers(1, &uvId);
    glBindBuffer(GL_ARRAY_BUFFER, uvId);
    glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr) meshData.uvsSize, meshData.uvs, GL_STATIC_DRAW);

    materialShader.attrib("aTexCoord", 2, GL_FLOAT, GL_FALSE, 0, nullptr);

    glGenBuffers(1, &elementsId);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementsId);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr) meshData.elementsSize, meshData.elements, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementsId);

* materialShader.attrib = glVertexAttribPointer; glEnableVertexAttribArray

Rendering chunk mesh (each chunk, each frame), pseudocode in correct order, actual code is abstracted:

    glUseProgram // use shader, skipped if used last
    glBindVertexArray // use vao, skipped if used last
    glBindTexture // use texture atlas, skipped if used last 
    glUniformMatrix4fv // set transform matrix in shader
    glDrawElements(GL_TRIANGLES, (GLint) elementsSize, GL_UNSIGNED_INT, nullptr) // draw chunk mesh 

Vertex shader:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 transform;

void main()
{
    gl_Position = transform * vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}

Fragment shader:

#version 330 core
out vec4 FragColor;

in vec2 TexCoord;

uniform sampler2D texture1;

void main()
{
    vec4 texColor = texture(texture1, TexCoord);
    if (texColor.a < 0.01) {
        discard;
    }
    FragColor = texColor;
}

What is also going on

During init: generating terrain with OpenGL compute shaders and SSBO.

Each frame: render debug text on the screen, not very optimized but ~1000 fps on it's own.

0 Answers
Related