"Hello, OpenGL": Simplest Implementation

In this simple standalone version, you will see the bulk of the control, data, and data transmission patterns between the CPU and the GPU. While studying this example, it will be helpful to visualize what is happening by referring to the following two diagrams:

Referring to hello.c++, you will see the four key steps for basic scene generation and rendering:

  1. Creation of a window on the display along with its Rendering Context (RC).
  2. Creation of a GLSL shader interface (ShaderIF) object. The constructor is given the GLSL shader program code. It compiles, links, and sends the program to the GPU. Methods are provided to look up PVA attribute variable locartions (in variables to the vertex shader) and uniform variables.
  3. Creation of the geometry of a model (just our single triangle in this case) and its one-time transmission to the GPU in a VAO-VBO pair. (Defined below.)
  4. Handing off control to the window manager which will then monitor and handle events. The example here illustrates the simplest case in which events are actually ignored, but will cause a display update when "run" calls "handleDisplay".

Each call to the OpenGL routine glDrawArrays (see "handleDisplay") triggers the rendering of a primitive, which in turn triggers execution of the OpenGL pipeline.

A Quick Note on Function Name Conventions
OpenGL and many window manager interfaces typically used with it are written in pure ANSI C, hence they are not class-based, their names are not polymorphic, and they do not have namespace associations. As a result, these OpenGL and window manager related functions obey a naming convention in which the first few letters are lowercase and identify the "package" to which they belong. After this prefix, the rest of the function name indicates the purpose of the function and follows a typical "CamelCase" convention. The primary lower case prefixes we will see in this course are:
  • gl: A "core" OpenGL function.
  • glu: An OpenGL "utility" function, typically some tool useful for certain special types of geometry creation. An example is the OpenGL polygon tessellation facility we will study later.
  • glfw: A function belonging to the GLFW window manager interface.

Window manager interfaces such as GLFW and GLUT are not considered to be an actual part of OpenGL itself. Hence, whenever we refer to an "OpenGL function call", we mean a function whose complete prefix is either gl or glu.

In addition to these prefixes, several OpenGL functions have suffixes that identify the number and type of some of their parameters. For example, glUniform1i is an OpenGL function that uses a single integer parameter to set a "uniform" variable. On the other hand, glUniform4fv is another version of glUniform* that uses four floating point parameters passed in an array of float to set a different "uniform" variable.

Let us now look at the four steps listed above in a bit more detail. Referring to the code in hello.c++, the four steps above are performed, respectively, by the following code in main:

  1. createWindowAndRC
    OpenGL is a state machine, and state is maintained on a per-window basis in a structure called the Rendering Context (RC). The function glfwInit (called from the main program here) does general initialization of the window system interface. Calls to glfwWindowHint (from createWindowAndRC here) specify properties that we wish the next created RC to have. The ones you will see in createWindowAndRC primarily specify that we want to use only non-deprecated shader-based OpenGL functionality. When glfwCreateWindow is called, the window appears on the screen, and the RC is created with default settings for anything not otherwise requested via glfwWindowHint calls.

    No OpenGL function call can be executed before the RC is created!

  2. The ShaderIF object that is created first compiles and links the GLSL program whose pieces it is given in its constructor parameters. If compilation and linking is successful, the compiled and linked GLSL program will be sent to the GPU where it will be executed.

    The GLSL language is basically a subset of C/C++ with extensions. You may want to quickly glance at the GLSL code here and/or quickly look at the GLSL Overview page. But don't get lost down that rabbit hole just now; we will have plenty of time to study all that later!

    The ShaderIF object continues to be used as the CPU program executes. In this example, we see it being used when making the GLSL GPU program "active" (see glUseProgram). In later examples we will study, we will also see it used when sending attribute data to the GLSL program on the GPU.

  3. initModelGeometry (Refer to this VAO-VBO storage diagram)
    The data for an OpenGL object to be displayed is stored in a vertex array object (VAO). A VAO encapsulates geometry and related attributes whose values can change from one vertex to another. Each such attribute (coordinates, color, etc.) is sent to the GPU in a vertex buffer object (VBO). In general, one VAO can reference an arbitrary number of VBOs. In this example, our VAO references a single VBO, which holds the coordinates of our triangle.

    VAOs and VBOs are "named" using unsigned integers. Each such "name" must be assigned by an OpenGL name broker routine. VAO names are assigned by glGenVertexArrays; VBO names are assigned by glGenBuffers. Note that the interface to the name broker routines allows an arbitrary number of names to be assigned at once. Here we are asking for only a single name of each type.

    We make the VAO and VBOs active (i.e., open for editing or use) by binding them. Binding is accomplished by passing a single assigned name to glBindVertexArray (for a VAO) or glBindBuffer (for VBOs).

    Once a VBO has been bound, the routine glBufferData can be used to both (i) dynamically allocate GPU storage for the buffer and (ii) send CPU data to the dynamically allocated GPU buffer storage.

    It is important to note that glBufferData sends the data as an anonymous array of bytes. (Note, for example, that the size of the data is specified in bytes.) The glVertexAttribPointer call tells the GPU "vertex fetch" processor how to extract data from a VBO to be associated with a vertex during rendering time.

    Finally, the glEnableVertexAttribArray call tells the "vertex fetch" processor (see the OpenGL pipeline) to actually obtain values for this attribute from the VBO. It might seem like you would always want to do this, but we will see an alternative possibility and motivations for it later.

  4. run
    The interactive, animation, and other real-time aspects of graphics programs are managed by event-handling facilities of the window manager interfaces. A typical approach is to utilize an "event loop" that continuously looks for events, and responds by passing the event along with event-specific data to registered callback functions.

    In "run", the call to glfwWaitEvents hands off control to the GLFW event loop. Control does not return from this call until an event has occurred. The code in "run" assumes that the image must be redrawn after each event (not an unreasonable assumption), hence the call to the "display callback" function handleDisplay. It is the call to glDrawArrays in handleDisplay that actually triggers execution of the OpenGL pipeline on the GPU.

    The while loop inside "run" continually returns control to the GLFW event loop after each event has been processed and the display updated. The glfwWindowShouldClose function returns true if, for example, the user clicks the "X" in the graphics window title bar.

Important Terms

A single VAO-VBO set was used to render the single primitive in this example. This will continue to be the case for many of our introductory examples. However, it is extremely common for one VAO-VBO set to be used to specify many primitives, so be sure to remember the more precise characterization of "primitive" shown above, especially when trying to understand the effect of "per-primitive attributes". (We will see one example shortly when discussing the sample program that illustrates OpenGL's "drawing modes".)