Mac OS X Reference Library Apple Developer
Search

Updating OpenGL Contexts


Q: When and why does an OpenGL context need to be updated?

A: OpenGL applications must update their OpenGL contexts when either the drawable geometry changes or the renderer could have changed. For windowed applications this is normally accomplished via the provided update API's, aglUpdateContext or the NSOpenGLContext's -update method. Full screen clients should re-issue the CGLSetFullScreen or aglSetFullScreen calls, instead of calling the update functions, to perform a context update. Collectively these functions allow the OpenGL engine to ensure the surface size is set and the renderer is properly updated for any virtual screen changes. Failure to update the context can result in rendering artifacts or complete lack of OpenGL output. Finally, the context update API is not without cost and thus should not be over used, it is strongly recommended clients make the appropriate context update calls in response to system level events and notifications rather than issuing an update every frame.

Again, actions which can affect drawable geometry or current renderer must be followed by a context update. In terms of system events, these map to drawable move, resize, coordinate offset, and display configuration change, including display depth changes. The client API in use determines how these events are handled. Cocoa applications sub-classing NSOpenGLView will have NSOpenGLView's -update method called automatically. This also applies to GLUT applications as the GLUT framework will ensure context updates are handled correctly. Clients written with Cocoa, but not sub-classing NSOpenGLView, should use NSOpenGLContext's -update method directly. Carbon windowed applications using the AGL API should call aglUpdateContext. For both full screen CGL and AGL applications, the client will need to re-issue CGLSetFullScreen and aglSetFullScreen respectively rather than calling an update function as these are required for depth, size or display changes to take affect. These update calls must occur after the system event requiring the update but before the client performs drawing to the context. Lastly, one may want to issue a glViewport command in cases where the drawable is resized to keep the client's OpenGL content scaled properly for the new drawable size. At this time code examples for each case are helpful.

Note:
Some of these system level events requiring a context update could reallocate the context's buffers, thus the entire scene should be redrawn after all context updates.



Cocoa NSOpenGLView

Cocoa applications which sub-class NSOpenGLView will see NSOpenGLView's -update method automatically called in all four cases which need to be handled. If clients wish to override this method to get visibility into context update events, they are free to do so but should always call [super update] prior to performing their own code thus ensuring the context has properly handled the update. Also, as mentioned previously, clients should use the -update method with care as it is called on window drag, periodically during live resize and for all display configuration changes, thus heavy processing here could make an application seem unresponsive on window drags or live resizes.


Cocoa Custom OpenGL View

Applications not sub-classing NSOpenGLView will want to use NSOpenGLContext directly and handle the update at the application level via NSOpenGLContext's -update method. The code in Listing 1 shows portions of a custom OpenGL view class. The class adds an observer to the NSViewGlobalFrameDidChangeNotification giving a callback upon which to base context updates. This notification will result in the same set of updates as NSOpenGLView receives, namely view resize, move, and coordinate offset and display configuration change. There is a slight difference in events received between this class and NSOpenGLView. The difference being that the -update method will be called for all cases required, including resizes, but no -reshape method will be called on size changes since NSView does not export a specific reshape method to override. It is recommended that clients handle reshapes directly in their -drawRect method, looking for changes in view bounds prior to actually drawing content. This approach does not specifically provide any improved or degraded performance relative to using NSOpenGLView's -reshape method but does provide equivalent results. While it is possible to hook into NSView's size changed logic, it is complicated and not normally worth the effort for OpenGL applications which are building a custom OpenGL view class.

#import <Cocoa/Cocoa.h>
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl.h>

// partial implementation of custom OpenGL view class
// showing method for handling context updates

@class NSOpenGLContext, NSOpenGLPixelFormat;

@interface CustomOpenGLView : NSView
{
  @private
  NSOpenGLContext*   _openGLContext;
  NSOpenGLPixelFormat* _pixelFormat;
}

- (id)initWithFrame:(NSRect)frameRect 
        pixelFormat:(NSOpenGLPixelFormat*)format;

// reshape is not supported, update bounds in drawRect
- (void)update;   // moved, resize, display change

@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
{ // get rid of the context and pixel format
  [[NSNotificationCenter defaultCenter] removeObserver:self 
                                 name:NSViewGlobalFrameDidChangeNotification
                                 object:self];
  [self clearGLContext];
  [_pixelFormat release];
  
  [super dealloc];
}

