Piszę system renderowania grafiki 2D. Jedno polecenie rysowania może narysować 600 000 wierzchołków. Kiedy ta liczba zostanie przekroczona, następne wierzchołki są rysowane przed drugie polecenie rysowania (które właśnie dodaliśmy).
Działa to sprawnie z glBufferSubData
.
Chciałbym jednak użyć do tego bufora, który jest trwale zmapowany.
Aktualnie mam 2 przypadki, które mają swoje problemy:
- synchronizacja na każde polecenie rysowania - geometria się renderuje, ale mruga (tylko gdy jest więcej niż 1 polecenie rysowania. Jeśli jest tylko 1 polecenie, wszystko działa jak natura chciała),
- synchronizacja raz na wszystkie polecenie rysowania - tylko geometria z ostatniego polecenia rysowania jest renderowana.
Minimalny przykład pokazuje bardzo prosty przypadek, 4 wierzchołki na 1 polecenie rysowania
Wygląda to tak:
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;
};
Rozkładając to na podane wyżej przypadki:
- synchronizacja osobno dla każdego polecenia rysowania - prostokąty migają
int main()
{
//stwórz window, gl context, init gl, itd. itp.
Renderer renderer;
while(!quit) {
glClear(GL_COLOR_BUFFER_BIT);
renderer.FillRect(10.0f, 70.0f, 50.0f, 50.0f, 0xffffffff);
renderer.RenderPresent();
//Udajemy, że nie ma miejsca już w buforze wierzchołków i wywołujemy drugie polecenie rysowania
renderer.FillRect(70.0f, 70.0f, 50.0f, 50.0f, 0xff0000ff);
renderer.RenderPresent();
//swap windows
}
}
- drugi przypadek - sychronizacja raz dla wszystkich poleceń rysowania - tylko ostatnie polecenie rysowania renderuje prostokąty
Musimy skomentowaćLock()
iUnlock()
w metodzieRenderPresent()
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();
}
I do maina dodać Renderer::Lock
i Renderer::Unlock
while(!quit) {
glClear(GL_COLOR_BUFFER_BIT);
renderer.Lock();
renderer.FillRect(10.0f, 70.0f, 50.0f, 50.0f, 0xffffffff);
renderer.RenderPresent();
//Udajemy, że nie ma miejsca już w buforze wierzchołków i wywołujemy drugie polecenie rysowania
renderer.FillRect(70.0f, 70.0f, 50.0f, 50.0f, 0xff0000ff);
renderer.RenderPresent();
renderer.Unlock();
//swap windows
}
Dlaczego takie problemy się pojawiają? Jak je naprawić?