Last Ray of Hope Home of Kaluriel Hargrove

28May/09Off

Athena: OpenGL 3.0 Upgrade in Progress…

For replacing glMatrixMode(), there were two options to replace it with.

  • Transform a set of pre-transformed vertices into a VBO.
  • Uniform variables for projection, model, and view matrices.

Since the former would needlessly waste CPU time, I went with the latter option, however it would mean updating the same information over and over for every change in shader I performed.

To get around this I decided to do some research into an nVidia extension GL_EXT_bindable_uniform I had been reading about in a whitepaper the night before for geometry instancing.

Basically it allows you to setup a struct or array of shader variables that are bound to a buffer object, so that only the buffer object has to be updated, and shaders using this will have access to it.

The model matrix will be kept as part of another buffer object that will be change per model, storing information such as joint transformations for animation.

First of all, the creation of the buffer, I've just got some code at the moment in my common shader namespace that I call when OpenGL has been loaded but before a shader is created.

//
//
namespace CommonShader
{
	struct Transform
	{
		Matrix4x4 projMatrix; // My Matrix4x4 class is exactly 64 bytes in size
		Matrix4x4 viewMatrix; // This is important since extra padding will misalign types.
	};
 
	GLuint l_transformBuffer = 0;
}
 
//
//
void CommonShader::CreateTransformBuffer()
{
	// Make sure its not already created
	assert( !l_transformBuffer );
 
	// Create
	glGenBuffers( 1, &l_transformBuffer );
	glBindBuffer( GL_UNIFORM_BUFFER_EXT, l_transformBuffer );
	glBufferData( GL_UNIFORM_BUFFER_EXT, sizeof( Transform ), 0, GL_DYNAMIC_READ );
}
 
//
//
void CommonShader::DestroyTransformBuffer()
{
	if( l_transformBuffer )
	{
		glDeleteBuffers( 1, &l_transformBuffer );
		l_transformBuffer = 0;
	}
}

 

You will notice that there is a special buffer type for uniform variables called GL_UNIFORM_BUFFER_EXT, this is important. I've gave it a null pointer for now, so the data will be uninitialised, and used GL_DYNAMIC_READ, since the data in the buffer object will be updated constantly, and is used for reading by GL.

Now with the buffer created, when a shader has been loaded, and linked, we can use a call to glUniformBufferEXT() to bind this buffer object to the shader program.

// ...
 
//
// Link and Use shader program
glLinkProgram( m_program );
glUseProgram( m_program );
 
//
//
const int u_transformLocation = glGetUniformLocation( m_program, "u_transform" );
if( u_transformLocation != -1 )
{
	//
	const int kTransformBufferSize = glGetUniformBufferSizeEXT( m_program, u_transformLocation );
	assert( sizeof( Transform ) >= kTransformBufferSize );
 
	//
	glUniformBufferEXT( m_program, u_transformLocation, l_transformBuffer );
}

 

The glGetUniformLocation() function also works with bindable uniform buffers as well, I've decided to call my bindable uniform "u_transform". Although not essential, I believe the size of the buffer object doesn't have to be the same as the one internally, depending on how it is accessed.

To be sure, I added an assert() so that it must be least equal to greater than what is required to the returned value of the extension specific call glGetUniformBufferSizeEXT() - this retrieves the size of the bindable uniform variable.

To bind a buffer object to the bindable uniform variable, a call to gl_UniformBufferEXT() with the id of the buffer object will bind that buffer object to that uniform variable. There is no further need to use glBindBuffer() when using the shader program, it is handled internally.

Now for the shader sourcecode itself.

//
// Enable extension in shader
#extension GL_EXT_bindable_uniform : enable
 
//
// Buffer Object layout
struct Transform
{
	mat4 proj; // THIS DOES NOT WORK!
	mat4 view; // I'll get to the why in a moment.
};
 
//
// Attributes
attribute vec4 a_vertex;
 
//
// Uniform variables
bindable uniform Transform u_transform;
uniform mat4 u_model;
 