// ---------------------------------

// no reshape exists since NSView does not export a specific reshape method

// ---------------------------------

- (void)update
{
  if ([_openGLContext view] == self) {
    [_openGLContext update];
  }
}

// ---------------------------------

- (void) _surfaceNeedsUpdate:(NSNotification*)notification
{
  [self update];
}

@end

Listing 1. Custom OpenGL view class derived from NSView


GLUT

Applications using the GLUT framework do not have to update their OpenGL contexts or windows as GLUT performs proper context updates without interaction from the client. They should just ensure they correctly respond to the GLUT reshape callback to handle proper resizing from the user.


Carbon AGL

One will use the AGL framework for OpenGL clients written using the Carbon API. The simplest way to handled drawable resize and move events is via Carbon Events. Handling the kEventWindowBoundsChanged and kEventWindowZoomed window events is sufficient to cover drawable resize and move. kEventWindowBoundsChanged events are produced on both "live" window resizes and window drags thus handling the majority of required cases. The kEventWindowZoomed window event handles the additional window zoom case. For more information on these and other Carbon Events see the Carbon Events documentation and the CarbonEvents.h framework header. The code shown in Listing 2 demonstrates a simple window event handler which handles these (and other) events. In this example the window event handler calls the resizeGL routine which is shown in Listing 3.

#include <Carbon/Carbon.h>
// Note: Support routines handleWindowUpdate(), disposeGL(), 
//       buildGL() are left as an exercise for the reader, while 
//       resizeGL() is shown in Listing 3

static pascal OSStatus windowEvtHndlr (EventHandlerCallRef myHandler, 
                                       EventRef event, 
                                       void* userData)
{
  WindowRef     window;
  AGLContext    aglContext = (AGLContext) userData; // context storage
  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) {
    // handle other event types here
    case kEventClassWindow:
      switch (kind) {
        case kEventWindowActivated: // click activation and initially
          // pass through intentional to prevent initial flash
        case kEventWindowDrawContent:
          // call window update function, for example...
          handleWindowUpdate(window);
          break;
        case kEventWindowClose: // window is being closed (close box)
          HideWindow (window);
          // call OpenGL dispose function, for example...
          disposeGL (window);
          break;
        case kEventWindowShown: // initial show (not on un-minimize)
          // call OpenGL set up function, for example...
          buildGL (window);
          if (window == FrontWindow ())
            SetUserFocusWindow (window);
          InvalWindowRect (window, &rectPort);
          break;
        case kEventWindowBoundsChanged: // resize and moves (drag)
          resizeGL (window, aglContext);
          // call window update function, for example...
          handleWindowUpdate(window); // force update for live resize. 
          // Note: slow window redraw may affect drag performance 
          break;
        case kEventWindowZoomed: // user clicks on zoom button
          resizeGL (window, aglContext);
          break;
      }
      break;
  }
  return result;
}

Listing 2. Carbon Window Event Handler

The actual code to handle the context update is shown in Listing 3. In its simplest form this code ensures the context of interest is current with aglSetCurrentContext and then calls aglUpdateContext for that context. As previously noted, applications may also call glViewport to update the drawable size to the current window size, as shown in the code listing, or some other meaningful value. Additionally, clients may want to use this opportunity to update their projection matrix, if they do not already do this in their draw routine, since the window dimensions, and thus its relative geometry, has likely changed.

#include <Carbon/Carbon.h>
#include <AGL/agl.h>
#include <OpenGL/OpenGL.h>

// handles resizing of GL
// - need context update and if the window dimensions change, 
//   a window dimension update, resetting of view port

void resizeGL (WindowRef window, AGLContext aglContext)
{
  Rect rectPort;

  aglSetCurrentContext (aglContext);
  aglUpdateContext (aglContext);

  GetWindowPortBounds (window, &rectPort);
  glViewport (0, 0, rectPort.right - rectPort.left, 
                    rectPort.bottom - rectPort.top);
  // update projection matrix here if desired
}

Listing 3. Carbon Context Update Handler

