I am writing a 2d renderer. In a real world example one draw call can handle 600 000 vertices.
When buffer is bigger than 600 000, the renderer execute another draw call and so on.
To upload vertex data I used to use glBufferSubData
and it worked perfectly fine.
Since I have watched Zero Driver Overhead Approach
video I decided to use persistent mapping with triple buffering (To test it out, to learn new technique and there may be more pros).
When actual vertex count is buffer than max per draw call and another draw call is executed there are 2 cases, each got its problem.
Example code tests basic case, 4 vertices per draw call
This is how renderer looks like:
struct Vertex2D
{
Vertex2D() = default;
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()
{
glUnmapNamedBuffer(vbo);
glDeleteBuffers(1, &vbo);
glUnmapNamedBuffer(ebo);
glDeleteBuffers(1, &ebo);
glDeleteVertexArrays(1, &vao);
}
void Create()
{
shader.Create("shader.glsl");
vertices.resize(MaxVertexCount);
elements.resize(MaxElementCount);
auto mapBit = GL_MAP_WRITE_BIT | GL_MAP_COHERENT_BIT | GL_MAP_PERSISTENT_BIT;
glCreateVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glNamedBufferStorage(vbo, BufferCount * MaxVertexCount * sizeof(Vertex2D), nullptr, mapBit);
mappedVertex = (Vertex2D*)glMapNamedBufferRange(vbo, NULL, BufferCount * MaxVertexCount * sizeof(Vertex2D), mapBit);
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));
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glNamedBufferStorage(ebo, BufferCount * MaxElementCount * sizeof(uint32), nullptr, mapBit);
mappedElements = (uint32*)glMapNamedBufferRange(ebo, NULL, BufferCount * MaxElementCount * sizeof(uint32), mapBit);
vertexRanges[0].begin = 0;
vertexRanges[1].begin = MaxVertexCount;
vertexRanges[2].begin = MaxVertexCount * 2;
elementsRanges[0].begin = 0;
elementsRanges[1].begin = MaxElementCount;
elementsRanges[2].begin = MaxElementCount * 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;
}
}
}
void FillRect(float x, float y, float w, float h, uint32 color)
{
vertices[0] = Vertex2D(x, y, color);
vertices[1] = Vertex2D(x, y + h, color);
vertices[2] = Vertex2D(x + w, y + h, color);
vertices[3] = Vertex2D(x + w, y, color);
elements[0] = 0;
elements[1] = 1;
elements[2] = 2;
elements[3] = 2;
elements[4] = 3;
elements[5] = 0;
}
void Lock()
{
WaitBuffer(vertexRanges[rangeIndex].sync);
WaitBuffer(elementsRanges[rangeIndex].sync);
}
void RenderPresent()
{
Lock();
memcpy(&mappedVertex[vertexRanges[rangeIndex].begin], vertices.data(), (sizeof(Vertex2D) * vertices.size()));
memcpy(&mappedElements[elementsRanges[rangeIndex].begin], elements.data(), (sizeof(MiEuint32) * elements.size()));
glDrawElements(GL_TRIANGLES, elements.size(), GL_UNSIGNED_INT, nullptr);
Unlock();
}
void Unlock()
{
LockBuffer(vertexRanges[rangeIndex].sync);
LockBuffer(elementsRanges[rangeIndex].sync);
rangeIndex = (rangeIndex + 1) % BufferCount;
}
enum
{
BufferCount = 3,
MaxVertexCount = 4,
MaxElementCount = (MaxVertexCount - 2) * 3
};
int rangeIndex = 0;
Range vertexRanges[BufferCount];
Range elementsRanges[BufferCount];
Shader shader;
GLuint vao;
GLuint vbo;
GLuint ebo;
Vertex2D* mappedVertex = nullptr;
uint32* mappedElements = nullptr;
std::vector<Vertex2D> vertices;
std::vector<uint32> elements;
};
int main()
{
//Create window, gl context, init gl, etc. etc.
Renderer renderer;
while(!quit) {
glClear(GL_COLOR_BUFFER_BIT);
renderer.FillRect(10.0f, 70.0f, 50.0f, 50.0f, 0xffffffff);
renderer.RenderPresent();
//Pretend we are out of max vertex count range, allocate another draw call with render present;
renderer.FillRect(70.0f, 70.0f, 50.0f, 50.0f, 0xff0000ff);
renderer.RenderPresent();
//swap windows
}
}
Lock()
and Unlock()
in RenderPresent();
and add Lock()
and Unlock()
in main loop.void RenderPresent()
{
//Lock();
memcpy(&mappedVertex[vertexRanges[rangeIndex].begin], vertices.data(), (sizeof(Vertex2D) * vertices.size()));
memcpy(&mappedElements[elementsRanges[rangeIndex].begin], elements.data(), (sizeof(MiEuint32) * elements.size()));
glDrawElements(GL_TRIANGLES, elements.size(), GL_UNSIGNED_INT, nullptr);
//Unlock();
}
//---------------
while(!quit) {
glClear(GL_COLOR_BUFFER_BIT);
renderer.Lock();
renderer.FillRect(10.0f, 70.0f, 50.0f, 50.0f, 0xffffffff);
renderer.RenderPresent();
//Pretend we are out of max vertex count range, allocate another draw call with render present;
renderer.FillRect(70.0f, 70.0f, 50.0f, 50.0f, 0xff0000ff);
renderer.RenderPresent();
renderer.Unlock();
//swap windows
}
Why those problems occur? How to fix it?
User contributions licensed under CC BY-SA 3.0