OpenGL UBO's - Uniform Buffer Objects

Uniform buffer objects were introduced with OpenGL 3.1. The following example is an adapted form of the UBO example from the UBO spec:

This example contains workarounds for an fglrx linux driver issue. The libGL.so of ATI (shipped with fglrx) does not export the correct amount of function symbols. Therefor you can’t use the functions that were defined within the glext.h header files. To workaround this issue i had to use glXGetProcAddress to determine the function address within the libGL.so. The address is used to create a function pointer and therefor use the function as usual.

Uniform Buffer Objects allow fast exchange of uniform values because they were backed up by a buffer object. As far as i know DMA is used to transfer the data directly from your system memory into the GPU memory.

This example requires freeglut 2.6.0 to initiate a opengl 3 context via glut. Please note that your driver must support UBO’s ( GL_ARB_uniform_buffer_object ).

GL3_UBO_Example.cpp:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define GL_GLEXT_PROTOTYPES 1
#define GLX_GLXEXT_PROTOTYPES 1
#include <GL/gl.h>
#include <GL/freeglut.h>
#include <GL/glx.h>
#include "GL/glext.h"
#include "textfile.h"

char *VertexShaderSource, *FragmentShaderSource;

#define GLX_CONTEXT_MAJOR_VERSION_ARB       0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB       0x2092

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig,
		GLXContext, Bool, const int*);
#define glXGetProcAddress(x) (*glXGetProcAddressARB)((const GLubyte*)x)

// mouse controls
int mouse_old_x, mouse_old_y;
int mouse_buttons = 0;
float rotate_x = 0.0, rotate_y = 0.0;
float translate_z = -2.0;

PFNGLGETUNIFORMBLOCKINDEXPROC mglGetUniformBlockIndex = NULL;
PFNGLGETACTIVEUNIFORMBLOCKIVPROC mglGetActiveUniformBlockiv = NULL;
PFNGLBINDBUFFERBASEPROC mglBindBufferBase = NULL;
PFNGLGETUNIFORMINDICESPROC mglGetUniformIndices = NULL;
PFNGLGETACTIVEUNIFORMSIVPROC mglGetActiveUniformsiv = NULL;
PFNGLUNIFORMBLOCKBINDINGPROC mglUniformBlockBinding = NULL;

#define glError() { \
    GLenum err = glGetError(); \
    while (err != GL_NO_ERROR) { \
    printf("glError: %s caught at %s:%u", \
           (char*)gluErrorString(err), __FILE__, __LINE__); \
    err = glGetError(); \
    exit(-1); \
    } \
    }

// globals
int initialized = 0;
unsigned int window_width = 640;
unsigned int window_height = 480;

float delta = 0.01;
GLfloat wf, hf;

//uniform names
const GLchar* names[] = { "SurfaceColor", "WarmColor", "CoolColor",
		"DiffuseWarm", "DiffuseCool" };
GLuint buffer_id, uniformBlockIndex, uindex, vshad_id, fshad_id, prog_id;

GLsizei uniformBlockSize;
GLint singleSize;
GLint offset;

GLfloat colors[] = { 0.45, 0.45, 1, 1, 0.45, 0.45, 1, 1, 0.75, 0.75, 0.75, 1,
		0.0, 0.0, 1.0, 1, 0.0, 1.0, 0.0, 1, };

void reshape(int w, int h) {
	window_width = w;
	window_height = h;
	wf = (GLfloat) window_width;
	hf = (GLfloat) window_height;
	glMatrixMode( GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, wf / hf, 0.1, 100.0);
}

