< Previous PageNext Page > Hide TOC

Drawing to a Window or View

The OpenGL programming interface provides hundreds of drawing commands that drive graphics hardware. It doesn't provide any commands that interface with the windowing system of an operating system. Without a windowing system, the 3D graphics of an OpenGL program are trapped inside the GPU. Figure 2-1 shows a cube drawn to a Cocoa view and a trefoil drawn to a Carbon window. (You can just as easily draw the trefoil to the Cocoa view and the cube to the Carbon window.)


Figure 2-1  OpenGL content in a Cocoa view (left) and a Carbon window (right)

OpenGL content in a Cocoa view (left) and a Carbon window (right)

This chapter shows how to display OpenGL drawing onscreen using the APIs provided by Mac OS X. You'll see how to draw to Cocoa views and Carbon windows. (This chapter does not show how to use GLUT.) The first section describes the overall approach to drawing onscreen and provides an overview to the functions and methods used by each API. You'll want to read this regardless of the application framework that you use. The remaining sections in the chapter provide information that's specific to Cocoa or Carbon. After you consult the appropriate section, take a look at “What's Next” for pointers to optimization strategies and other information that will help your OpenGL application to perform at its best.

In this section:

General Approach
Drawing to a Cocoa View
Drawing to a Carbon Window
What's Next
See Also


General Approach

Mac OS X provides three interfaces for drawing OpenGL content onscreen: the NSOpenGL classes, AGL, and CGL. (See “Programming Interfaces” for more information). You use the NSOpenGL classes from within the Cocoa application framework, while AGL is the interface that supports drawing OpenGL content to a Carbon application. CGL can be used from either a Cocoa or Carbon application. For drawing to a view or a window, you'll either use the NSOpenGL classes (for a Cocoa view) or AGL (for a Carbon window), because CGL supports drawing only to the full screen.

Regardless of the application framework, to draw OpenGL content to a window or view, you need to perform these tasks:

  1. Set up the renderer and buffer attributes that support the OpenGL drawing you want to perform.

    Each of the OpenGL APIs in Mac OS X has its own set of constants that represent renderer and buffer attributes. For example, the all-renderers attribute is represented by the NSOpenGLPFAAllRenderers constant in Cocoa and the AGL_ALL_RENDERERS constant in the AGL API.

  2. Request, from the operating system, a pixel format object that encapsulates pixel storage information and the renderer and buffer attributes required by your application. The returned pixel format object contains all possible combinations of renderers and displays available on the system that your program runs on and that meets the requirements specified by the attributes. The combinations are referred to as virtual screens. (See “Virtual Screens.”)

    There may be situations for which you want to ensure that your program uses a specific renderer. “Techniques for Choosing Attributes” discusses how to set up an attributes array that will guarantee the system passes back a pixel format object that uses only that renderer.

    You'll need to provide code that handles the case of getting back a NULL pixel format object.

  3. Create a rendering context and bind the pixel format object to it. The rendering context keeps track of state information that controls such things as drawing color, view and projection matrices, characteristics of light, and conventions used to pack pixels.

    Your application needs a pixel format object to create a rendering context.

  4. Release the pixel format object. Once the pixel format object is bound to a rendering context, its resources are no longer needed.

  5. Bind a drawable object to the rendering context. You'll either bind a Cocoa view or a Carbon window to the context.

  6. Make the rendering context the current context. The system sends OpenGL drawing to whichever rendering context is designated as the current one. It's possible for you to set up more than one rendering context, so you'll need to make sure that the one you want to draw to is the current one.

  7. Perform your drawing.

The specific functions or methods that you use to perform each of the steps are discussed in the sections that follow.

Drawing to a Cocoa View

There are two ways to draw OpenGL content to a Cocoa view. You can either use the NSOpenGLView class or create a custom NSView class. If your application has modest drawing requirements, then you can use the NSOpenGLView class. For example, if your application draws to a single view and does not support dragging the view between monitors, you can use the NSOpenGLView class. See “Drawing to an NSOpenGLView Class: A Tutorial.”

If your application is more complex and needs to support drawing to multiple rendering contexts, you may want to consider subclassing the NSView class. For example, if your application supports drawing to multiple views at the same time, you'll need to set up a custom NSView class. See “Drawing OpenGL Content to a Custom View.”