//
// Vertex Shader entry point
void main()
{
	// My matrices are row major unlike OpenGL, so my multiplication is reversed to compensate.
	gl_Position = u_transform.proj * u_transform.view * u_model * a_vertex;
}

 

First of all, the shader needs to be updated to have the extension enabled, so the first line is a #extension to do this. I've defined a structure that contains two matrices, and declared a uniform variable using this struct.

The keyword bindable is added before uniform to let the shader compiler know that this is link to a buffer object. And finally I've added a second uniform variable for the model matrix - this is just a normal uniform variable to keep things simple for this blog entry.

As I said in the shader source comment, this does not work! When I first ran this code, it ran for a few seconds and crashed at an unknown location in the call stack. It took me a while to figure it out, but after playing around with the structure in the shader, I found that it was because proj and view were defined as mat4.

I don't understand why some examples on the internet use this, maybe its just some implementations of OpenGL allow it, or there is another extension, but within the GL_EXT_bindable_uniform specification it clearly states...

"...The memory layout of matrix, boolean, or boolean vector uniforms is not defined, and the error INVALID_OPERATION will be generated if <location> refers to a boolean, boolean vector, or matrix uniform..."

The glUniformMatrix4*() function must format the data correctly in software when you pass a matrix to it, but it can't be done on a buffer object. So here is how I fixed the problem.

//
// Enable extension in shader
#extension GL_EXT_bindable_uniform : enable
 
//
// Buffer Object layout
struct Transform
{
	vec4 proj[4];
	vec4 view[4];
};
 
//
// Attributes
attribute vec4 a_vertex;
 
//
// Uniform variables
bindable uniform Transform u_transform;
uniform mat4 u_model;
 
//
// Vertex Shader entry point
void main()
{
	// Create projection and view matrix from vec4 arrays.
	mat4 proj = mat4( u_transform.proj[0], u_transform.proj[1], u_transform.proj[2], u_transform.proj[3] );
	mat4 view = mat4( u_transform.view[0], u_transform.view[1], u_transform.view[2], u_transform.view[3] );
 
	// My matrices are row major unlike OpenGL, so my multiplication is reversed to compensate.
	gl_Position = proj * view * u_model * a_vertex;
}

 

This takes the same data but stores it in an array of four vec4 types, creating a matrix from it in the main function.

Finally for updating the buffer object. This code is the same as other buffer objects. You can use glMapBuffer(), or as I've done below, glBufferSubData().

//
//
void CommonShader::SetProjMatrix( const Core::Matrix4x4 & inMatrix )
{
	glBindBuffer( GL_UNIFORM_BUFFER_EXT, l_transformBuffer );
	glBufferSubData( GL_UNIFORM_BUFFER_EXT,
			0, // Destination Offset
			sizeof( Core::Matrix4x4 ), // Copy Size
			&inMatrix[0][0] ); // Source Data
}
 
//
//
void CommonShader::SetViewMatrix( const Core::Matrix4x4 & inMatrix )
{
	glBindBuffer( GL_UNIFORM_BUFFER_EXT, l_transformBuffer );
	glBufferSubData( GL_UNIFORM_BUFFER_EXT,
			sizeof( Core::Matrix4x4 ), // Destination Offset
			sizeof( Core::Matrix4x4 ), // Copy Size
			&inMatrix[0][0] ); // Source Data
}

 

And with that, I could finally remove calls to glMatrixMode() and glLoadMatrix(). It was at this point I decided to remove the ortho matrix for my 2D rendering, and just use -1.0 to 1.0 coordinates instead.

One of the ways I plan to extend this is to use OpenGL's method of compiling multiple sources for a shader to automatically add the code for the bindable uniforms so that they are always the same as they are in code, and maybe create a function for calculating the modelViewProj matrix that can be called.

On the next page of this blog entry will be all the little things which aren't that much to fix, but there are lots of.

Tagged as: , , Comments Off
Comments (0) Trackbacks (0)

Sorry, the comment form is closed at this time.

Trackbacks are disabled.