Developing OpenGL 3 when all you have is OpenGL 1.5

As you probably should know by now there was a big cleanup with OpenGL 3.1, when the functions deprecated with OpenGL 3.0 were removed. This means that the immediate mode is gone as well as all fixed function options, which results in a more flexible and Object Oriented API.

But this also means that programs written with OpenGL3.1+ are fundamentally different than those written with OpenGL 2 and before. So if you develop a new application it makes sense to stick to the OpenGL3 model. The problem is that the current Linux open source drivers are stuck to at most OpenGL 1.5 support due to missing GLSL support. But what if you want to develop a OpenGL3 compatible renderer today?

This post will cover how to create a renderer based on the OpenGL3 model, but only using OpenGL 1.5 constructs. If you want to know more about the OpenGL3 model, read here.

The core of OpenGL3 probably is that you feed arbitary data into vertex/ fragment shaders which do something useful with them, instead of specifying directly what to do. These shaders are written in GLSL, which is a sort of a C dialect. But as I said there is no GLSL with OpenGL 1.5, so we will have to use vertex/ fragment programs. They do basically the same as shaders, but are written in assembly.

Luckily one can compile GLSL Shaders to vertex/ fragment programs using Nvidias CG so we obviosly need the nvidia-cg-toolkit. Vertex/ Fragment programs are also loaded differently, so while for a vertex shader you do

unsigned int prog = glCreateProgram();
unsigned int id = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(id, 1, (char**)&progStr.c_str(), NULL);
glCompileShader(id);
glAttachShader(prog, id);
glLinkProgram(prog);

you do for a vertex program

unsigned int id;
glGenProgramsARB(1, &id);
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, id);
glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
 progStr.length(), progStr.c_str());

The pushing of data into the shader is also handled differently. For GLSL Shader:

// we bind the uniform variable "matPMV" from the vertex shader
int loc = glGetUniformLocation(prog, "matPMV");
glUniformMatrix4fv(loc, 1, GL_FALSE, matPMV);

// bind the attribute "vertexPos" in the shader
#define VERTEX_ATTR 0
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindAttribLocation(prog, VERTEX_ATTR, "vertexPos");
glEnableVertexAttribArrayARB(VERTEX_ATTR);
glVertexAttribPointerARB(VERTEX_ATTR, 3, GL_FLOAT, false, 0, 0);

for a vertex program

// we have no uniforms here yet
// this binds the C0..C3 to the model view matrix
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, id);
glProgramLocalParameters4fvEXT(GL_VERTEX_PROGRAM_ARB, 0, 4, matPMV);

// attributes work almost the same
#define VERTEX_ATTR 0
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArrayARB(VERTEX_ATTR);
glVertexAttribPointerARB(VERTEX_ATTR, 3, GL_FLOAT, false, 0, 0);

but since we could specify the variable name in the code, we have to do it in the Shader:

uniform mat4 matPMV : C0;
attribute vec4 vertexPos : ATTR0;

the ordinary GLSL Vertex shader would just read

uniform mat4 matPMV;
attribute vec4 vertexPos;

But wait, I said there is no shader support in OpenGL 1.5. Right – this is where cgc comes into play. You have to compile your shader manually with it into a vertex program, which you finally load. The command for this is:

cgc -oglsl -profile arbvp1 vertexShader.cg -o vertexProg.vp

As you can see, the differences are pretty small using this technique, so you can start developing using the Open Source drivers now 🙂