Drawing to an NSOpenGLView Class: A Tutorial

The NSOpenGLView class is a lightweight subclass of the NSView class that provides convenience methods for setting up OpenGL drawing. An NSOpenGLView object maintains an NSOpenGLPixelFormat object and an NSOpenGLContext object into which OpenGL calls can be rendered. It provides methods for accessing and managing the pixel format object and the rendering context, and handles notification of visible region changes.

An NSOpenGLView object does not support subviews. You can, however, divide the view into multiple rendering areas using the OpenGL function glViewport.

This section provides step-by-step instructions for creating a simple Cocoa application that draws OpenGL content to a view. The tutorial assumes that you know how to use Xcode and Interface Builder. If you have never created an application using the Xcode development environment, see Getting Started with Tools.

  1. Open Xcode and create a Cocoa application project named Golden Triangle.

  2. Open the Frameworks folder in the Groups & File list. Then select the Linked Frameworks folder.

  3. Choose Project > Add to Project and navigate to the OpenGL framework, which is located in the System/Library/Frameworks directory. In the sheet that appears, choose OpenGL.framework and click Add. Then, in the next sheet that appears, click Add to add the framework to the target.

  4. Choose File > New File. Then choose the Objective-C class template.

  5. Click Next and name the file MyOpenGLView.m. Make sure the checkbox to create MyOpenGLView.h is selected. Then click Finish.

  6. Open the MyOpenGLView.h file and modify the file so that it looks like the code shown in Listing 2-2 to declare the interface.



    Listing 2-1  The interface for MyOpenGLView

    #import <Cocoa/Cocoa.h>
     
    @interface MyOpenGLView : NSOpenGLView
    {
    }
    - (void) drawRect: (NSRect) bounds;
    @end
  7. Save and close the MyOpenGLView.h file.

  8. Open the MyOpenGLView.m file and include the gl.h file, as shown in Listing 2-3.



    Listing 2-2  Include OpenGL/gl.h

    #import "MyOpenGLView.h"
    #include <OpenGL/gl.h>
     
    @implementation MyOpenGLView
    @end
  9. Implement the drawRect: method as shown in Listing 2-3, adding the code after the @implementation statement. The method sets the clear color to black and clears the color buffer in preparation for drawing. Then, drawRect: calls your drawing routine, which you’ll add next. The OpenGL command glFlush draws the content provided by your routine to the view.



    Listing 2-3  The drawRect: method for MyOpenGLView

    -(void) drawRect: (NSRect) bounds
    {
        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT);
        drawAnObject();
        glFlush();
    }
  10. Add the code to perform your drawing. In your own application, you'd perform whatever drawing is appropriate. But for the purpose of learning how to draw OpenGL content to a view, you'll add the code shown in Listing 2-4. This code draws a 2-dimensional, gold-colored triangle, whose dimensions are not quite the dimensions of a true golden triangle, but good enough to show how to perform OpenGL drawing.

    Make sure that you insert this routine before the drawRect: method in the MyOpenGLView.m file.



    Listing 2-4  Code that draws a triangle using OpenGL commands

    static void drawAnObject ()
    {
        glColor3f(1.0f, 0.85f, 0.35f);
        glBegin(GL_TRIANGLES);
        {
            glVertex3f(  0.0,  0.6, 0.0);
            glVertex3f( -0.2, -0.3, 0.0);
            glVertex3f(  0.2, -0.3 ,0.0);
        }
        glEnd();
    }
  11. In the File Name list, double click the MainMenu.xib file to open Interface Builder. A default menu bar and window titled "Window" appears when the file opens.

  12. Click the window and choose Tools > Inspector.

  13. In the Window Attributes pane of the inspector window, change the Title entry to Golden Triangle.

  14. Choose Tools > Library and type NSOpenGLView in the Search field.

  15. Drag an NSOpenGLView object from the Library to the window. Resize the view to fit the window.

  16. In the Identity pane of the inspector for the view, choose MyOpenGLView from the Class pop-up menu.

  17. Open the Attributes pane of the inspector for the view, and take a look at the renderer and buffer attributes that are available to set. These settings save you from setting attributes programmatically.

    Only those attributes listed in the Interface Builder inspector are set when the view is instantiated. If you need additional attributes, you'll need to set them programmatically.

  18. Choose File > Build & Go in Xcode. You should see content similar to the triangle shown in Figure 2-2.

    Figure 2-2  The output from the Golden Triangle program

    The output from the Golden Triangle program

