Context: I am currently working on a 2D engine in Opengl 4.6 and C++. Part of the project is a large batch of sprites getting rendered in different batches, my current approach is using a combination of glBufferData and glBufferSubData to accomplish this. Recently been working on implementing something similar using persistent data mapping with glBufferStorage. However I have only been able to draw the first batch all consecutive batches don't get drawn and am a bit floored as to the reason why.
Code:
Renderer initialization
struct Renderer
{
Renderer(std::vector<GLuint> channel_sizes, GLuint indices_size, std::vector<GLuint> attribute_sizes)
: current_material(nullptr), indices_size(indices_size), attribute_size(0), quad_count(0)
{
for (auto att : attribute_sizes)
attribute_size += att;
vertex_size = attribute_size * indices_size;
auto max_sprites = 0;
for (auto size : channel_sizes)
{
channels.push_back(max_sprites * vertex_size);
max_sprites += size;
}
channels.push_back(max_sprites * vertex_size);
this->channels = channels;
this->curr_channel = this->channels.begin();
this->next_channel = this->curr_channel + 1;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
// Memory mapping flags
GLbitfield fMap = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
// Buffer creation flags
GLbitfield fCreate = fMap | GL_DYNAMIC_STORAGE_BIT;
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// Initialize and allocate buffer object
glBufferStorage(GL_ARRAY_BUFFER, (GLsizeiptr)(max_sprites * vertex_size), nullptr, fCreate);
// Map from gpu to local
buffer = (float*)glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)(max_sprites * vertex_size), fMap);
buffer_curr = buffer;
auto stride = 0ull;
for (auto i = 0; i < attribute_sizes.size(); ++i)
{
glEnableVertexAttribArray(i);
glVertexAttribPointer(i, attribute_sizes[i], GL_FLOAT, GL_FALSE, attribute_size * sizeof(float), (GLvoid*)stride);
stride += attribute_sizes[i] * sizeof(float);
}
}
~Renderer()
{
// delete allocated buffers
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &vao);
}
GLuint max_sprites,
indices_size,
vertex_size,
attribute_size,
quad_count;
std::vector<GLuint> channels;
std::vector<GLuint>::iterator curr_channel;
std::vector<GLuint>::iterator next_channel;
float* buffer;
float* buffer_curr;
GLuint vbo;
GLuint vao;
Material* current_material;
};
void flush(Renderer* renderer)
{
if (!renderer->quad_count) return;
if (!renderer->current_material)
{
renderer->quad_count = 0;
renderer->buffer_curr = renderer->buffer;
return;
}
// bind texture
renderer->current_material->mat->bind(renderer->current_material->texture);
// set uniforms and use shader
renderer->current_material->mat->compile(renderer->current_material->shader);
auto total_draws = (GLsizei)(renderer->quad_count * renderer->indices_size);
auto first_pos = *renderer->curr_channel / renderer->attribute_size;
// draw triangles
glDrawArrays(GL_TRIANGLES, first_pos, total_draws);
renderer->curr_channel++;
renderer->buffer_curr = &renderer->buffer[*renderer->curr_channel];
renderer->next_channel++;
renderer->quad_count = 0;
}
void draw(Renderer* renderer, Src* src, Dest* dest, Material* material)
{
if (renderer->next_channel == renderer->channels.end())
{
std::cerr << "not enough space to render batch: " << material->batch_id << std::endl;
return;
}
if ((renderer->quad_count * renderer->vertex_size >= *renderer->next_channel || !renderer->current_material) || material->hash != renderer->current_material->hash)
{
pflush(renderer);
renderer->current_material = material;
}
auto& buffer = renderer->buffer_curr;
// first triangle
*buffer++ = dest->dest.x;
*buffer++ = dest->dest.w;
*buffer++ = src->src.x;
*buffer++ = src->src.w;
*buffer++ = dest->dest.z;
*buffer++ = dest->dest.y;
*buffer++ = src->src.z;
*buffer++ = src->src.y;
*buffer++ = dest->dest.x;
*buffer++ = dest->dest.y;
*buffer++ = src->src.x;
*buffer++ = src->src.y;
// second triangle
*buffer++ = dest->dest.x;
*buffer++ = dest->dest.w;
*buffer++ = src->src.x;
*buffer++ = src->src.w;
*buffer++ = dest->dest.z;
*buffer++ = dest->dest.w;
*buffer++ = src->src.z;
*buffer++ = src->src.w;
*buffer++ = dest->dest.z;
*buffer++ = dest->dest.y;
*buffer++ = src->src.z;
*buffer++ = src->src.y;
++renderer->quad_count;
}
/*
dest for sprites. with layer
eg:
p1
*_______
| |
| |
| |
-------*
p2
dest = {p1.x, p1.y, p2.x, p2.y}
*/
struct Dest
{
Dest(glm::vec4 dest)
: dest(dest)
{}
Dest()
: dest()
{}
glm::vec4 dest;
};
struct Src : Component
{
Src()
: src(0.0f)
{}
Src(glm::vec4 src)
: src(src)
{}
glm::vec4 src;
};
struct Material
{
Material(GLint tex_unit)
: image(tex_unit)
{}
GLint image;
void compile(Shader* shader) override
{
glUseProgram(shader->id);
glUniform1i(glGetUniformLocation(shader->id, "image"), image);
}
void bind(Texture* tex)
{
glActiveTexture(GL_TEXTURE0 + image);
glBindTexture(GL_TEXTURE_2D, tex->id);
}
};
main function (renders first 64 and not the next 64)
int main()
{
// init gl/glad/glfw/etc...
// load some textures and shaders...
auto Texture1 = new Texture("tex src");
auto Shader1 = new Shader("...vertex shader", "...fragment shader" )
auto Texture2 = new Texture("tex src");
auto Shader2 = new Shader("...vertex shader", "...fragment shader" )
auto projection = glm::ortho(0.0f, static_cast<GLfloat>(800),
static_cast<GLfloat>(600), 0.0f, -1.0f, 1.0f);
glUniformMatrix4fv(glGetUniformLocation(Shader1->id, "projection"), 1, GL_FALSE, projection);
glUniformMatrix4fv(glGetUniformLocation(Shader2->id, "projection"), 1, GL_FALSE, projection);
auto material1 = new Material(0);
auto material2 = new Material(1);
auto src = new Src({0,0,1,1});
auto Dest dests[] = new Dest()[128];
for(auto i = 0; i < 128; ++i)
{
auto x = (float)(std::rand() % 800) / 800.0f;
auto y = (float)(std::rand() % 600) / 600.0f;
dests[i] = new Dest(x,y, x + 32, y + 32)
}
auto renderer = new Renderer({128, 128}, 6u, { 2u, 2u })
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
renderer->current_material = nullptr;
renderer->quad_count = 0;
renderer->buffer_curr = renderer_->buffer;
renderer->curr_channel = renderer_->channels.begin();
renderer->next_channel = renderer_->curr_channel + 1;
for (auto i = 0; i < 64; ++i)
draw(renderer, src, dest[i], material1);
for (auto i = 64; i < 128; ++i)
draw(renderer, src, dest[i], material2);
flush(renderer);
std::cin.ignore();
}