Search code examples
c++opengloptimization2drenderer

Why Dear ImGui based renderer is so slow?


I have done class which render 2d objects based on Dear ImGui DrawList, because it can draw many different variants of objects thanks index vector dynamic array and still stay well optimized. Dear ImGui can render 30k unfilled rects while having ~36fps and ~70MB on debug mode, without antialiasing (my computer). Mine very limited version draws 30k unfilled rects while having ~3 fps and ~130MB on debug mode.

class Renderer
{
public:
    Renderer();
    ~Renderer();

    void Create();

    void DrawRect(float x, float y, float w, float h, GLuint color, float thickness);

    void Render(float w, float h);

    void Clear();

    void ReserveData(int numVertices, int numElements);

    void CreatePolygon(const Vector2* vertices, const GLuint verticesCount, GLuint color, float thickness);

    GLuint vao, vbo, ebo;
    GLShader shader;

    Vertex* mappedVertex = nullptr;     
    GLuint* mappedElement = nullptr,   
            currentVertexIndex = 0;

    std::vector<Vertex> vertexBuffer;  
    std::vector<GLuint> elementBuffer; 
    std::vector<Vector2> vertices;     

};

const char* vtx =
R"(

#version 460 core

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec4 a_color;

out vec3 v_position;
out vec4 v_color;

uniform mat4 projection;

void main()
{
    gl_Position = projection * vec4(a_position, 1.0);

    v_color = a_color;
}

)";

const char* frag =
R"(
#version 460 core

layout (location = 0) out vec4 outColor;

in vec4 v_color;

void main()
{
    outColor = v_color;
}
)";

void Renderer::Clear()
{
    vertexBuffer.resize(0);
    elementBuffer.resize(0);
    vertices.resize(0);
    mappedVertex = nullptr;
    mappedElement = nullptr;
    currentVertexIndex = 0;
}

void Renderer::Create()
{
    glGenBuffers(1, &vbo);
    glGenBuffers(1, &ebo);

   shader.VtxFromFile(vtx);
   shader.FragFromFile(frag);
}

void Renderer::DrawRect(float x, float y, float w, float h, GLuint color,     float thickness)
{
    // Add vertices
    vertices.push_back({ x, y });
    vertices.push_back(Vector2(x, y + w));
    vertices.push_back(Vector2( x, y ) + Vector2(w, h));
    vertices.push_back(Vector2(x + w, y));
    // Create rect
    CreatePolygon(vertices.data(), vertices.size(), color, thickness);
}

void Renderer::Render(float w, float h)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    shader.UseProgram();
    shader.UniformMatrix4fv("projection", glm::ortho(0.0f, w, 0.0f, h));

    GLuint elemCount = elementBuffer.size();

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const void*)offsetof(Vertex, position));
    glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), (const void*)offsetof(Vertex, position));

    glBufferData(GL_ARRAY_BUFFER, vertexBuffer.size() * sizeof(Vertex), vertexBuffer.data(), GL_STREAM_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBuffer.size() * sizeof(GLuint), elementBuffer.data(), GL_STREAM_DRAW);

    const unsigned short* idxBufferOffset = 0;

    glDrawElements(GL_TRIANGLES, elemCount, GL_UNSIGNED_INT, idxBufferOffset);

    idxBufferOffset += elemCount;

    glDeleteVertexArrays(1, &vao);

    glDisable(GL_BLEND);
}

void Renderer::CreatePolygon(const Vector2* vertices, const GLuint     verticesCount, GLuint color, float thickness)
{
    // To create for example unfilled rect, we have to draw 4 rects with small sizes
    // So, unfilled rect is built from 4 rects and each rect contains 4 vertices ( * 4) and 6 indices ( *6)
    ReserveData(verticesCount * 4, verticesCount * 6);

    for (GLuint i = 0; i < verticesCount; ++i)
    {
        const int j = (i + 1) == verticesCount ? 0 : i + 1;

        const Vector2& position1 = vertices[i];
        const Vector2& position2 = vertices[j];

        Vector2 difference = position2 - position1;

        difference *= difference.Magnitude() > 0 ? 1.0f / difference.Magnitude() : 1.0f;

        const float dx = difference.x * (thickness * 0.5f);
        const float dy = difference.y * (thickness * 0.5f);

        mappedVertex[0].position = Vector2(position1.x + dy, position1.y - dx);
        mappedVertex[1].position = Vector2(position2.x + dy, position2.y - dx);
        mappedVertex[2].position = Vector2(position2.x - dy, position2.y + dx);
        mappedVertex[3].position = Vector2(position1.x - dy, position1.y + dx);

        mappedVertex[0].color = color;
        mappedVertex[1].color = color;
        mappedVertex[2].color = color;
        mappedVertex[3].color = color;

        mappedVertex += 4;

        mappedElement[0] = currentVertexIndex;
        mappedElement[1] = currentVertexIndex + 1;
        mappedElement[2] = currentVertexIndex + 2;
        mappedElement[3] = currentVertexIndex + 2;
        mappedElement[4] = currentVertexIndex + 3;
        mappedElement[5] = currentVertexIndex;

        mappedElement += 6;
        currentVertexIndex += 4;
    }

    this->vertices.clear();
}

void Renderer::ReserveData(int numVertices, int numElements)
{
    currentVertexIndex = vertexBuffer.size();

    // Map vertex buffer
    int oldVertexSize = vertexBuffer.size();
    vertexBuffer.resize(oldVertexSize + numVertices);
    mappedVertex = vertexBuffer.data() + oldVertexSize;

    // Map element buffer
    int oldIndexSize = elementBuffer.size();
    elementBuffer.resize(oldIndexSize + numElements);
    mappedElement = elementBuffer.data() + oldIndexSize;
}


int main()
{
    //Create window, init opengl, etc.
    Renderer renderer;
    renderer.Create();
    bool quit=false;
    while(!quit) {
        //Events
        //Clear color bit

        renderer.Clear();

        for(int i = 0; i < 30000; ++i)
            renderer.DrawRect(100.0f, 100.0f, 50.0f, 50.0f, 0xffff0000, 1.5f);

        renderer.Render(windowW, windowH);        

        //swap buffers
    }
    return 0;
}

Why is it that much slower? How can I make it faster and less memory-consuming?


Solution

  • Solution

    1. I do not use std::vector anymore. I use ImVector instead (it maybe your own implementation as well),
    2. I set position directly to a Vector2.x/.y