78 5.DelayingOpenGLCalls
fore finally issuing a draw call. In this case, glUseProgram() and glUni-
form1f()
would be called three times each. The other downside is that
glUseProgram() is called each time a uniform changes. Ideally, it would be
called only once before all of a program’s uniforms change.
Of course, it is possible to keep track of the currently bound program in addi-
tion to the current value of each uniform. The problem is that this becomes diffi-
cult with multiple contexts and multiple threads. The currently bound program is
context-level global state, so each uniform instance in our abstraction needs to be
aware of the current thread’s current context. Also, tracking the currently bound
program in this fashion is error prone and susceptible to thrashing when different
uniforms are set for different programs in an interleaved manner.
5.4DelayedCallsImplementation
In order to come up with a clean and efficient implementation for our uniform
abstraction, observe that it doesn’t matter what value OpenGL thinks a uniform
has until a draw call is issued. Therefore, it is not necessary to call into OpenGL
when a user provides a value for a uniform. Instead, we keep a list of uniforms
that were changed on a per-program basis and make the necessary
glUni-
form1f()
calls as part of a draw command. We call the list of changed uniforms
the program’s dirty list. We clean the list by calling
glUniform1f() for each
uniform and then clear the list itself. This is similar to a cache where dirty cache
lines are flushed to main memory and marked as clean.
An elegant way to implement this delayed technique is similar to the observ-
er pattern [Gamma et al. 1995]. A shader program “observes” its uniforms.
When a uniform’s value changes, it notifies its observer (the program), which
adds the uniform to the dirty list. The dirty list is then cleaned as part of a draw
command.
The observer pattern defines a one-to-many dependency between objects.
When an object changes, its dependents are notified. The object that changes is
called the subject and its dependents are called observers. For us, the situation is
simplified: each uniform is a subject with only one observer—the program. Since
we are interested in using this technique for abstracting other areas of OpenGL,
we introduce the two generic interfaces shown in Listing 5.5.
The shader program class will implement
ICleanableObserver, so it can
add a uniform to the dirty list when it is notified that the uniform changed. The
uniform class will implement
ICleanable so it can call glUniform1f() when
the dirty list is cleaned. These relationships are shown in Figure 5.1.