void init_opengl() {
	initialized = 1;
	reshape(window_width, window_height);

	VertexShaderSource = textFileRead("src/shader.vert");
	FragmentShaderSource = textFileRead("src/shader.frag");

	const char * VS = VertexShaderSource;
	const char * FS = FragmentShaderSource;

	vshad_id = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vshad_id, 1, &VS, 0);

	fshad_id = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fshad_id, 1, &FS, NULL);

	glCompileShader(vshad_id);
	glCompileShader(fshad_id);

	prog_id = glCreateProgram();
	glAttachShader(prog_id, vshad_id);
	glAttachShader(prog_id, fshad_id);
	glLinkProgram(prog_id);

	//Update the uniforms using ARB_uniform_buffer_object
	glGenBuffers(1, &buffer_id);

	//There's only one uniform block here, the 'colors0' uniform block.
	//It contains the color info for the gooch shader.

	//REPLACED:
	//uniformBlockIndex = glGetUniformBlockIndex(prog_id, "colors0");
	mglGetUniformBlockIndex
			= (PFNGLGETUNIFORMBLOCKINDEXPROC) glXGetProcAddress("glGetUniformBlockIndex");
	printf("mglGetUniformBlockIndex:%016p\n", mglGetUniformBlockIndex);
	(*mglGetUniformBlockIndex)(prog_id, "colors0");

	//We need to get the uniform block's size in order to back it with the
	//appropriate buffer
	//REPLACED:
	//glGetActiveUniformBlockiv(prog_id, uniformBlockIndex,GL_UNIFORM_BLOCK_DATA_SIZE, &uniformBlockSize);
	mglGetActiveUniformBlockiv = (PFNGLGETACTIVEUNIFORMBLOCKIVPROC)
	glXGetProcAddress("glGetActiveUniformBlockiv");
	printf("mglGetActiveUniformBlockiv:%016p\n", mglGetActiveUniformBlockiv);
	(*mglGetActiveUniformBlockiv)(prog_id, uniformBlockIndex,
			GL_UNIFORM_BLOCK_DATA_SIZE, &uniformBlockSize);

	//glError();

	//Create UBO.
	glBindBuffer(GL_UNIFORM_BUFFER, buffer_id);
	glBufferData(GL_UNIFORM_BUFFER, uniformBlockSize, NULL, GL_DYNAMIC_DRAW);

	//Now we attach the buffer to UBO binding point 0...
	//REPLACED:
	//glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer_id);
	mglBindBufferBase
			= (PFNGLBINDBUFFERBASEPROC) glXGetProcAddress("glBindBufferBase");
	printf("mglBindBufferBase:%016p\n", mglBindBufferBase);
	(*mglBindBufferBase)(GL_UNIFORM_BUFFER, 0, buffer_id);

	//And associate the uniform block to this binding point.
	//REPLACED:
	//glUniformBlockBinding(prog_id, uniformBlockIndex, 0);
	mglUniformBlockBinding
			= (PFNGLUNIFORMBLOCKBINDINGPROC) glXGetProcAddress("glUniformBlockBinding");
	printf("mglUniformBlockBinding:%016p\n", mglUniformBlockBinding);
	(*mglUniformBlockBinding)(prog_id, uniformBlockIndex, 0);

	//glError();

	//To update a single uniform in a uniform block, we need to get its
	//offset into the buffer.
	//REPLACED:
	//glGetUniformIndices(prog_id, 1, &names[2], &uindex);
	mglGetUniformIndices
			= (PFNGLGETUNIFORMINDICESPROC) glXGetProcAddress("glGetUniformIndices");
	(*mglGetUniformIndices)(prog_id, 1, &names[2], &uindex);

	//REPLACED:
	//glGetActiveUniformsiv(prog_id, 1, &uindex, GL_UNIFORM_OFFSET, &offset);
	//glGetActiveUniformsiv(prog_id, 1, &uindex, GL_UNIFORM_SIZE, &singleSize);
	mglGetActiveUniformsiv = (PFNGLGETACTIVEUNIFORMSIVPROC)
	glXGetProcAddress("glGetActiveUniformsiv");
	printf("mglGetActiveUniformsiv:%016p\n", mglGetActiveUniformsiv);
	(*mglGetActiveUniformsiv)(prog_id, 1, &uindex, GL_UNIFORM_OFFSET, &offset);
	(*mglGetActiveUniformsiv)(prog_id, 1, &uindex, GL_UNIFORM_SIZE, &singleSize);

	//glError();

	printf("GL_VERSION:%s\n", glGetString(GL_VERSION));
	glViewport(0, 0, window_width, window_height);
}

void render() {
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	glUseProgram(prog_id);
	glEnable( GL_DEPTH_TEST);

	glMatrixMode( GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0, 0.0, translate_z);
	glRotatef(rotate_x, 1.0, 0.0, 0.0);
	glRotatef(rotate_y, 0.0, 1.0, 0.0);
	glColor3f(1.0, 1.0, 1.0);

	glBindBuffer(GL_UNIFORM_BUFFER, buffer_id);
	//We can use BufferData to upload our data to the shader,
	//since we know it's in the std140 layout
	glBufferData(GL_UNIFORM_BUFFER, 80, colors, GL_DYNAMIC_DRAW);
	//With a non-standard layout, we'd use BufferSubData for each uniform.
	glBufferSubData(GL_UNIFORM_BUFFER_EXT, offset, singleSize, &colors[8]);
	//the teapot winds backwards
	glFrontFace( GL_CW);
	glutSolidTeapot(0.6);
	glFrontFace( GL_CCW);
	glutSwapBuffers();
	glutPostRedisplay();

	int nColor = 8;
	colors[nColor] += delta;

	if (colors[nColor] > 1.0) {
		delta = -0.01;
	}

	if (colors[nColor] < 0.0) {
		delta = +0.01;
	}

}

