A rendering context is a container for state information. When you designate a rendering context as the current rendering context, subsequent OpenGL commands modify the drawable object associated with that context. The actual drawing surfaces are never really owned by the rendering context but are created, as needed, only when the rendering context is actually attached to a drawable object. You can attach multiple rendering contexts to a set of drawing surfaces. Each context draws with its own unique “pen” represented by its current state.
“Drawing to a Window or View,” “Drawing to the Full Screen,” and “Drawing Offscreen” show how to create a rendering context and attach it to a drawable object. As you'll recall, each of the Apple-specific OpenGL APIs provides a routine that's fairly easy to use for creating a rendering context. This chapter goes beyond creating rendering contexts; it shows how to set context parameters, update rendering contexts, and set up a shared context.
Context Parameters
Updating a Rendering Context
Sharing Rendering Contexts
See Also
A rendering context has a variety of parameters that you can set to suit the needs of your OpenGL drawing. Some of the most useful, and often overlooked, context parameters are discussed in this section: swap interval, surface opacity, surface drawing order, and back-buffer size control.
Each of the Apple-specific OpenGL APIs provides a routine for setting and getting rendering context parameters:
The setValues:forParameter:
method of the NSOpenGLContext
class takes as arguments a list of values and a list of parameters.
The aglSetInteger
function takes as parameters a rendering context, a constant that specifies an option, and a value for that option.
The CGLSetParameter
function takes as parameters a rendering context, a constant that specifies an option, and a value for that option.
Some parameters need to be enabled for their values to take effect. The reference documentation for a parameter indicates whether a parameter needs to be enabled. See NSOpenGLContext Class Reference, AGL Reference, and CGL Reference.
The swap interval parameter synchronizes the vertical retrace. If the swap interval is set to 0
(the default), buffers are swapped as soon as possible, without regard to the vertical refresh rate of the monitor. If the swap interval is set to any other value, the buffers are swapped only during the vertical retrace of the monitor. For more information, see “Draw Only When Necessary.”
You can use the following constants to specify that you are setting the swap interval value:
For Cocoa, use NSOpenGLCPSwapInterval
.
For Carbon, use AGL_SWAP_INTERVAL
.
If you are using the CGL API, use kCGLCPSwapInterval
. See Listing 6-1.
Listing 6-1 Using CGL to set up synchronization
long sync = 1; |
// ctx must be a valid context |
CGLSetParameter (ctx, kCGLCPSwapInterval, &sync); |
OpenGL surfaces are typically rendered as opaque. Thus the background color for pixels with alpha values of 0.0
is the surface background color. If you set the value of the surface opacity parameter to 0
, then the contents of the surface are blended with the contents of surfaces behind the OpenGL surface. This operation is equivalent to OpenGL blending with a source contribution proportional to the source alpha and a background contribution proportional to 1
minus the source alpha. A value of 1
means the surface is opaque (the default); 0
means completely transparent.
You can use the following constants to specify that you are setting the surface opacity value:
For Cocoa, use NSOpenGLCPSurfaceOpacity
.
For Carbon, use AGL_SURFACE_OPACITY
.
If you are using the CGL API, use kCGLCPSurfaceOpacity
. See Listing 6-2.
Listing 6-2 Using CGL to set surface opacity
long opaque = 0; |
// ctx must be a valid context |
CGLSetParameter (ctx, kCGLCPSurfaceOpacity, &opaque); |
The surface drawing order parameter specifies the position of the OpenGL surface relative to the window. A value of 1
means that the position is above the window; a value of –1
specifies a position that is below the window. When you have overlapping views, setting the order to -1
causes OpenGL to draw underneath, 1
causes OpGL to draw on top. This parameter is useful for drawing user interface controls on top of an OpenGL view.
You can use the following constants to specify that you are setting the surface drawing order value:
For Cocoa, use NSOpenGLCPSurfaceOrder
.
For Carbon, use AGL_SURFACE_ORDER
.
If you are using the CGL API, use kCGLCPSurfaceOrder
. See Listing 6-3.
Listing 6-3 Using CGL to set surface drawing order
long order = –1; // below window |
// ctx must be a valid context |
CGLSetParameter (ctx, kCGLCPSurfaceOrder, &order); |
CGL provides two parameters for checking whether the system is using the GPU for processing: kCGLCPGPUVertexProcessing
and kCGLCPGPUFragmentProcessing
. To check vertex processing, pass the vertex constant to the CGLGetParameter
function. To check fragment processing, pass the fragment constant to CGLGetParameter
.
Important: Although you can perform these queries at any time, keep in mind that such queries force an internal state validation, which can impact performance. For best performance, do not use these queries inside your drawing loop. Instead, perform the queries once at initialization or context setup time to determine whether OpenGL is using the CPU or the GPU for processing, and then act appropriately in your drawing loop.
Listing 6-4 Using CGL to check whether the GPU is processing vertices and fragments
BOOL gpuProcessing; |
GLint fragmentGPUProcessing, vertexGPUProcessing; |
CGLGetParameter (CGLGetCurrentContext(), kCGLCPGPUFragmentProcessing, |
&fragmentGPUProcessing); |
CGLGetParameter(CGLGetCurrentContext(), kCGLCPGPUVertexProcessing, |
&vertexGPUProcessing); |
gpuProcessing = (fragmentGPUProcessing && vertexGPUProcessing) ? YES : NO; |
Normally, the back buffer is the same size as the window or view that it's drawn into, and it changes size when the window or view changes size. For a window whose size is 720 by 480 pixels, the OpenGL back buffer is sized to match. If the window grows to 1024 by 768 pixels, for example, then the back buffer tracks this growth. If you do not want this behavior, use the back buffer size control parameter.
Using this parameter fixes the size of the back buffer and lets the system scale the image automatically when it moves the data to a variable size buffer (see Figure 6-1). The size of the back buffer remains fixed at the size that you set up regardless of whether the image is resized to display larger onscreen.
You can use the following constants to specify that you are setting the surface drawing order value:
If you are using the CGL API, use kCGLCPSurfaceBackingSize
, as shown in Listing 6-5.
For Carbon, use AGL_SURFACE_BACKING_SIZE
.
Listing 6-5 Using CGL to set up back buffer size control
long dim[2] = {720, 480}; |
// ctx must be a valid context |
CGLSetParameter(ctx, kCGLCPSurfaceBackingSize, dim); |
CGLEnable (ctx, kCGLCESurfaceBackingSize); |
A rendering context must be updated whenever the renderer or geometry changes. A renderer change can occur when the user drags a window from one display to another. Geometry changes occur when the display mode changes or when a a window is resized or moved. If the system does not update the context automatically, then your application must perform the update. You need to track the appropriate events and call the update function provided by the Apple-specific OpenGL API that you're using.
Updating a rendering context is not the same as flushing graphics buffers. An update notifies the rendering context of geometry changes; it doesn't flush content. Calling an update function allows the OpenGL engine to ensure that the surface size is set and that the renderer is properly updated for any virtual screen changes. If you don't update the rendering context you either do not see any OpenGL rendering or you see rendering artifacts.
The routine that you call for updating determines how events related to renderer and geometry changes are handled. For applications that subclass NSOpenGLView
, Cocoa calls the update
method automatically. Applications that use the NSOpenGLContext
class without subclassing its view must call the update
method of NSOpenGLContext
directly. For a full-screen Cocoa application, calling the setFullScreen
method of NSOpenGLContext
ensures that depth, size, or display changes take affect.
Carbon applications drawing OpenGL content to a window should call the function aglUpdateContext
. For full-screen CGL and AGL applications, you need to call CGLSetFullScreen
and aglSetFullScreen
respectively to ensure that depth, size, or display changes take affect rather than calling an update function.
Your application must update the rendering context after the system event but before drawing to the context. If the drawable object is resized, you may want to issue a glViewport
command to ensure that the content scales properly.
Note: Some system-level events (such as display mode changes) that require a context update could reallocate the buffers of the context, thus you need to redraw the entire scene after all context updates.
It's important that you don't update rendering contexts more than necessary. Your application should respond to system-level events and notifications rather than updating every frame. For example, you'll want to respond to window move and resize operations and to display configuration changes such as a color depth change.
The sections that follow describe in more detail how to use Cocoa, AGL, and CGL to update a rendering context, but you'll want to read “Tracking Renderer Changes” before going on to the sections specific to the three APIs.
It's fairly straightforward to track geometry changes, but how are renderer changes tracked? This is where the concept of a virtual screen becomes important (see “Virtual Screens”). A change in the virtual screen indicates a renderer change, a change in renderer capability, or both. When your application detects a window resize event, window move event, or display change, it can then check for a virtual screen change and respond to the change appropriately. This ensures that the current application state reflects any changes in renderer capabilities.
Each of the Apple-specific OpenGL APIs has a function that returns the current virtual screen number:
The currentVirtualScreen
method of the NSOpenGLContext
class
The aglGetVirtualScreen
function
The CGLGetVirtualScreen
function
The virtual screen number represents an index in the list of virtual screens that were set up specifically for the pixel format object used for the rendering context. The number is unique to the list but is meaningless otherwise.
If you subclass NSView
instead of using the NSOpenGLView
class, your application must update the rendering context. That's due to a slight difference between the events normally handled by the NSView
class and those handled by the NSOpenGLView
class. Cocoa does not call a reshape
method for the NSView
class when the size changes because that class does not export a reshape
method to override. Instead, you need to perform reshape operations directly in your drawRect:
method, looking for changes in view bounds prior to actually drawing content. Using this approach provides results that are equivalent to using the reshape
method of the NSOpenGLView
class.
Listing 6-6 is a partial implementation of a custom view that shows how to handle context updates. The update
method is called after move, resize, and display change events and when the surface needs updating. The class adds an observer to the notification NSViewGlobalFrameDidChangeNotification
, giving a callback upon which to base context updates. This notification is posted whenever an NSView
object that has attached surfaces (that is, NSOpenGLContext
objects) resizes, moves, or changes coordinate offsets.
It's slightly more complicated to handle changes in the display configuration. For that, you need to register for the notification NSApplicationDidChangeScreenParametersNotification
through the NSApplication
class. This notification is posted whenever the configuration of any of the displays attached to the computer is changed (either programmatically or when the user changes the settings in the interface).
Listing 6-6 Handling context updates for a custom view
#import <Cocoa/Cocoa.h> |
#import <OpenGL/OpenGL.h> |
#import <OpenGL/gl.h> |
@class NSOpenGLContext, NSOpenGLPixelFormat; |
@interface CustomOpenGLView : NSView |
{ |
@private |
NSOpenGLContext* _openGLContext; |
NSOpenGLPixelFormat* _pixelFormat; |
} |
- (id)initWithFrame:(NSRect)frameRect |
pixelFormat:(NSOpenGLPixelFormat*)format; |
- (void)update; |
@end |
@implementation CustomOpenGLView |
- (id)initWithFrame:(NSRect)frameRect |
pixelFormat:(NSOpenGLPixelFormat*)format |
{ |
self = [super initWithFrame:frameRect]; |
if (self != nil) { |
_pixelFormat = [format retain]; |
[[NSNotificationCenter defaultCenter] addObserver:self |
selector:@selector(_surfaceNeedsUpdate:) |
name:NSViewGlobalFrameDidChangeNotification |
object:self]; |
} |
return self; |
} |
- (void)dealloc |
[[NSNotificationCenter defaultCenter] removeObserver:self |
name:NSViewGlobalFrameDidChangeNotification |
object:self]; |
[self clearGLContext]; |
[_pixelFormat release]; |
[super dealloc]; |
} |
- (void)update |
{ |
if ([_openGLContext view] == self) { |
[_openGLContext update]; |
} |
} |
- (void) _surfaceNeedsUpdate:(NSNotification*)notification |
{ |
[self update]; |
} |
@end |
The simplest way to handle resize and move events is by using Carbon events. To cover window resize and move operations, your application can handle the events kEventWindowBoundsChanged
and kEventWindowZoomed
. The system generates the event kEventWindowBoundsChanged
for window resize and window drag operations, which takes care of most cases other than a window zoom operation. For that, track the event kEventWindowZoomed
. For more information on these and other Carbon events see Carbon Event Manager Programming Guide and Carbon Event Manager Reference.
Listing 6-7 demonstrates a simple window event handler. Note that the supporting routines needed by the window event handler—MyHandleWindowUpdate
, MyDisposeGL
, and MyBuildGL
—are not shown in the listing. These are routines that you need to write. A detailed explanation for each numbered line of code appears following the listing.
Listing 6-7 Handling Carbon events associated with an AGL context
#include <Carbon/Carbon.h> |
static pascal OSStatus windowEvtHndlr (EventHandlerCallRef myHandler, |
EventRef event, |
void* userData) |
{ |
WindowRef window; |
AGLContext aglContext = (AGLContext) userData; // 1 |
Rect rectPort = {0,0,0,0}; |
OSStatus result = eventNotHandledErr; |
UInt32 class = GetEventClass (event); |
UInt32 kind = GetEventKind (event); |
GetEventParameter(event, kEventParamDirectObject, typeWindowRef, |
NULL, sizeof(WindowRef), NULL, &window); |
if (window) { |
GetWindowPortBounds (window, &rectPort); |
} |
switch (class) { |
case kEventClassWindow: |
switch (kind) { |
case kEventWindowActivated: // 2 |
case kEventWindowDrawContent: // 3 |
MyHandleWindowUpdate(window); |
break; |
case kEventWindowClose: // 4 |
HideWindow (window); |
MyDisposeGL (window); |
break; |
case kEventWindowShown: // 5 |
MyBuildGL (window); |
if (window == FrontWindow ()) |
SetUserFocusWindow (window); |
InvalWindowRect (window, &rectPort); |
break; |
case kEventWindowBoundsChanged: //6) |
MyResizeGL (window, aglContext); |
MyHandleWindowUpdate(window); |
break; |
case kEventWindowZoomed: // 7 |
MyResizeGL (window, aglContext); |
break; |
} |
break; |
} |
return result; |
} |
Here's what the code does:
Stores the rendering context, which is passed to the event handler through the userData
parameter.
Passes the activation event through, which prevents an initial flash of the screen.
Handles a draw content event by calling your window update function.
Handles a window close event by calling your dispose function to perform the necessary cleanup work.
Handles a window shown event by calling your function that performs the necessary work to render OpenGL to the window and to make the window frontmost with user focus.
Handles a window bounds changed event by resizing the window appropriately and then updating the content.
Handles a zoom event by resizing the window.
The code to handle the context update is shown in Listing 6-8. In its simplest form this code ensures the context of interest is current and then updates the context. Your application can also call the function glViewport
to update the size of the drawable object to the current window size or to some other meaningful value. You might also want to update the projection matrix because the window dimensions have changed, and thus the relative geometry of the window has changed.
Listing 6-8 Updating a context using AGL
#include <Carbon/Carbon.h> |
#include <AGL/agl.h> |
#include <OpenGL/OpenGL.h> |
void MyUpdateContextAGL (WindowRef window, AGLContext aglContext) |
{ |
Rect rectPort; |
aglSetCurrentContext (aglContext); |
aglUpdateContext (aglContext); |
GetWindowPortBounds (window, &rectPort); |
glViewport (0, 0, rectPort.right - rectPort.left, |
rectPort.bottom - rectPort.top); |
/* Your code to update the projection matrix if needed */ |
} |
It's slightly more complicated to handle changes in display configuration. You can detect these using Display Manager callback functions. (See Display Manager Reference and Optimizing Display Modes and Window Arrangement With the Display Manager.) You need to provide a callback function that conforms to the DMExtendedNotificationProcPtr
callback. Then, after creating a universal procedure pointer to this function by calling the function NewDMExtendedNotificationUPP
, register this UPP by calling the function DMRegisterExtendedNotifyProc
.
Listing 6-9 shows the callback, the UPP creation and registration tasks, and other tasks you need for perform when handling display configuration changes. The callback function handleWindowDMEvent
is simple. It calls the context update routine and invalidates the full window graphics port bounds to force an update event. Make sure to check for the kDMNotifyEvent
message type; otherwise, the event is probably not one for which you need to update the context. If you use multiple rendering contexts or windows, it may be helpful to add the window or context to the user data.
Listing 6-9 Handling display configuration changes
#include <Carbon/Carbon.h> |
#include <AGL/agl.h> |
void handleWindowDMEvent (void *userData, |
short msg, void *notifyData) |
{ |
AGLContext aglContext = (AGLContext) userData; |
if (kDMNotifyEvent == msg) { |
MyUpdateContextAGL (window, aglContext); |
GetWindowPortBounds (window, &rectPort); |
InvalWindowRect (window, &rectPort); |
} |
} |
void setupDMNotify (WindowRef window) |
{ |
gWindowEDMUPP = NewDMExtendedNotificationUPP(handleWindowDMEvent); |
DMRegisterExtendedNotifyProc (gWindowEDMUPP, |
(void *)window, NULL, &psn); |
} |
OSStatus disposeDM Notify (WindowRef window) |
{ |
if (gWindowEDMUPP) { |
DisposeDMExtendedNotificationUPP (gWindowEDMUPP); |
gWindowEDMUPP = NULL; |
} |
} |
It's easier to update full-screen drawable objects than it is windowed ones since the drawable object position is fixed. Its size is directly linked to the display configuration, so full-screen applications need to perform updates only when they actually change the configuration. Instead of calling a context update routine, a full-screen application issues a set-full-screen call. Listing 6-10 and Listing 6-11 show examples of AGL and CGL routines, respectively, to reset the full-screen context.
For AGL, the aglSetFullScreen
function handles screen capture and display resizing; thus you just need to ensure that a valid full-screen pixel format object and rendering context are created prior to resizing. For CGL, you can use the Quartz Display Services functions CGCaptureAllDisplays
, CGDisplayBestModeForParametersAndRefreshRate
(or similar function), and CGDisplaySwitchToMode
to set the requested display configuration. Then set the pixel format for the display and call the resize function.
Note: When you capture all displays, using either the function aglSetFullScreen
(but without setting AGL_FS_CAPTURE_SINGLE
) or the function CGCaptureAllDisplays
, your application does not see any Display Manager notifications, because the display configuration is fixed and does not change until released. If you do not capture all displays, the application still receives display configuration changes for the noncaptured displays. Normally full-screen applications do not need to handle these display notifications, because they are for the displays not currently in use or of interest.
Listing 6-10 Handling full-screen updates using AGL
#include <Carbon/Carbon.h> |
#include <AGL/agl.h> |
#include <OpenGL/gl.h> |
void MyResizeAGLFullScreen (AGLContext aglContext, GLSizei width, |
GLSizei height) |
{ |
GLint displayCaps [3]; |
if (!aglContext) // 1 |
return; |
aglSetCurrentContext (aglContext);// 2 |
aglSetFullScreen (aglContext, width, height, 0, 0); // 3 |
aglGetInteger (aglContext, AGL_FULLSCREEN, displayCaps); // 4 |
glViewport (0, 0, displayCaps[0], displayCaps[1]); // 5 |
// Your code to update the projection matrix here if needed |
} |
Here's what the code does:
Checks for a valid context and returns if the context is not valid. Note that the MyResizeAGLFullScreen
function assumes that the pixel format object associated with the context was created with the full-screen attribute.
Sets the context to the current context.
Attaches a full-screen drawable object to the context to ensure the context is updated.
Gets the display capabilities of the display, which are the width, height, and screen resolution.
Sets the viewport, specifying (0,0)
as the left corner of the viewport rectangle, and the width and height of the screen (which were obtained with the previous call to aglGetInteger
).
Listing 6-11 assumes that the pixel format object associated with the context was created with the full-screen, single display, and pixel depth attributes. Additionally, this code assumes that the screen is captured and set to the requested dimensions. The viewport is not set here since the calling routine actually sets the display size.
Listing 6-11 Handling full-screen updates using CGL
#include <Carbon/Carbon.h> |
#include <OpenGL/OpenGL.h> |
#include <OpenGL/gl.h> |
void MyResizeCGL (CGLContextObj cglContext) |
{ |
if (!cglContext) |
return; |
CGLSetCurrentContext (cglContext); |
CGLSetFullScreen (cglContext); |
} |
A rendering context does not own the drawing objects attached to it, which leaves open the option for sharing. Rendering contexts can share resources and can be attached to the same drawable object (see Figure 6-2) or to different drawable objects (see Figure 6-3). You set up context sharing—either with more than one drawable object or with another context—at the time you create a rendering context.
Contexts can share object resources and their associated object state by indicating a shared context at context creation time. Shared contexts share all texture objects, display lists, vertex programs, fragment programs, and buffer objects created before and after sharing is initiated. The state of the objects are also shared but not other context state, such as current color, texture coordinate settings, matrix and lighting settings, rasterization state, and texture environment settings. You need to duplicate context state changes as required, but you need to set up individual objects only once.
When you create an OpenGL context you can designate a second context to share object resources. All sharing is peer to peer. Shared resources are reference-counted and thus are maintained until explicitly released or when the last context sharing resource is released.
Not every context can be shared with every other context. Much depends on ensuring that the same set of renderers supports both contexts. You can meet this requirement by ensuring each context uses the same virtual screen list, using either of the following techniques:
Use the same pixel format object to create all the rendering contexts that you want to share.
Create pixel format objects using attributes that narrow down the choice to a single display. This practice ensures that the virtual screen is identical for each pixel format object.
Setting up shared rendering contexts is very straightforward. Each Apple-specific OpenGL API provides functions with an option to specify a context to share in its context creation routine.
Use the share
argument for the initWithFormat:shareContext:
method of the NSOpenGLContext
class. See Listing 6-12.
Use the share
parameter for the function aglCreateContext
. See Listing 6-13.
Use the share
parameter for the function CGLCreateContext
. See Listing 6-14.
Listing 6-12 ensures the same virtual screen list by using the same pixel format object for each of the shared contexts.
Listing 6-12 Setting up an NSOpenGLContext object for sharing
#import <Cocoa/Cocoa.h> |
+ (NSOpenGLPixelFormat*)defaultPixelFormat |
{ |
NSOpenGLPixelFormatAttribute attributes [] = { |
NSOpenGLPFADoubleBuffer, |
(NSOpenGLPixelFormatAttribute)nil }; |
return [[(NSOpenGLPixelFormat *)[NSOpenGLPixelFormat alloc] |
initWithAttributes:attribs] autorelease]; |
} |
- (NSOpenGLContext*)openGLContextWithShareContext:(NSOpenGLContext*)context |
{ |
if (_openGLContext == NULL) { |
_openGLContext = [[NSOpenGLContext alloc] |
initWithFormat:[[self class] defaultPixelFormat] |
shareContext:context]; |
[_openGLContext makeCurrentContext]; |
[self prepareOpenGL]; |
} |
return _openGLContext; |
} |
- (void)prepareOpenGL |
{ |
// Your code here to initialize the OpenGL state |
} |
Listing 6-13 sets up two pixel formats objects—aglPixFmt
and aglPixFmt2
—that share the same display.
Listing 6-13 Getting the same virtual screen list with different attributes
GLint attrib[] = {AGL_RGBA, AGL_DOUBLEBUFFER, AGL_FULL_SCREEN, AGL_NONE}; |
GLint attrib2[] = {AGL_RGBA, AGL_DOUBLEBUFFER, AGL_NONE}; |
disp = GetMainDevice(); |
aglPixFmt = aglChoosePixelFormat(&disp, 1, attrib); |
aglContext = aglCreateContext(aglPixFmt, NULL); |
//Use same display |
aglPixFmt2 = aglChoosePixelFormat (&disp, 1, attrib2); |
aglContext2 = aglCreateContext(aglPixFmt2, aglContext); |
Listing 6-14 ensures the same virtual screen list by using the same pixel format object for each of the shared contexts.
Listing 6-14 Setting up a CGL context for sharing
#include <OpenGL/OpenGL.h> |
CGLPixelFormatAttribute attrib[] = {kCGLPFADoubleBuffer, 0}; |
CGLPixelFormatObj pixelFormat = NULL; |
long numPixelFormats = 0; |
CGLContextObj cglContext1 = NULL; |
CGLContextObj cglContext2 = NULL; |
CGLChoosePixelFormat (attribs, &pixelFormat, &numPixelFormats); |
CGLCreateContext(pixelFormat, NULL, &cglContext1); |
CGLCreateContext(pixelFormat, cglContext1, &cglContext2); |
OpenGL sample code projects (Sample Code > Graphics & Imaging > OpenGL):
GLCarbon1ContextPbuffer demonstrates using a single shared rendering context with OpenGL pixel buffer objects.
© 2004, 2008 Apple Inc. All Rights Reserved. (Last updated: 2008-06-09)