Only the last instance is rendered

Started by
7 comments, last by Rarosu 8 years, 7 months ago

Hello GameDev!

I am porting an OpenGL 4.4 application to OpenGL 3.1 to get it to run on my Intel HD Graphics 3000 card (on my laptop). So far, things have been going well, but I ran into a problem when rendering more than one instance using the same vertex buffer (just changing the uniform buffer between the draw calls). When I do this, only the last instance is rendered. The non-ported code did not exhibit this problem and I just cannot see what I might have done to cause it to fail.

I have managed to isolate the bug in a minimal sample: http://pastebin.com/87DQhdrS


#include <SDL2/SDL.h>
#include <GL/gl3w.h>
#include <iostream>
#include <string>

const int OPENGL_MAJOR = 3;
const int OPENGL_MINOR = 1;
const int UNIFORM_BINDING = 1;

const char* VERTEX_SOURCE =
	"#version 140                                    \n"
	"                                                \n"
	"in vec2 in_position;                            \n"
	"                                                \n"
	"layout(std140) uniform UniformBuffer            \n"
	"{                                               \n"
	"    vec4 offset;                                \n"
	"};                                              \n"
	"                                                \n"
	"void main()                                     \n"
	"{                                               \n"
	"    gl_Position = vec4(in_position, 0.0f, 1.0f);\n"
	"    gl_Position.x += offset.x;                  \n"
	"}                                               \n";

const char* FRAGMENT_SOURCE =
	"#version 140                                    \n"
	"                                                \n"
	"out vec4 out_color;                             \n"
	"                                                \n"
	"void main()                                     \n"
	"{                                               \n"
	"    out_color = vec4(1.0f, 1.0f, 1.0f, 1.0f);   \n"
	"}                                               \n";

struct UniformBuffer
{
	float offset[4];
};

SDL_Window* window = nullptr;
SDL_GLContext glcontext = nullptr;
unsigned int viewport_width = 800;
unsigned int viewport_height = 600;
bool running = true;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
GLuint program = 0;
GLuint position_vbo = 0;
GLuint vao = 0;
UniformBuffer uniform_buffer_data;
GLuint uniform_buffer = 0;

void initialize();
void loadResources();
GLuint compileShader(const char* source, GLuint type);
void linkProgram(GLuint program);
void run();
void handleEvents();
void render();

int main(int argc, char* argv[])
{
	initialize();
	loadResources();
	run();

	return 0;
}

void initialize()
{
	if (SDL_Init(0) != 0)
	{
		throw std::runtime_error("Failed to initialize SDL");
	}

	window = SDL_CreateWindow("gl3", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, viewport_width, viewport_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
	if (window == nullptr)
	{
		throw std::runtime_error("Failed to create SDL window");
	}

	SDL_GLcontextFlag flags = SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
#ifndef NDEBUG
	flags = (SDL_GLcontextFlag)(flags | SDL_GL_CONTEXT_DEBUG_FLAG);
#endif
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, OPENGL_MAJOR);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, OPENGL_MINOR);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, flags);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32);
	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);

	glcontext = SDL_GL_CreateContext(window);
	if (glcontext == nullptr)
	{
		throw std::runtime_error("Failed to create OpenGL context");
	}

	if (gl3wInit() != 0)
	{
		throw std::runtime_error(std::string("Failed to initialize gl3w"));
	}

	if (gl3wIsSupported(OPENGL_MAJOR, OPENGL_MINOR) != 1)
	{
		throw std::runtime_error("OpenGL version not supported");
	}

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glViewport(0, 0, viewport_width, viewport_height);

	SDL_GL_SetSwapInterval(1);
}

void loadResources()
{
	// VBOs.
	float positions[] = { -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f };

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

	glGenBuffers(1, &position_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, position_vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 8, positions, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	// Shaders.
	vertexShader = compileShader(VERTEX_SOURCE, GL_VERTEX_SHADER);
	fragmentShader = compileShader(FRAGMENT_SOURCE, GL_FRAGMENT_SHADER);

	program = glCreateProgram();
	glAttachShader(program, vertexShader);
	glAttachShader(program, fragmentShader);
	linkProgram(program);

	glUniformBlockBinding(program, glGetUniformBlockIndex(program, "UniformBuffer"), UNIFORM_BINDING);

	// UBOs.
	uniform_buffer_data.offset[0] = 0.0f;
	uniform_buffer_data.offset[1] = 0.0f;
	uniform_buffer_data.offset[2] = 0.0f;
	uniform_buffer_data.offset[3] = 0.0f;

	glGenBuffers(1, &uniform_buffer);
	glBindBufferBase(GL_UNIFORM_BUFFER, UNIFORM_BINDING, uniform_buffer);
	glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformBuffer) * sizeof(float), &uniform_buffer_data, GL_DYNAMIC_DRAW);
}

GLuint compileShader(const char* source, GLuint type)
{
	GLuint shader = glCreateShader(type);
	glShaderSource(shader, 1, &source, nullptr);
	glCompileShader(shader);

	GLint status;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (status != GL_TRUE)
	{
		GLint logSize;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logSize);

		std::string log;
		if (logSize > 0)
		{
			int written;
			log.resize(logSize);
			glGetShaderInfoLog(shader, logSize, &written, &log[0]);
		}

		throw std::runtime_error("Failed to compile shader: " + log);
	}

	return shader;
}

