Sharing an uniform buffer across a block array.

Hi,

I was trying to use a single uniform buffer across a block array.
For this I used glBindBufferRange instead of glBindBufferBase to bind the buffer and in this experiment I expected it will use the offset and range parameters.

Unfortunately, I can’t make it work on nVidia drivers. (AMD doesn’t support block arrays yet).

So, is such feature supposed to be an OpenGL 4.0 feature?

I haven’t find any clue against it but, nothing confirming it explicitly either:
As least glBindBufferRange can be used on uniform buffer.
http://www.opengl.org/sdk/docs/man4/xhtml/glBindBufferRange.xml

I was trying to use a single uniform buffer across a block array.

I’m not sure what it is that you’re trying to accomplish.

Each element of a block array is a separate UBO binding point. As long as you bind different regions of the buffer to those different binding points, you should be fine.

Unfortunately, I can’t make it work on nVidia drivers.

What happens?

That’s what I was trying to accomplish. :wink:

I have an invalid operation error on the second glBindBufferRange call…

I have an invalid operation error on the second glBindBufferRange call…

Do you have the alignment for your data correct? Are you sure that you are not binding beyond the end of the buffer object?

Yes I am pretty sure not to bind beyond the buffer object.

What are the restriction on the alignment? For my experiment it was 64 bytes alignment so it should be not problem here.

Are you sure you’re getting GL_INVALID_OPERATION? Because the only reason glBindBufferRange should return that is if you have passed a buffer object name that doesn’t exist.

My mistake Alfonse, the error is “invalid value” actually.

I just made a basic sample code out of it to show the problem and I guess it’s a bug then:

//**********************************
// OpenGL Uniform Buffer Shared
// 23/08/2010 - 23/08/2010
//**********************************
// Christophe Riccio
// [email]g.truc.creation@gmail.com[/email]
//**********************************
// G-Truc Creation
// www.g-truc.net
//**********************************

#include <glf/glf.hpp>

namespace
{
	std::string const SAMPLE_NAME = "OpenGL Uniform Buffer Shared";
	std::string const VERTEX_SHADER_SOURCE(glf::DATA_DIRECTORY + "400/uniform-buffer.vert");
	std::string const FRAGMENT_SHADER_SOURCE(glf::DATA_DIRECTORY + "400/uniform-buffer.frag");
	int const SAMPLE_SIZE_WIDTH = 640;
	int const SAMPLE_SIZE_HEIGHT = 480;
	int const SAMPLE_MAJOR_VERSION = 4;
	int const SAMPLE_MINOR_VERSION = 0;

	glf::window Window(glm::ivec2(SAMPLE_SIZE_WIDTH, SAMPLE_SIZE_HEIGHT));

	GLsizei const VertexCount = 4;
	GLsizeiptr const PositionSize = VertexCount * sizeof(glm::vec2);
	glm::vec2 const PositionData[VertexCount] =
	{
		glm::vec2(-1.0f,-1.0f),
		glm::vec2( 1.0f,-1.0f),
		glm::vec2( 1.0f, 1.0f),
		glm::vec2(-1.0f, 1.0f)
	};

	GLsizei const ElementCount = 6;
	GLsizeiptr const ElementSize = ElementCount * sizeof(GLushort);
	GLushort const ElementData[ElementCount] =
	{
		0, 1, 2, 
		2, 3, 0
	};

	GLuint const Instances = 2;

	GLuint ProgramName = 0;
	GLuint ElementBufferName = 0;
	GLuint ArrayBufferName = 0;
	GLuint VertexArrayName = 0;
	GLuint TransformBufferName = 0;
	GLuint MaterialBufferName = 0;
	GLint UniformTransform[Instances] = {0, 0};
	GLint UniformMaterial = 0;

}//namespace

bool initProgram()
{
	bool Validated = true;
	
	// Create program
	if(Validated)
	{
		GLuint VertexShaderName = glf::createShader(GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
		GLuint FragmentShaderName = glf::createShader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);

		ProgramName = glCreateProgram();
		glAttachShader(ProgramName, VertexShaderName);
		glAttachShader(ProgramName, FragmentShaderName);
		glDeleteShader(VertexShaderName);
		glDeleteShader(FragmentShaderName);

		glLinkProgram(ProgramName);
		Validated = glf::checkProgram(ProgramName);
	}

	// Get variables locations
	if(Validated)
	{
		UniformMaterial = glGetUniformBlockIndex(ProgramName, "material");
		UniformTransform[0] = glGetUniformBlockIndex(ProgramName, "transform[0]");
		UniformTransform[1] = glGetUniformBlockIndex(ProgramName, "transform[1]");
	}

	return Validated && glf::checkError("initProgram");
}

