OpenGL Handles, and how Stuff lives on the GPU

CS 481 Lecture, Dr. Lawlor

(This is a sort of meta-introduction, where you're supposed to replace "Thingy" with an actual specific object like "Texture", "Program", "Buffer", and so on as we'll discuss below.)

Here's the deal with thingys.  You allocate an OpenGL handle to an on-graphics card thingy by calling a gen function:
    void glGenThingys(int n_thingys, GLuint *thingy_handle);
Here's a typical call:
    static GLuint myThingy=0;
    if (myThingy==0) {
       glGenThingys(1,&myThingy);
       ... set up your new Thingy ...
    }

Once you've made a thingy, to use it you need to bind it as the current thingy.  All subsequent thingy-related calls will then apply to your thingy.  The default, fixed-function OpenGL operation uses handle zero, so it's good practice to bind thingy zero back before you exit.  The bind call is usually something like:
    glBindThingy(myThingy); // start using myThingy
    ... render using your Thingy ...
    glBindThingy(0); // back to fixed-function OpenGL

Once bound, you need to set up your new thingy.  OpenGL provides about fifty functions for setting up your thingy, of the form:
    glSomethingThingy(GL_THINGY,GL_THINGY_SOMETHING, GL_FOR_GOODNESS_SAKES_GET_TO_THE_DANG_POINT);

Eventually, you may need to delete your thingy.  There's a corresponding glDeleteThingys call to free up that space on the graphics card.  However, I can't recommend creating and deleting thingys every frame--creating and deleting any of these objects is usually fairly expensive (like milliseconds), so it's faster to create things once and re-use them many times.
    glDeleteThingys(1,&myThingy);

Texture == Thingy

A Texture is a 1D, 2D, 3D, or cubemap array of color pixels.  It's implemented as a solid rectangular block of pixels sitting in GPU memory.   Texture state includes how to handle out-of-bounds pixels (GL_TEXTURE_WRAP_axis), how to shrink and enlarge the texture (GL_TEXTURE_MIN_FILTER/MAG_FILTER), and so on.

Textures are kinda weird in that you can have several different textures bound at once, to different texture "units", which are numbered 0 through some small integer.  You have to "activate" a texture unit before binding a texture to it.  Typical GPUs nowadays support 4-8 texture units.
	static GLuint monkeyTex=0;
if (monkeyTex==0) { /* first-time initialization */
glGenTextures(1,&monkeyTex); /* make a texture handle */
glActiveTexture(GL_TEXTURE3); /* we'll bind to texture unit 3 */
glBindTexture(GL_TEXTURE_2D,monkeyTex);
readTextureFromFile("monkey.bmp"); /* pixels go into monkeyTex */
/* Texture state applies to the currently bound texture */
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAX_ANISOTROPY_EXT,8);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
}

... bind up a GLSL program
/* The monkey texture is bound to texture unit 3 */
glUniform1iARB(glGetUniformLocationARB(prog,"myMonkey"),3);
... In GLSL:
uniform sampler2D myMonkey;
... vec4 t = texture2D(myMonkey,vec2(monkeyCoords));
Pitfalls:

Vertex Buffer Object == Thingy

Vertex Buffer Objects (VBOs) are a way to store geometry information on the graphics card, just like Textures let you store raster information on the graphics card.  This is part of the ARB_vertex_buffer_object extension.

A VBO describes a series of glVertex (and optionally glColor, glNormal, and glTexCoord) calls.  The parameters for the calls start in a CPU array, and get copied into graphics card memory.

You create a Vertex Buffer Object with (guess what!) glGenBuffersARB.  You then have to glBindBufferARB the buffer, and then you can then copy data in with glBufferDataARB.    You describe what your data contains using calls to glVertexPointer (and optionally glColorPointer, glNormalPointer, and glTexCoordPointer), which each take the same four parameters:
Here's how you'd create a vertex buffer object to store vertex locations and colors, then render it:
	static GLuint vb=0; 
if (vb==0) { /* set up the vertex buffer object */
glGenBuffersARB(1,&vb); /* make a buffer */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb);
/* Copy our vtx array (on the CPU) into our new GPU buffer */
glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(myVertex)*vtx.size(),
&vtx[0],GL_STATIC_DRAW_ARB);

