Each process in Mac OS X is made up of one or more threads. A thread is a stream of execution that runs code for the process. You can improve application performance and enhance the perceived responsiveness of the user interface when you set up your application to use multiple threads. On computers with one processor, multithreading can allow a program to execute multiple pieces of code independently. On computers with more than one processor, multithreading can allow a program to execute multiple pieces of code simultaneously.
Multithreading, however, is not the solution for all performance issues. When it is a possible solution, it enhances performance only when it's set up correctly. Getting multithreading to work properly in an OpenGL application requires advanced programming techniques—the OpenGL API is not inherently thread-safe. If you want to make your OpenGL program multithreaded, read this chapter to get started, then roll up your sleeves. Be prepared to undertake a lot of detective work if things go wrong. In threaded applications, the cause of the problem is often difficult to isolate.
Program Design
Guidelines for Threading OpenGL Applications
When Things Go Wrong
Threading APIs
See Also
You'll have the best chance of success with multithreading if you design your program with threading in mind. It's difficult, and often risky, to retrofit an existing OpenGL application to use multiple threads. Before you write any threading code, choose a strategy for dividing work among threads.
Consider using one of the following strategies for your OpenGL application:
Move OpenGL onto a separate thread.
Split OpenGL texture and vertex processing onto separate threads. You gain performance advantages by applying threads on single processor machines but threads are most efficient on computers with multiple CPUs since each processor can devote itself to a thread, potentially doubling the throughput.
For contexts on separate threads, share surfaces or OpenGL object state: display lists, textures, vertex and fragment programs, vertex array objects, and so on.
Applications that move OpenGL onto a separate thread are designed as shown in Figure 11-1. The CPU writes its data to a shared space, accessible to OpenGL. This design provides a clear division of labor and is fairly straightforward to implement. You can use this design to load data into your application on one thread, and then draw with the data on the other thread.
The Apple-specific OpenGL APIs provide the option for sharing data between contexts. You can leverage this feature in a threaded application by creating a separate thread for each of the contexts that share data, as shown in Figure 11-2. Shared resources are automatically set up as mutual exclusion (mutex) objects. Notice that Thread 2 draws to a pixel buffer that is linked to the shared state as a texture. Thread 1 can then draw using that texture.
Follow these guidelines to ensure successful threading in an application that uses OpenGL:
Use only one thread per context. OpenGL commands for a specific context are not reentrant. You should never have more than one thread accessing a single context simultaneously.
If for some reason you decide to set more than one thread to target the same context, then you must synchronize threads by placing a mutex around all OpenGL calls to the context, such as gl*
and CGL*
. You can use one of the APIs listed in “Threading APIs” to set up a mutex. OpenGL commands that block—such as fence
commands—do not synchronize threads.
Contexts that are on different threads can share object resources. For example, it is acceptable for one context in one thread to modify a texture and a second context in a second thread to modify the same texture. Why? Because the shared object handling provided by the Apple APIs automatically protects against thread errors. And, your application is following the "one thread per context" guideline.
When you use an NSOpenGLView
object with OpenGL calls that are issued from a thread other than the main one, you must set up mutex locking. Why? Unless you override the default behavior, the main thread may need to communicate with the view for such things as resizing.
Applications that use Objective-C with multithreading can lock contexts using the functions CGLLockContext
and CGLUnlockContext
, which were introduced in Mac OS X v10.4. If you want to perform rendering in a thread other than the main one, you can lock the context that you want to access and safely execute OpenGL commands. The locking calls must be placed around all of your OpenGL calls in all threads. You can't set up your own mutex in versions of Mac OS X earlier than v10.4.
CGLLockContext
blocks the thread it is on until all other threads have unlocked the same context using the function CGLUnlockContext
. You can use CGLLockContext
recursively. Context-specific CGL calls by themselves do not require locking, but you can guarantee serial processing for a group of calls by surrounding them with CGLLockContext
and CGLUnlockContext
. Keep in mind that calls from the OpenGL API (the API provided by the Architecture Review Board) require locking.
Keep track of the current context. When switching threads it is easy to switch contexts inadvertently, which causes unforeseen effects on the execution of graphic commands. You must set a current context when switching to a newly created thread.
Note: The guidelines in this section are specific to OpenGL applications. Any threading code that you write also needs to comply with general threading practices. You can find general resources for thread programming in the “See Also” section.
If you don't set up threading correctly, you'll most likely see your application freeze or crash. Things typically go wrong when your application introduces a command to the graphics processor that violates threading practices. The bad command will cause the processor to hang. The CPU blocks against that, causing any drawing onscreen to stop and the spinning wait cursor to appear.
You can use OpenGL Profiler to check thread safety in OpenGL. In the breakpoints window, set the "Break on thread error" option to check whether a problem is due to a thread error.
The following APIs are available for creating threaded applications in Mac OS X:
Foundation provides threading support for Cocoa application through the NSThread
class.
UNIX provides POSIX threads to support threading for any layer in Mac OS X.
Carbon provides thread support through the Multiprocessing Services API.
The OpenGL sample code project Vertex Optimization (available from Sample Code > Graphics & Imaging > OpenGL) has an option to run as a multithreaded application.
Multithreading programming guides and reference documentation:
Threading Programming Guide explains how to use threads in Cocoa applications.
NSThread Class Reference describes the Foundation threading class and its methods.
Multiprocessing Services Programming Guide explains how to implement preemptive tasks in Carbon applications.
Multiprocessing Services Reference describes the C API for creating preemptively scheduled tasks in Carbon applications.
"Debugging programs with multiple threads" in the "Running Programs Under GDB" chapter of Debugging with GDB provides useful information for any multithreaded application.
© 2004, 2008 Apple Inc. All Rights Reserved. (Last updated: 2008-06-09)