bool initVertexArray()
{
	// Build a vertex array object
	glGenVertexArrays(1, &VertexArrayName);
    glBindVertexArray(VertexArrayName);
		glBindBuffer(GL_ARRAY_BUFFER, ArrayBufferName);
		glVertexAttribPointer(glf::semantic::attr::POSITION, 2, GL_FLOAT, GL_FALSE, 0, 0);

		glEnableVertexAttribArray(glf::semantic::attr::POSITION);
	glBindVertexArray(0);

	return glf::checkError("initVertexArray");
}

bool initArrayBuffer()
{
	// Generate a buffer object
	glGenBuffers(1, &ElementBufferName);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ElementBufferName);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, ElementSize, ElementData, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	glGenBuffers(1, &ArrayBufferName);
    glBindBuffer(GL_ARRAY_BUFFER, ArrayBufferName);
    glBufferData(GL_ARRAY_BUFFER, PositionSize, PositionData, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	return glf::checkError("initArrayBuffer");
}

bool initUniformBuffer()
{
	GLint UniformBlockSize = 0;

	glGenBuffers(1, &TransformBufferName);
	glBindBuffer(GL_UNIFORM_BUFFER, TransformBufferName);

	glGetActiveUniformBlockiv(
		ProgramName, 
		UniformTransform[0],
		GL_UNIFORM_BLOCK_DATA_SIZE,
		&UniformBlockSize);

	glBufferData(GL_UNIFORM_BUFFER, UniformBlockSize * Instances, 0, GL_DYNAMIC_DRAW);

	// Attach the buffer to UBO binding point glf::semantic::uniform::TRANSFORMn
	glBindBufferRange(GL_UNIFORM_BUFFER, glf::semantic::uniform::TRANSFORM0, TransformBufferName, 0 * sizeof(glm::mat4), sizeof(glm::mat4));
	glBindBufferRange(GL_UNIFORM_BUFFER, glf::semantic::uniform::TRANSFORM1, TransformBufferName, 1 * sizeof(glm::mat4), sizeof(glm::mat4));

	{
		glm::vec4 Diffuse(1.0f, 0.5f, 0.0f, 1.0f);

		glGetActiveUniformBlockiv(
			ProgramName, 
			UniformMaterial,
			GL_UNIFORM_BLOCK_DATA_SIZE,
			&UniformBlockSize);

		glGenBuffers(1, &MaterialBufferName);
		glBindBuffer(GL_UNIFORM_BUFFER, MaterialBufferName);
		glBufferData(GL_UNIFORM_BUFFER, UniformBlockSize, &Diffuse[0], GL_DYNAMIC_DRAW);
		glBindBuffer(GL_UNIFORM_BUFFER, 0);

		// Attach the buffer to UBO binding point glf::semantic::uniform::MATERIAL
		glBindBufferBase(GL_UNIFORM_BUFFER, glf::semantic::uniform::MATERIAL, MaterialBufferName);
	}

	return glf::checkError("initUniformBuffer");
}

bool begin()
{
	GLint MajorVersion = 0;
	GLint MinorVersion = 0;
	glGetIntegerv(GL_MAJOR_VERSION, &MajorVersion);
	glGetIntegerv(GL_MINOR_VERSION, &MinorVersion);
	bool Validated = glf::version(MajorVersion, MinorVersion) >= glf::version(SAMPLE_MAJOR_VERSION, SAMPLE_MINOR_VERSION);

	if(Validated)
		Validated = initProgram();
	if(Validated)
		Validated = initArrayBuffer();
	if(Validated)
		Validated = initVertexArray();
	if(Validated)
		Validated = initUniformBuffer();

	return Validated && glf::checkError("begin");
}