/* Tell OpenGL how our array is laid out */
glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3,GL_FLOAT,sizeof(vtx[0]), (void *)0); /* myVertex.xyz is first thing in struct */
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer (3,GL_FLOAT,sizeof(vtx[0]), (void *)12); /* myVertex.color starts 12 bytes after struct start */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0); /* back to plain OpenGL */
}
/* Draw all our (GPU) points.
This is way faster than looping over vtx and calling glVertex many times! */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb);
glDrawArrays(GL_POINTS,0,vtx.size());
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
You can also create an "element buffer" to store vertex indices.  For example, to make a triangle from vertices zero, seven, and thirteen, you'd put {0,7,13} into an element buffer.  Element buffers allow many triangles to point to the same vertex, which saves that vertex many trips through your vertex shader.  You upload the index data with glBufferDataARB (just like vertex buffer objects), and then use glDrawElements to look up your indices into your (already bound) vertex array:
	static GLuint eb=0; 
if (eb==0) { /* set up the element buffer object */
glGenBuffersARB(1,&eb); /* make a buffer */
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,vb);
/* Copy our idx array (on the CPU) into our new GPU buffer */
glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(int)*idx.size(),
&idx[0],GL_STATIC_DRAW_ARB);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,0); /* back to plain OpenGL */
}
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb); /* vertex data */
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,eb); /* index data */
glDrawElements(GL_TRIANGLES,idx.size(),GL_UNSIGNED_INT,0);
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,0);

Framebuffer Object == Thingy

A "Framebuffer Object" (FBO) is a place you can render stuff.  This consists of a color texture, an optional depth texture (for the depth buffer), and possibly other weirder things like stencils (one byte per pixel, used for certain shadow algorithms) or multiple render targets (one fragment shader, many output colors!).  The gory details are in EXT_framebuffer_object.

Framebuffer Objects are a handy way to do offscreen rendering which allows:
As usual, you make a new Framebuffer Object with glGenFramebuffersEXT, bind in a Framebuffer Object with glBindFramebufferEXT, and then glFramebufferTexture2DEXT can attach texture objects (as raw GLuint handles!) to the current framebuffer.  You can then reset the rendering size with glViewport, and start rendering away!  Note that you can also render a few things, switch the destination buffer with glFramebufferTexture2DEXT, and then render more stuff; this "buffer swap" is actually a bit faster than binding in a new framebuffer object.
	/* Framebuffer output texture */
static GLuint frameTex=0;
int w=256,h=256; /* size of our texture */
if (frameTex==0) {
glGenTextures(1,&frameTex); /* make a texture handle */
glBindTexture(GL_TEXTURE_2D,frameTex);
glTexImage2D(GL_TEXTURE_2D,0,
GL_RGBA8, w,h, /* data format and size (pixels) */
0,GL_LUMINANCE,GL_FLOAT,0); /*<- no data needed, just size */
glGenerateMipmapEXT(GL_TEXTURE_2D); /*<- most cards *require* all mipmap levels to be present! */
}

/* Framebuffer object */
static GLuint fbo=0;
if (fbo==0) { /* set up framebuffer object */
glGenFramebuffersEXT(1,&fbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, frameTex, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0); /* back to normal */
}

/* Render into our framebuffer object */
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fbo);
if (glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT != GL_FRAMEBUFFER_COMPLETE_EXT)
printf("Framebuffer object is unhappy! Oh no!\n");
glViewport(0,0,w,h);
glDisable(GL_DEPTH_TEST); /* or else attach a depth texture to GL_DEPTH_ATTACHMENT_EXT! */

... rendered stuff will go into the frameTex texture now! ...

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0); /* back to normal rendering */
glViewport(0,0,glutGet(GLUT_WINDOW_WIDTH),glutGet(GLUT_WINDOW_HEIGHT));
glEnable(GL_DEPTH_TEST);

glBindTexture(GL_TEXTURE_2D,frameTex);
glGenerateMipmapEXT(GL_TEXTURE_2D); /* build mipmaps of rendered data */
Pitfalls:

GLSL Program == Thingy (kinda)

A compiled GLSL program is a rather unusual OpenGL object in a few ways:
	static GLhandleARB p=0;
if (p==0) {
p=glCreateProgramObjectARB();
GLhandleARB vo=glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
GLhandleARB fo=glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
glShaderSourceARB(vo,1,&"//This is my vertex shader ... GLSL here ...",NULL);
glShaderSourceARB(fo,1,&"//This is my fragment shader ... GLSL here ...",NULL);
glCompileShaderARB(vo); glCompileShaderARB(fo); /* FIXME: error check! */
glAttachObjectARB(p,vo); glAttachObjectARB(p,fo);
glLinkProgramARB(p); /* FIXME: error check! */
glDeleteObjectARB(vo); glDeleteObjectARB(fo); /* don't leak memory! */
}
glUseShaderObjectARB(p);
/* render stuff with our GLSL code here! */
glUseShaderObjectARB(0); /* Back to ordinary OpenGL */