glBufferStorage

Hello,

For the past several days I have attempted to create a batch renderer of same size sprites from different spritesheets. However after drawing the first batch all proceeding batches don’t seem to want to draw.

I’m still pretty new to OpenGL and was wondering if anyone has had any success with 2D batching with glBufferStorage.

You probably aren’t setting up some state correctly, or haven’t populate the buffer(s) properly.

Post the GL calls you’re making to setup for and perform your 1st and 2nd draw calls.

Using a tool like apitrace that’ll capture and dump out the GL calls your making can be useful here. Interactive GL debuggers like Nsight Graphics can be as well, as they let you inspect the full GL state active when you launch a draw call.

Alright here we go… Not entirely

This is where I create my buffer objects. Channels represents the max amount of objects which can be drawn per batch. It used to be stored in one large allocated buffer, this is just an attempt to fix the issue.

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), first_quad(0)
		{
			for (auto att : attribute_sizes)
				attribute_size += att;

			vertex_size = attribute_size * indices_size;

			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 the VBO address
			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);
			}
		}

		~PRenderer()
		{
			// delete allocated buffers
			glDeleteBuffers(1, &vbo);
			glDeleteBuffers(1, &vao);
		}

		GLuint max_sprites,
			indices_size,
			vertex_size,
			attribute_size,
			quad_count, first_quad;

		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;
	};

This is where I am drawing the quads. Src and Dest are both just glm::vec4s and Material is a holding class for my Shaders and Textures.

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;

    // increment quad
	++renderer->quad_count;
}

Lastly here is my flush function, where I call glDrawArrays. I don’t think I am using first properly, and I reckon that has something to do with the issue I am running into.

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 = *renderer->curr_channel;

	// draw triangles
	glDrawArrays(GL_TRIANGLES, first, total_draws);

    // increment chanel and buffer position
	renderer->curr_channel++;
	renderer->buffer_curr = &renderer->buffer[*renderer->curr_channel];
	renderer->next_channel++;
	renderer->first_quad += renderer->quad_count;
	renderer->quad_count = 0;
}

Let me know what you see wrong here, I would be happy to provide more if need be. I have already successfully gotten one batch renderer working using glBufferData and glBufferSubData so I am fairly confident the problem is in these three files.

Thanks,
Wad

I’m guessing that you got this wrong.

Good to know I made the changes outlined in the stack overflow post. However the same rendering issue is still present.

New Renderer class from suggestions of top answer

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), first_quad(0)
		{
			for (auto att : attribute_sizes)
				attribute_size += att;

			vertex_size = attribute_size * indices_size;

			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 the VBO address
			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(const Renderer&) = delete;
		Renderer& operator=(const Renderer&) = delete;

		Renderer(Renderer&& other) : vbo(other.vbo), vao(other.vao)
		{
			other.vbo = 0;
			other.vao = 0;
		}

		Renderer& operator=(Renderer&& other)
		{
			//ALWAYS check for self-assignment
			if (this != &other)
			{
				release();
				vbo = other.vbo;
				vao = other.vao;
				other.vbo = 0;
				other.vao = 0;
			}

			return *this;
		}

		~Renderer() { release(); }

		void release()
		{
			if (vbo)
				glDeleteBuffers(1, &vbo);
			if (vao)
				glDeleteBuffers(1, &vao);
		}

		GLuint max_sprites,
			indices_size,
			vertex_size,
			attribute_size,
			quad_count, first_quad;

		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;
	};

Heres some images to hopefully help visualize the issue.

glBufferStorage sprite batcher

glBufferData/glBufferSubData sprite batcher

No, these are not the GL calls with the values provided for the parameters (the GL call trace). This is a bunch of C++ code that called in some way in some order results in some unspecified number and order of GL calls with some parameters and some GL context state. We’re not going to sift through all this. This isn’t a C++ debugging service. We need the GL calls with the parameter values that are being provided.

My guess is your bug is in the C++ code, and you’re getting some behavior you hadn’t intended.

To help you diagnose the problem, I would suggest putting this code off to-the-side for now and prototyping your batching technique in a simple, standalone C program. It should have the data for 2 draw calls hard-coded into arrays in the program. Then make the GL calls to setup these batches and render them in simple top-to-bottom code. I bet you’ll find your bug.

If you don’t, you’ll have very simple GL code that you can post to get assistance, without all the C++ obfuscation of what you’re trying to do with OpenGL.

1 Like