void display() {
	if (!initialized) {
		init_opengl();
		initialized = 1;
	}

	render();
}

void mouse(int button, int state, int x, int y) {
	if (state == GLUT_DOWN) {
		mouse_buttons |= 1 << button;
	} else if (state == GLUT_UP) {
		mouse_buttons = 0;
	}

	mouse_old_x = x;
	mouse_old_y = y;
	glutPostRedisplay();
}

void motion(int x, int y) {
	float dx, dy;
	dx = x - mouse_old_x;
	dy = y - mouse_old_y;

	if (mouse_buttons & 1) {
		rotate_x += dy * 0.2;
		rotate_y += dx * 0.2;
	} else if (mouse_buttons & 4) {
		translate_z += dy * 0.01;
	}

	mouse_old_x = x;
	mouse_old_y = y;
}

void initGlut(int argc, char** argv) {

	// Create GL context
	glutInit(&argc, argv);
	glutInitContextVersion(3, 1);
	//glutInitContextFlags(GLUT_DEBUG);

	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowSize(window_width, window_height);
	glutCreateWindow("UBO Example");
	init_opengl();

	// register callbacks
	glutDisplayFunc(display);
	//glutKeyboardFunc( keyboard);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);

	glutMainLoop();

}

int main(int argc, char** argv) {
	initGlut(argc, argv);
}

shader.frag:

#extension GL_ARB_uniform_buffer_object : enable


layout(std140) uniform colors0
{
    float DiffuseCool;
    float DiffuseWarm;
    vec3  SurfaceColor;
    vec3  WarmColor;
    vec3  CoolColor;
};

varying float NdotL;
varying vec3  ReflectVec;
varying vec3  ViewVec;

void main (void)
{

    vec3 kcool    = min(CoolColor + DiffuseCool * SurfaceColor, 1.0);
    vec3 kwarm    = min(WarmColor + DiffuseWarm * SurfaceColor, 1.0);
    vec3 kfinal   = mix(kcool, kwarm, NdotL);

    vec3 nreflect = normalize(ReflectVec);
    vec3 nview    = normalize(ViewVec);

    float spec    = max(dot(nreflect, nview), 0.0);
    spec          = pow(spec, 32.0);

    gl_FragColor = vec4 (min(kfinal + spec, 1.0), 1.0);
}

shader.vert:

vec3 LightPosition = vec3(0.0, 10.0, 4.0);

varying float NdotL;
varying vec3  ReflectVec;
varying vec3  ViewVec;

void main(void)
{
    vec3 ecPos      = vec3 (gl_ModelViewMatrix * gl_Vertex);
    vec3 tnorm      = normalize(gl_NormalMatrix * gl_Normal);
    vec3 lightVec   = normalize(LightPosition - ecPos);
    ReflectVec      = normalize(reflect(-lightVec, tnorm));
    ViewVec         = normalize(-ecPos);
    NdotL           = (dot(lightVec, tnorm) + 1.0) * 0.5;
    gl_Position     = ftransform();
}

textfile.cpp:

#include <stdio.h>
#include <malloc.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>


char *textFileRead(char *fn) {


	FILE *fp;
	char *content = NULL;

	int f,count;
	f = open(fn, O_RDONLY);

	count = lseek(f, 0, SEEK_END);

	close(f);

	if (fn != NULL) {
		fp = fopen(fn,"rt");

		if (fp != NULL) {


			if (count > 0) {
				content = (char *)malloc(sizeof(char) * (count+1));
				count = fread(content,sizeof(char),count,fp);
				content[count] = '\0';
			}
			fclose(fp);
		}
	}
	return content;
}

int textFileWrite(char *fn, char *s) {

	FILE *fp;
	int status = 0;

	if (fn != NULL) {
		fp = fopen(fn,"w");

		if (fp != NULL) {

			if (fwrite(s,sizeof(char),strlen(s),fp) == strlen(s))
				status = 1;
			fclose(fp);
		}
	}
	return(status);
}

textfile.h:

char *textFileRead(char *fn);
int textFileWrite(char *fn, char *s);