Handling Carbon display configuration changes is slightly more complicated but still very reasonable. Display configuration changes can be detected using Display Manager callback functions. This API is also available in the Carbon framework, with the prototypes residing in the Displays.h header file. Developers need to provide a callback function conforming the DMExtendedNotificationProcPtr callback API. Then, after creating a Universal Procedure Pointer to this function via NewDMExtendedNotificationUPP, register this UPP with the Display Manager DMRegisterExtendedNotifyProc. If the client is using multiple contexts or windows it maybe helpful to add the window or context to the user data as shown in Listing 4. The callback function itself is a simple matter, consisting of a call to the context update routine, shown above in Listing 3 and invalidating the full window graphics port bounds to force an update event. One should always check that the event is actually a "notify" event by looking for kDMNotifyEvent as the message type sent to the callback. There are Display Manager events, other than the notify event, to which clients do not need to respond in handling context updates. Listing 4, shows the callback, the UPP creation and registration and finally disposing of the UPP via DisposeDMExtendedNotificationUPP when the application no longer needs Display Manager notifications.

#include <Carbon/Carbon.h>
#include <AGL/agl.h>

// context is passed in via userData
void handleWindowDMEvent (void *userData, short msg, void *notifyData)
{
  AGLContext aglContext = (AGLContext) userData; // context storage
  if (kDMNotifyEvent == msg) { // post change notifications only
    resizeGL (window, agContext); // update context and handle resize
    GetWindowPortBounds (window, &rectPort);
    InvalWindowRect (window, &rectPort); // force redraw
  }
}

// ---------------------------------

void setupDMNotify (WindowRef window)
{
  // ensure we know when display configs are changed
  gWindowEDMUPP = NewDMExtendedNotificationUPP (handleWindowDMEvent);
  DMRegisterExtendedNotifyProc (gWindowEDMUPP, (void *)window, NULL, &psn);
}

// ---------------------------------

OSStatus disposeDM Notify (WindowRef window)
{
  if (gWindowEDMUPP) { // dispose UPP for DM notifications
    DisposeDMExtendedNotificationUPP (gWindowEDMUPP);
    gWindowEDMUPP = NULL;
  }
}

Listing 4. Display Manager Notification Handler


Full Screen AGL and CGL

Applications using full screen AGL and CGL have a slightly simpler task. Since the drawable position is fixed, its size is directly linked to the display configuration and this configuration is under the control of the application, full screen applications need to just perform updates when they actually change the configuration. Instead of calling a context update routine full screen clients should just re-issue the set full screen call. Listing 5 and 6 show examples of both AGL and CGL routines to reset the full screen context respectively. In the AGL case the aglSetFullScreen function handles screen capture and display resizing, thus one just needs to ensure a valid full screen pixel format and context have been created prior to calling resizeGL. For the CGL case one could use CGCaptureAllDisplays, CGDisplayBestModeForParametersAndRefreshRate (or an associated CGDirectDisplay function), and CGDisplaySwitchToMode to set the requested display configuration, then set the pixel format for the display and call resizeGL. An example of setting OpenGL for use with CGL is shown in Listing 7.

Note:
When all displays are captured, via either aglSetFullScreen, without AGL_FS_CAPTURE_SINGLE set, or CGCaptureAllDisplays, the application will not see any Display Manager notifications, since the display configuration is fixed and will not change until released. If the client does not capture all displays, it will still receive display configuration changes for the non-captured displays. Normally, full screen applications will not need to handle these display notifications, since they are for the displays not currently in use or of interest for full screen OpenGL.


#include <Carbon/Carbon.h>
#include <AGL/agl.h>
#include <OpenGL/gl.h>

// Handles resizing of the screen and the context needed for an update
// This assumes the context has been created with a pixel format which 
// specifies full screen, the single GDevice to use and the pixel 
// depth to set.

void resizeGL (AGLContext aglContext, GLSizei height, GLSizei width)
{
  GLint displayCaps [3];

  if (!aglContext) // check for valid context
    return;

  // re-attach drawable to ensure context is updated
  aglSetCurrentContext (aglContext);
  aglSetFullScreen (aglContext, width, height, 0, 0);
  // note: depth is set in pixel format, 0 freq will match any,
  // 0 display will select a single display selected by pixel format
  
  aglGetInteger (aglContext, AGL_FULLSCREEN, displayCaps); // get size
  glViewport (0, 0, displayCaps[0], displayCaps[1]);
  // update projection matrix here if desired
}

Listing 5. Full screen AGL update handling


#include <Carbon/Carbon.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>

