Before we can begin looking into writing an OpenGL application, we must first know what it is that we are writing. What exactly is OpenGL?
OpenGL is usually thought of as an Application Programming Interface (API). The OpenGL API has been exposed to a number of languages. But the one that they all ultimately use at their lowest level is the C API.
The OpenGL API is defined as a state machine. Almost all of the OpenGL functions set or retrieve some state in OpenGL. The only functions that do not change state are functions that use the currently set state to cause rendering to happen.
You can think of the state machine as a very large struct with a great many different fields. This struct is called the OpenGL context, and each field in the context represents some information necessary for rendering.
The API, in C, is defined by a number of typedefs, #defined enumerator values, and functions. The typedefs define basic GL types like GLint, GLfloat and so forth. These are defined to have a specific bit depth.
Complex aggregates like structs are never directly exposed in OpenGL. Any such constructs are hidden behind the API. This makes it easier to expose the OpenGL API to non-C languages without having a complex conversion layer.
In C++, if you wanted an object that contained an integer, a float, and a string, you would create it and access it like this:
struct Object { int count; float opacity; char *name; }; //Create the storage for the object. Object newObject; //Put data into the object. newObject.count = 5; newObject.opacity = 0.4f; newObject.name = "Some String";
In OpenGL, you would use an API that looks more like this:
//Create the storage for the object GLuint objectName; glGenObject(1, &objectName); //Put data into the object. glBindObject(GL_MODIFY, objectName); glObjectParameteri(GL_MODIFY, GL_OBJECT_COUNT, 5); glObjectParameterf(GL_MODIFY, GL_OBJECT_OPACITY, 0.4f); glObjectParameters(GL_MODIFY, GL_OBJECT_NAME, "Some String");
None of these are actual OpenGL commands, of course. This is simply an example of what the interface to such an object would look like.
OpenGL owns the storage for all OpenGL objects. Because of this, the user can only
access an object by reference. Almost all OpenGL objects are referred to by an
unsigned integer (the GLuint). Objects are created by a function of the
form glGen*
, where * is the type of the object. The first
parameter is the number of objects to create, and the second is a
GLuint* array that receives the newly created object names.
To modify most objects, we must first bind them to the context. Many objects can
be bound to different locations in the context; this allows the same object to be
used in different ways. These different locations are called
targets; all objects have a list of valid targets, and
some have only one. In the above example, the fictitious target
“GL_MODIFY” is the location where objectName
is
bound.
Think of a target like a global pointer. So in our case, the
GL_MODIFY
target is just a global pointer that can store an
object of this type. Calling glBindObject(GL_MODIFY, objectName)
is similar to doing this:
Object *GL_MODIFY = NULL; //glBindObject: GL_MODIFY = ptr(objectName); //Convert object into pointer.
OpenGL functions that modify these objects only modify an object that is bound to the context. Those function take, as their first parameter, the target of the object to modify. This references one of the bound objects.
The enumerators GL_OBJECT_*
all name fields in the object that
can be set. The glObjectParameter
family of functions set
parameters within the object bound to the given target. Note that since OpenGL is a
C API, it has to name each of the differently typed variations differently. So there
is glObjectParameteri
for integer parameters,
glObjectParameterf
for floating-point parameters, and so
forth. In terms of code, you can think of these functions as follows:
//glObjectParameteri(GL_MODIFY, GL_OBJECT_COUNT, 5); GL_MODIFY->count = 5; //glObjectParameterf(GL_MODIFY, GL_OBJECT_OPACITY, 0.4f); GL_MODIFY->opacity = 0.4f; ...
Binding the object 0 to the a target in the context is the equivalent of setting
that target's global pointer to NULL
. It unbinds whatever object
is currently bound to that target.
Note that all OpenGL objects are not as simple as this example, and the functions that change object state do not always follow these naming conventions. We will discuss exceptions as we get to them.
To be technical about it, OpenGL is not an API; it is a specification. A document. The C API is merely one way to implement the spec. The specification defines the initial OpenGL state, what each function does to change or retrieve that state, and what is supposed to happen when you call a rendering function.
The specification is written by the OpenGL Architectural Review Board (ARB), a group of representatives from companies like Apple, NVIDIA, and AMD (the ATI part), among others. The ARB is part of the Khronos Group.
The specification is a very complicated and technical document. However, parts of it are quite readable, though you will usually need at least some understanding of what should be going on to understand it. If you try to read it, the most important thing to understand about it is this: it describes results, not implementation. Just because the spec says that X will happen does not mean that it actually does. What it means is that the user should not be able to tell the difference. If a piece of hardware can provide the same behavior in a different way, then the specification allows this, so long as the user can never tell the difference.
OpenGL Implementations. While the OpenGL ARB does control the specification, it does not control OpenGL's code. OpenGL is not something you download from a centralized location. For any particular piece of hardware, it is up to the developers of that hardware to write an OpenGL Implementation for that hardware. Implementations, as the name suggests, implement the OpenGL specification, exposing the OpenGL API as defined in the spec.
Who controls the OpenGL implementation is different for different operating systems. On Windows, OpenGL implementations are controlled virtually entirely by the hardware makers themselves. On Mac OSX, OpenGL implementations are controlled by Apple; they decide what version of OpenGL is exposed and what additional functionality can be provided to the user. Apple writes much of the OpenGL implementation on Mac OSX, with the hardware developers writing to an Apple-created internal driver API. On Linux, things are... complicated.
The long and short of this is that if you are writing a program and it seems to be exhibiting off-spec behavior, that is the fault of the maker of your OpenGL implementation (assuming it is not a bug in your code). On Windows, the various graphics hardware makers put their OpenGL implementations in their regular drivers. So if you suspect a bug in their implementation, the first thing you should do is make sure your graphics drivers are up-to-date; the bug may have been corrected since the last time you updated your drivers.
OpenGL Versions. There are many versions of the OpenGL Specification. OpenGL versions are not like most Direct3D versions, which typically change most of the API. Code that works on one version of OpenGL will almost always work on later versions of OpenGL.
The only exception to this deals with OpenGL 3.0 and above, relative to previous versions. v3.0 deprecated a number of older functions, and v3.1 removed most of those functions from the API[1]. This also divided the specification into 2 variations (called profiles): core and compatibility. The compatibility profile retains all of the functions removed in 3.1, while the core profile does not. Theoretically, OpenGL implementations could implement just the core profile; this would leave software that relies on the compatibility profile non-functional on that implementation.
As a practical matter, none of this matters at all. No OpenGL driver developer is going to ship drivers that only implement the core profile. So in effect, this means nothing at all; all OpenGL versions are all effectively backwards compatible.
[1] Deprecation only means marking those functions as to be removed in later functions. They are still available for use in 3.0.