bool end()
{
	glDeleteVertexArrays(1, &VertexArrayName);
	glDeleteBuffers(1, &ArrayBufferName);
	glDeleteBuffers(1, &ElementBufferName);
	glDeleteBuffers(1, &TransformBufferName);
	glDeleteBuffers(1, &MaterialBufferName);
	glDeleteProgram(ProgramName);

	return glf::checkError("end");
}

void display()
{
	glBindBuffer(GL_UNIFORM_BUFFER, TransformBufferName);
	glBufferData(GL_UNIFORM_BUFFER, sizeof(glm::mat4) * Instances, NULL, GL_DYNAMIC_DRAW);

	{
		// Compute the MVP (Model View Projection matrix)
		glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
		glm::mat4 ViewTranslate = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -Window.TranlationCurrent.y));
		glm::mat4 ViewRotateX = glm::rotate(ViewTranslate, Window.RotationCurrent.y, glm::vec3(1.f, 0.f, 0.f));
		glm::mat4 View = glm::rotate(ViewRotateX, Window.RotationCurrent.x, glm::vec3(0.f, 1.f, 0.f));
		glm::mat4 Model = glm::mat4(1.0f);
		glm::mat4 MVP = Projection * View * Model;

		glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(MVP), &MVP[0][0]);
	}

	{
		// Compute the MVP (Model View Projection matrix)
		glm::mat4 Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
		glm::mat4 ViewTranslate = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -Window.TranlationCurrent.y));
		glm::mat4 ViewRotateX = glm::rotate(ViewTranslate, Window.RotationCurrent.y, glm::vec3(1.f, 0.f, 0.f));
		glm::mat4 View = glm::rotate(ViewRotateX, Window.RotationCurrent.x + 90.f, glm::vec3(0.f, 1.f, 0.f));
		glm::mat4 Model = glm::mat4(1.0f);
		glm::mat4 MVP = Projection * View * Model;

		glBufferSubData(GL_UNIFORM_BUFFER, sizeof(MVP), sizeof(MVP), &MVP[0][0]);
	}
	glBindBuffer(GL_UNIFORM_BUFFER, 0);

	// Set the display viewport
	glViewport(0, 0, Window.Size.x, Window.Size.y);

	// Clear color buffer with black
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	// Bind program
	glUseProgram(ProgramName);
	glUniformBlockBinding(ProgramName, UniformTransform[0], glf::semantic::uniform::TRANSFORM0);
	glUniformBlockBinding(ProgramName, UniformTransform[1], glf::semantic::uniform::TRANSFORM1);
	glUniformBlockBinding(ProgramName, UniformMaterial, glf::semantic::uniform::MATERIAL);

	// Bind vertex array & draw 
	glBindVertexArray(VertexArrayName);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ElementBufferName);

	glDrawElementsInstancedBaseVertex(GL_TRIANGLES, ElementCount, GL_UNSIGNED_SHORT, NULL, Instances, 0);

	glf::checkError("display");
	glf::swapBuffers();
}

int main(int argc, char* argv[])
{
	if(glf::run(
		argc, argv,
		glm::ivec2(::SAMPLE_SIZE_WIDTH, ::SAMPLE_SIZE_HEIGHT), 
		::SAMPLE_MAJOR_VERSION, 
		::SAMPLE_MINOR_VERSION))
		return 0;
	return 1;
}

I just made more tests about a single uniform buffer shared across 2 blocks and I have the same error.

1 block from the vertex shader, 1 block from the fragment shader. Do you know if it is a limitation?

By the way I have this error both on nVidia and AMD.

Likely me being paranoid, but just to check: if you bind 2 different buffer objects to two different indices, everything is then ok?

On another wonkiness check, this is more like out of curiosity: what happens if you use glBindBufferBase (yes one loses the offset) for two different indices and same buffer object?

Odd though… I cannot see any thing in the 3.3 or 4.1 spec that says the same buffer object cannot be used the way you are… but if both NVIDIA and ATI have the same behavior, makes me suspicious that I missed something in the spec.

but if both NVIDIA and ATI have the same behavior, makes me suspicious that I missed something in the spec.

They don’t have the same behavior. NVIDIA drivers are having a problem with binding the same buffer object to multiple buffer object binding points that are buffer arrays. ATI drivers don’t yet support buffer arrays at all. This has the same effect as the NVIDIA issue, but it’s not the same problem.

I’d suggest sending a bug report off to NIVIDA.