This example is extremely simple. In a more complex application, you'd want to do the following:

Drawing OpenGL Content to a Custom View

This section provides an overview of the key tasks you need to perform to customize the NSView class for OpenGL drawing. Before you create a custom view for OpenGL drawing, you should read Creating a Custom View in View Programming Guide for Cocoa. You will also want to download Custom Cocoa OpenGL (available on the ADC website from Sample Code > Graphics & Imaging > OpenGL), which is a full-featured OpenGL sample application that uses a custom subclass of NSView that behaves similarly to the NSOpenGLView class. The custom class is declared and defined in the CustomOpenGLView.h and CustomOpenGLView.m files. After you've set up your custom class, you can use it just as you would use the built-in NSOpenGLView class.

When you subclass the NSView class to create a custom view for OpenGL drawing, you'll override any Quartz drawing or other content that is in that view. To set up a custom view for OpenGL drawing, subclass NSView and create two private variables—one which is an NSOpenGLContext object and the other an NSOpenGLPixelFormat object, as shown in Listing 2-5.

Listing 2-5  The interface for a custom OpenGL view

@class NSOpenGLContext, NSOpenGLPixelFormat;
 
@interface CustomOpenGLView : NSView
{
  @private
    NSOpenGLContext*     _openGLContext;
    NSOpenGLPixelFormat* _pixelFormat;
}
+ (NSOpenGLPixelFormat*)defaultPixelFormat;
- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format;
- (void)setOpenGLContext:(NSOpenGLContext*)context;
- (NSOpenGLContext*)openGLContext;
- (void)clearGLContext;
- (void)prepareOpenGL;
- (void)update;
- (void)setPixelFormat:(NSOpenGLPixelFormat*)pixelFormat;
- (NSOpenGLPixelFormat*)pixelFormat;
@end

In addition to the usual methods for the private variables (openGLContext, setOpenGLContext:, pixelFormat, and setPixelFormat:) you'll need to implement the following methods:

You need to override the update and initWithFrame: methods of the NSView class.

If the custom view is not guaranteed to be in a window, you must also override the lockFocus method of the NSView class. See Listing 2-7. This method makes sure that the view is locked prior to drawing and that the context is the current one.

Listing 2-6  The initWithFrame:pixelFormat: method

- (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) _surfaceNeedsUpdate:(NSNotification*)notification
{
   [self update];
}

Listing 2-7  The lockFocus method

- (void)lockFocus
{
    NSOpenGLContext* context = [self openGLContext];
 
    [super lockFocus];
    if ([context view] != self) {
        [context setView:self];
    }
    [context makeCurrentContext];
}

The reshape method is not supported by the NSView class. You need to update bounds in the drawRect: method, which should take the form shown in Listing 2-8.

Listing 2-8  The drawRect method for a custom view

-(void) drawRect
{
    [context makeCurrentContext];
    //Perform drawing here
    [context flushBuffer];
}

There may be other methods that you want to add. For example, you might consider detaching the context from the drawable object when the custom view is moved from the window, as shown in Listing 2-9.

Listing 2-9  Detaching the context from a drawable object

-(void) viewDidMoveToWindow
{
    [super viewDidMoveToWindow];
    if ([self window] == nil)
        [context clearDrawable];
}

Drawing to a Carbon Window

This section describes the steps for setting up onscreen drawing to a Carbon window. To get an idea of how these steps fit into an full application, you should look at the sample application GLCarbonAGLWindow.

