How to achieve the same effect using persistent mapping instead of glBufferSubData and how to synchronize it properly?

0

I want to write 2d renderer that allow user to draw some magic number vertices per draw call. If the magic number is exceeded, renderer executes another draw call. It works very well with glBufferSubData, but not exactly want to work with persistent mapping. If I do synchronization once per every draw calls, only the last one gets rendered. If every draw calls has its own lock and unlock, rectangles are blinking every half a second.

Why it does not work the way like BufferSubData works? Is it even possible to achieve the effect I want?

Here's a small example, it tests almost the worst case, a rect per draw call.

typedef unsigned int uint32;
typedef unsigned long long uint;

struct Vertex2D
{
    Vertex2D(float x, float y, uint32 color)
    {
        position = { x, y };
        this->color = color;
    }

    Vector2<float> position;
    uint32 color;
};

struct Range
{
    uint begin = 0;

    GLsync sync = 0;
};

struct Renderer
{
    Renderer() = default;
    ~Renderer()
    {
        if (persistentMapping) {
            glUnmapNamedBuffer(vbo);
        }
        glDeleteBuffers(1, &vbo);

        glDeleteVertexArrays(1, &vao);
    }

    void Create()
    {
        shader = shader.Create("assets/shaders/renderer.glsl");

        auto mapBit = GL_MAP_WRITE_BIT | (persistentMapping ? (GL_MAP_COHERENT_BIT | GL_MAP_PERSISTENT_BIT) : 0);

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

        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);

        glNamedBufferStorage(vbo, bufferCount * quadVertexCount * sizeof(Vertex2D), nullptr, mapBit | (!persistentMapping ? GL_DYNAMIC_STORAGE_BIT : 0));

        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);

        glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(Vertex2D), (const void*)offsetof(Vertex2D, position));
        glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, true, sizeof(Vertex2D), (const void*)offsetof(Vertex2D, color));

        if (persistentMapping) {
            mappedVertex = (Vertex2D*)glMapNamedBufferRange(vbo, NULL, bufferCount * quadVertexCount * sizeof(Vertex2D), mapBit);
        }

        ranges[0].begin = 0;
        ranges[1].begin = quadVertexCount;
        ranges[2].begin = quadVertexCount * 2;
    }

    void LockBuffer(GLsync& syncObject)
    {
        if (syncObject) {
            glDeleteSync(syncObject);
        }

        syncObject = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    }

    void WaitBuffer(GLsync& syncObject)
    {
        if (syncObject) {
            while (true) {
                auto waitReturn = glClientWaitSync(syncObject, GL_SYNC_FLUSH_COMMANDS_BIT, 1);
                if (waitReturn == GL_ALREADY_SIGNALED || waitReturn == GL_CONDITION_SATISFIED)
                    return;

                waitCount++;
            }
        }
    }

    void Begin()
    {
        vertices.clear();
    }

    void FillRect(float x, float y, float w, float h, uint32 color)
    {
        vertices.push_back(Vertex2D(x, y, color));
        vertices.push_back(Vertex2D(x, y + h, color));
        vertices.push_back(Vertex2D(x + w, y + h, color));
        vertices.push_back(Vertex2D(x + w, y, color));
    }

    void Lock()
    {
        LockBuffer(ranges[rangeIndex].sync);
        rangeIndex = (rangeIndex + 1) % bufferCount;
    } 

    void RenderPresent()
    {
        if (persistentMapping) WaitBuffer(ranges[rangeIndex].sync);

        //Clear old CPU data
        Begin();
        //Fill CPU storage with new data
        FillRect(10.0f, 10.0f, 50.0f, 50.0f, 0xff00ffff);
        //pass data to the GPU and draw
        if (persistentMapping) {
            memcpy(&mappedVertex[ranges[rangeIndex].begin], vertices.data(), sizeof(Vertex2D) * vertices.size());
        }
        else glNamedBufferSubData(vbo, 0, sizeof(Vertex2D) * vertices.size(), vertices.data());
        //Draw
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

#define LOCK_ONCE_PER_DRAW_CALL 1
#if LOCK_ONCE_PER_DRAW_CALL 1
        if (persistentMapping) Lock();
        if (persistentMapping) WaitBuffer(ranges[rangeIndex].sync);
#endif

        //Clear old CPU data
        Begin();
        //Fill CPU storage with new data
        FillRect(10.0f, 70.0f, 50.0f, 50.0f, 0xffffffff);
        //pass data to the GPU and draw
        if (persistentMapping) {
            memcpy(&mappedVertex[ranges[rangeIndex].begin], vertices.data(), sizeof(Vertex2D) * vertices.size());
        }
        else glNamedBufferSubData(vbo, 0, sizeof(Vertex2D) * vertices.size(), vertices.data());
        //Draw
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

        if (persistentMapping) Lock();

    } 

    static const bool persistentMapping = true;
    static const int bufferCount = 3;
    static const int quadVertexCount = 4;
    int rangeIndex = 0;

    Range ranges[bufferCount];

    int waitCount = 0;

    Shader shader = nullptr;

    GLuint vao;
    GLuint vbo;

    Vertex2D* mappedVertex = nullptr;

    std::vector<Vertex2D> vertices;
};

int main()
{
    //create window, gl context, init opengl .. other stuff

    Renderer renderer;
    renderer.Create();

    bool quit = false;

    while(!quit) {
        glClear(GL_COLOR_BUFFER_BIT);

        renderer.RenderPresent();

        //swap buffers/windows
    }
    return 0;
}
c++
opengl
synchronization
2d
rendering
asked on Stack Overflow Nov 19, 2019 by BrodaJarek3

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0