// Handles reattaching needed for a context update
// This assumes the context has been created with a pixel format which 
// specifies full screen, the single display to use and the pixel depth 
// to set.
// Additionally, this assumes the screen has been captured and set to the 
// requested dimensions. The view port is not set here since the calling
// routine actually deals with setting the actual display size.


void resizeGL (CGLContextObj cglContext)
{
  if (!cglContext) // check for valid context
    return;

  // re-attach drawable to ensure context is updated
  CGLSetCurrentContext (cglContext);
  CGLSetFullScreen (cglContext);
}

Listing 6. Full screen CGL update handling


#include <Carbon/Carbon.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>

// Code showing example of full screen pixel format creation,
// display capture and resize.  Also shows associated tear down
// code. 

// globals used for tear down
CGDirectDisplayID gDisplay = 0;
CFDictionaryRef gOldDisplayMode = NULL;
GLboolean gOldDisplayModeValid = GL_FALSE;

CGLContextObj buildFullScreenGL (size_t width, size_t height, 
                                 size_t depth, CGRefreshRate refresh)
{
  CGLContextObj cglContext = 0;
  CGLPixelFormatAttribute attribs[] = {kCGLPFADisplayMask, 0, 
                                       kCGLPFAFullScreen, 
                                       kCGLPFADoubleBuffer, 
                                       kCGLPFADepthSize, 16, NULL};
  CGLPixelFormatObj pixelFormat = NULL;
  long numPixelFormats = 0;
  CFDictionaryRef refDisplayMode = 0;
  CGRect displayRect;

  // set display mode
  gDisplay = CGMainDisplayID (); // use main display     
  refDisplayMode = CGDisplayBestModeForParametersAndRefreshRate 
                        (gDisplay, depth, width, height, refresh, NULL);
  if (refDisplayMode) {
    gOldDisplayMode = CGDisplayCurrentMode (gDisplay);
    gOldDisplayModeValid = GL_TRUE;
    CGCaptureAllDisplays ();
    CGDisplaySwitchToMode (gDisplay, refDisplayMode);
  } // else will use current mode
  
  // build context
  attribs[1] = CGDisplayIDToOpenGLDisplayMask (gDisplay); // set PF display
  CGLChoosePixelFormat (attribs, &pixelFormat, &numPixelFormats);
  if (pixelFormat) {
    CGLCreateContext (pixelFormat, NULL, &cglContext);
    CGLDestroyPixelFormat (pixelFormat);
  }
  if (cglContext) {
    resizeGL (cglContext);
    displayRect = CGDisplayBounds (gDisplay);
    glViewport (0, 0, displayRect.size.width, displayRect.size.height);
    // can update projection matrix if desired

    // set up OpenGL state here
  }
  return cglContext;
}

// ---------------------------------

void disposeGL (CGLContextObj cglContext)
{
  // dump context
  CGLSetCurrentContext (NULL);
  CGLClearDrawable (cglContext);
  if (cglContext)
    CGLDestroyContext (cglContext);   

  // switch to proper resolution
  if (gOldDisplayModeValid)
    CGDisplaySwitchToMode(gDisplay, gOldDisplayMode);
  gOldDisplayModeValid = GL_FALSE;
  CGReleaseAllDisplays ();
}

Listing 7. Example full screen CGL display set up and tear down


Note:
CGL uses a CGOpenGLDisplayMask in the pixel format attributes to specify the display to use for a full screen context vice a CGDirectDisplayID. This attribute should be set with the kCGLPFADisplayMask specifier followed by the actual display mask. This mask is found by using the CGDisplayIDToOpenGLDisplayMask function, passing the CGDirectDisplayID of the requested display.


To recap, clients of the OpenGL API must ensure their OpenGL context is updated for renderer and geometry changes. This is handled by either the system API or the client itself responding to events that could change the drawable's geometry, including surface origin, width, height, and pixel depth, the drawable's position or the display configuration. For the Carbon API, windowed clients should call aglUpdateContext. Full screen applications should re-establish their full screen drawable using aglSetFullScreen or CGLSetFullScreen. Cocoa clients using an NSOpenGLView sub-class and GLUT applications have updates handled by the system APIs. Finally Cocoa applications with custom OpenGL view classes should call their NSOpenGLContext's -update method when notified of geometry changes. Applications using the above methods should behave well when their renderers change or users modify their drawable providing a good user experience.


[Jun 19, 2003]

Did this document help you? Yes It's good, but... Not helpful...