< Previous PageNext Page > Hide TOC

Techniques for Working with Rendering Contexts

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.

In this section:

Context Parameters
Updating a Rendering Context
Sharing Rendering Contexts
See Also


Context Parameters

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:

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.

Swap Interval

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:

Listing 6-1  Using CGL to set up synchronization

long sync = 1;
// ctx must be a valid context
CGLSetParameter (ctx, kCGLCPSwapInterval, &sync);

Surface Opacity

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:

Listing 6-2  Using CGL to set surface opacity

long opaque = 0;
// ctx must be a valid context
CGLSetParameter (ctx, kCGLCPSurfaceOpacity, &opaque);

Surface Drawing Order

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:

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);

Vertex and Fragment Processing

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;

Back Buffer Size Control

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:

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);

Figure 6-1  A fixed size back buffer and variable size front buffer

A fixed size back buffer and variable size front buffer

Updating a Rendering Context

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.

Tracking Renderer Changes

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 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.

Updating a Rendering Context for a Custom Cocoa View

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

Updating a Rendering Context for a Carbon Window

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:

  1. Stores the rendering context, which is passed to the event handler through the userData parameter.

  2. Passes the activation event through, which prevents an initial flash of the screen.

  3. Handles a draw content event by calling your window update function.

  4. Handles a window close event by calling your dispose function to perform the necessary cleanup work.

  5. 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.

  6. Handles a window bounds changed event by resizing the window appropriately and then updating the content.

  7. 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;
  }
}

Updating Full-screen AGL and CGL Rendering Contexts

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:

  1. 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.

  2. Sets the context to the current context.

  3. Attaches a full-screen drawable object to the context to ensure the context is updated.

  4. Gets the display capabilities of the display, which are the width, height, and screen resolution.

  5. 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);
}

Sharing Rendering Contexts

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.


Figure 6-2  Shared contexts attached to the same drawable object

Shared contexts attached to the same drawable object

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:


Figure 6-3  Shared contexts and more than one drawable object

Shared contexts and more than one drawable 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.

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);

See Also

OpenGL sample code projects (Sample Code > Graphics & Imaging > OpenGL):



< Previous PageNext Page > Hide TOC


© 2004, 2008 Apple Inc. All Rights Reserved. (Last updated: 2008-06-09)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.