Supplying the Graphics Processor with Data
Programs store the data for 3D scenes in hardware random access memory (RAM). The embedded system’s central processing unit (CPU) has access to some RAM that is dedicated for its own exclusive use. The GPU also has RAM dedicated for exclusive use during graphics processing. The speed of rendering 3D graphics with modern hardware depends almost entirely on the ways the different memory areas are accessed.
OpenGL ES is a software technology. Portions of OpenGL ES execute on the CPU and other parts execute on the GPU. OpenGL ES straddles the boundary between the two processors and coordinates data exchanges between the memory areas. The arrows in Figure 1.4 identify data exchanges between the hardware components involved in 3D rendering. Each of the arrows also represents a bottleneck to rendering performance. OpenGL ES usually coordinates data exchanges efficiently, but the ways programs interact with OpenGL ES can dramatically increase or decrease the number and types of data exchanges needed. With regard to rendering speed, the fastest data exchange is the one that is avoided.
Figure 1.4 Relationships between hardware components and OpenGL ES.
First and foremost, copying data from one memory area to another is relatively slow. Even worse, unless care is taken, neither the GPU nor CPU can use the memory for anything else while memory copying takes place. Therefore, copying between memory areas needs to be avoided when possible.
Second, all memory accesses are relatively slow. A current embedded CPU can readily complete about a billion operations per second, but it can only read or write memory about 200 million times per second. That means that unless the CPU can usefully perform five or more operations on each piece of data read from memory, the processor is performing sub-optimally and is called “data starved.” The situation is even more dramatic with GPUs, which complete several billion operations per second under ideal conditions but can still only access memory about 200 million times per second. GPUs are almost always limited by memory access performance and can usually perform 10 to 30 operations on each piece of data without degradation in overall graphics output.
One way to summarize the difference between modern OpenGL ES and older versions of OpenGL is that OpenGL ES dropped support for archaic and inefficient memory copying operations in favor of new streamlined approaches. If you have ever programmed desktop OpenGL the old way, forget those experiences now. Most of the worst techniques don’t work in modern embedded systems anyway. OpenGL ES still provides several ways to supply data to the graphics processor, but only one “best” way exists, and it’s used consistently in this book.
Buffers: The Best Way to Supply Data
OpenGL ES defines the concept of buffers for exchanging data between memory areas. A buffer is a contiguous range of RAM that the graphics processor can control and manage. Programs copy data from the CPU’s memory into OpenGL ES buffers. After the GPU takes ownership of a buffer, programs running on the CPU ideally avoid touching the buffer again. By exclusively controlling the buffer, the GPU reads and writes the buffer memory in the most efficient way possible. The graphics processor applies its number-crunching power to buffers asynchronously and concurrently, which means the program running on the CPU continues to execute while the GPU simultaneously works on data in buffers.
Nearly all the data that programs supply to the GPU should be in buffers. It doesn’t matter if a buffer stores geometric data, colors, hints for lighting effects, or other information. The seven steps to supply data in a buffer are
- Generate—Ask OpenGL ES to generate a unique identifier for a buffer that the graphics processor controls.
- Bind—Tell OpenGL ES to use a buffer for subsequent operations.
- Buffer Data—Tell OpenGL ES to allocate and initialize sufficient contiguous memory for a currently bound buffer—often by copying data from CPU-controlled memory into the allocated memory.
- Enable or Disable—Tell OpenGL ES whether to use data in buffers during subsequent rendering.
- Set Pointers—Tell OpenGL ES about the types of data in buffers and any memory offsets needed to access the data.
- Draw—Tell OpenGL ES to render all or part of a scene using data in currently bound and enabled buffers.
- Delete—Tell OpenGL ES to delete previously generated buffers and free associated resources.
Ideally, each generated buffer is used for a long time (possibly the entire lifetime of the program). Generating, initializing, and deleting buffers sometimes require time-consuming synchronization between the graphics processor and the CPU. Delays are incurred because the GPU must complete any pending operations that use the buffer before deleting it. If a program generates and deletes buffers thousands of times per second, the GPU might not have time to accomplish any rendering.
OpenGL ES defines the following C language functions to perform each step in the process for using one type of buffer and provides similar functions for other types of buffer.
- glGenBuffers()—Asks OpenGL ES to generate a unique identifier for a buffer that the graphics processor controls.
- glBindBuffer()—Tells OpenGL ES to use a buffer for subsequent operations.
- glBufferData() or glBufferSubData()—Tells OpenGL ES to allocate and initialize sufficient contiguous memory for a currently bound buffer.
- glEnableVertexAttribArray() or glDisableVertexAttribArray()—Tells OpenGL ES whether to use data in buffers during subsequent rendering.
- glVertexAttribPointer()—Tells OpenGL ES about the types of data in buffers and any offsets in memory needed to access data in the buffers.
- glDrawArrays() or glDrawElements()—Tells OpenGL ES to render all or part of a scene using data in currently bound and enabled buffers.
- glDeleteBuffers()—Tells OpenGL ES to delete previously generated buffers and free associated resources.
The Frame Buffer
The GPU needs to know where to store rendered 2D image pixel data in memory. Just like buffers supply data to the GPU, other buffers called frame buffers receive the results of rendering. Programs generate, bind, and delete frame buffers like any other buffers. However, frame buffers don’t need to be initialized because rendering commands replace the content of the buffer when appropriate. Frame buffers are implicitly enabled when bound, and OpenGL ES automatically configures data types and offsets based on platform-specific hardware configuration and capabilities.
Many frame buffers can exist at one time, and the GPU can be configured through OpenGL ES to render into any number of frame buffers. However, the pixels on the display are controlled by the pixel color element values stored in a special frame buffer called the front frame buffer. Programs and the operating system seldom render directly into the front frame buffer because that would enable users to see partially completed images while rendering takes place. Instead, programs and the operating system render into other frame buffers including the back frame buffer. When the rendered back frame buffer contains a complete image, the front frame buffer and the back frame buffer are swapped almost instantaneously. The back frame buffer becomes the new front frame buffer and the old front frame buffer becomes the back frame buffer. Figure 1.5 illustrates the relationships between the pixels onscreen, the front frame buffer, and the back frame buffer.
Figure 1.5 The front frame buffer controls pixel colors on the display and is swapped with the back frame buffer.