Follow these steps to use the AGL API to set up onscreen drawing to a Carbon window:

  1. Set up an array of attributes that describes the buffer characteristics and renderer capabilities that you want. You can supply any of the pixel format attributes or extended attributes defined in AGL Constants in AGL Reference.

    This example in Listing 2-10 sets up attributes for RGBA, double buffering, and a pixel depth of 24 bits. Your code would set up whatever attributes are appropriate. In later chapters in this book, you'll see how to choose attributes for specific purposes. (See “Techniques for Choosing Attributes.”)

  2. Obtain a pixel format object by passing the attributes array to the function aglChoosePixelFormat.

    The pixel format object contains a list of all appropriate renderer-display combinations. In the example shown here, it's likely that the list will contain at least two items—one that uses a hardware renderer and another that uses a software renderer.

  3. Bind the pixel format object to a rendering context by passing the pixel format object to the function aglCreateContext.

    If the pixel format object has more than one pixel format (renderer-display combination) in it, AGL uses the first in the list. You can call the function aglNextPixelFormat if you want to use the next pixel format in the list.

  4. Release the pixel format object by calling the function aglDestroyPixelFormat.

  5. Get the port associated with the Carbon window that you want to draw into by calling the Window Manager function GetWindowPort. After you attach a rendering context to the Carbon window, its viewport is set to the full size of the window.

    Note: The AGL API for drawing to a Carbon window was developed prior to Mac OS X. Because of this heritage, the AGLDrawable data type is a CGrafPtr data type under the hood. That's why you must call GetWindowPort to obtain the associated graphics port from the WindowRef data type passed to MySetWindowAsDrawableObject.

  6. Bind the window to the rendering context by passing the port to the function aglSetDrawable.

  7. Make the rendering context the current context by calling function aglSetCurrentContext.

Listing 2-10 shows how to implement these steps and how to check for errors along the way by calling the application-defined function MySetWindowAsDrawableObject. It's recommended that your application provides a similar error-checking function. In the case of an error you'll either want to notify the user and abort the program or take some sort of fallback action that ensures you application can draw OpenGL content. (See “Ensuring a Valid Pixel Format Object” for an example of backing out of attributes. See “Retrieve Error Information Only When Debugging” for guidelines on error checking and performance.)

Note that the example passes the pixel format object returned from the aglChoosePixelFormat function to the function aglCreateContext. By default, AGL uses the first pixel format in the pixel format object regardless of how many pixel formats are actually in the object. You can iterate through the pixel format object using the functionaglNextPixelFormat.

Listing 2-10  Setting a Carbon window as a drawable object

OSStatus MySetWindowAsDrawableObject  (WindowRef window)
{
    OSStatus err = noErr;
    Rect rectPort;
    GLint attributes[] =  { AGL_RGBA,
                        AGL_DOUBLEBUFFER,
                        AGL_DEPTH_SIZE, 24,
                        AGL_NONE };
    AGLContext  myAGLContext = NULL;
    AGLPixelFormat myAGLPixelFormat;
 
    myAGLPixelFormat = aglChoosePixelFormat (NULL, 0, attributes);
    err = MyAGLReportError ();
    if (myAGLPixelFormat) {
        myAGLContext = aglCreateContext (myAGLPixelFormat, NULL);
        err = MyAGLReportError ();
    }
    if (! aglSetDrawable (myAGLContext, GetWindowPort (window)))
            err = MyAGLReportError ();
    if (!aglSetCurrentContext (myAGLContext))
            err = MyAGLReportError ();
    return err;
}
 
OSStatus MyAGLReportError (void)
{
    GLenum err = aglGetError();
    if (AGL_NO_ERROR != err) {
        char errStr[256];
        sprintf (errStr, "AGL: %s",(char *) aglErrorString(err));
        reportError (errStr);
    }
    if (err == AGL_NO_ERROR)
        return noErr;
    else
        return (OSStatus) err;
}

Note: Although this example shows how to draw OpenGL content to an entire Carbon window, it is possible for Carbon applications to draw to a part of a window. Carbon developers can find additional information on using windows by reading Handling Carbon Windows and Controls.

What's Next

After you've successfully drawn OpenGL content onscreen from within a Cocoa or a Carbon application, you'll want to move on to more complex tasks. Most 3D applications have sophisticated needs, especially with regard to performance and the need to ensure that the application works with a variety of graphics cards and displays. Some of the chapters that follow will help you to fine tune your code. Other chapters provide guidance and code examples for accomplishing common tasks, such as checking for OpenGL functionality or using images as textures.

See Also

OpenGL sample code projects (ADC Reference Library):



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