void linkProgram(GLuint program)
{
	glLinkProgram(program);

	GLint status;
	glGetProgramiv(program, GL_LINK_STATUS, &status);
	if (status != GL_TRUE)
	{
		GLint logSize;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logSize);

		std::string log;
		if (logSize > 0)
		{
			int written;
			log.resize(logSize);
			glGetProgramInfoLog(program, logSize, &written, &log[0]);
		}

		throw std::runtime_error("Failed to link program: " + log);
	}
}

void run()
{
	while (running)
	{
		handleEvents();
		render();
	}
}

void handleEvents()
{
	SDL_Event event;
	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
			case SDL_QUIT:
			{
				running = false;
			} break;

			case SDL_WINDOWEVENT:
			{
				switch (event.window.event)
				{
					case SDL_WINDOWEVENT_RESIZED:
					{
						viewport_width = event.window.data1;
						viewport_height = event.window.data2;

						glViewport(0, 0, viewport_width, viewport_height);
					} break;
				}
			} break;
		}
	}
}

void render()
{
	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(program);
	glBindVertexArray(vao);

	uniform_buffer_data.offset[0] = 1.3f;
	glBindBufferBase(GL_UNIFORM_BUFFER, UNIFORM_BINDING, uniform_buffer);
	glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformBuffer) * sizeof(float), &uniform_buffer_data);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	uniform_buffer_data.offset[0] = -0.4f;
	glBindBufferBase(GL_UNIFORM_BUFFER, UNIFORM_BINDING, uniform_buffer);
	glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformBuffer) * sizeof(float), &uniform_buffer_data);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	SDL_GL_SwapWindow(window);
}

I am using SDL2 and GL3W and I am working in Visual Studio 2013 on Windows 10. The biggest change when I ported was that I no longer could specify the uniform binding point in the shader, but had to use glUniformBlockBinding instead. Anyone familiar with this version of OpenGL who could tell me if I am doing something stupid?

Cheers!

- Rarosu

Advertisement

1. Please post the code in this forum so future readers will have it. (Who knows what will happen to pastebin in the future)

2. Most of the time, a post that says "Here's my broken code. Please fix it." does not get very good results.

Please tell us (and future readers) what you have tried and what you think the problem is, or where you are stuck.

Have you used any OpenGL debugging tools to inspect the frame and data? That can help with silly errors you can't see. Maybe https://github.com/apitrace/apitrace?

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

The std130 layout will pad out the float to a vec4. The buffer won't be the right size for the uniform struct in the shader and the shader will read zero.

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

Thanks for the reply, GlassKnife, I'll edit the original post with the code.

I have reduced the issue to where I know there is something going on with the uniform bindings. I have tried making a separate program, VBO and VAO for the second instance and the same problem occurs. When I make a separate uniform buffer object however and fill it with the same data, it works. That is, the difference between:


glUseProgram(program);
glBindVertexArray(vao);

// Instance is rendered.
uniform_buffer_data.offset = 1.3f;
glBindBufferBase(GL_UNIFORM_BUFFER, UNIFORM_BINDING, uniform_buffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformBuffer), &uniform_buffer_data);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// This instance is also rendered.
ubo_data2.offset = -0.4f;
glBindBufferBase(GL_UNIFORM_BUFFER, UNIFORM_BINDING, ubo2);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformBuffer), &ubo_data2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

And this:


glUseProgram(program);
glBindVertexArray(vao);

// This instance is no longer visible.
uniform_buffer_data.offset = 1.3f;
glBindBufferBase(GL_UNIFORM_BUFFER, UNIFORM_BINDING, uniform_buffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformBuffer), &uniform_buffer_data);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

// And only this one is rendered.
uniform_buffer_data.offset = -0.4f;
glBindBufferBase(GL_UNIFORM_BUFFER, UNIFORM_BINDING, uniform_buffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformBuffer), &uniform_buffer_data);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

As for checking the state, I am not sure what to look for in this scenario. I tried apitrace at your suggestion (which seems awesome) and the state looks fine to me; as I would expect it.

@beans222 I don't think that is the problem in this case, but to be safe, I have tried changing the uniform data to an array of 4 floats and will include it in the edit.

Running this project on an Nvidia card instead yielded the expected results... Two boxes. Either Nvidia is lenient with whatever error I might be doing, or this Intel driver has some bug. I will look into updating my drivers and see if this fixes the problem.

Man, I hate it when the driver seems to be at fault. I had something like that once. A beta Linux driver wasn't compiling the shader correctly. It was so hard to find.

If you do find an answer, be sure to post it here in case this comes up for someone else.

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

@Glass_Knife Hah. It it a frightening thought to think how many hidden driver errors there are out there.

Updating the Intel driver to 9.17.10.4229 did not solve the problem unfortunately. The only solution I can see currently is revert to using glUniform* or create separate uniform buffer objects for each instance. Which sucks. If I find out anything else I'll update this thread.

- Rarosu

Calling glFinish() after the first draw call seems to fix this problem. Obviously this is not ideal as it will block the execution until the draw call has finished, but it will at least fix the symptoms.

This topic is closed to new replies.

Advertisement