♦
|
![]() |
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:
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:
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:
No OpenGL function call can be executed before the RC is created!
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.
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.
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.
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".)