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.)
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.
General Approach
Drawing to a Cocoa View
Drawing to a Carbon Window
What's Next
See Also
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:
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.
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.
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.
Release the pixel format object. Once the pixel format object is bound to a rendering context, its resources are no longer needed.
Bind a drawable object to the rendering context. You'll either bind a Cocoa view or a Carbon window to the context.
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.
Perform your drawing.
The specific functions or methods that you use to perform each of the steps are discussed in the sections that follow.
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.”
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.
Open Xcode and create a Cocoa application project named Golden Triangle.
Open the Frameworks folder in the Groups & File list. Then select the Linked Frameworks folder.
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.
Choose File > New File. Then choose the Objective-C class template.
Click Next and name the file MyOpenGLView.m
. Make sure the checkbox to create MyOpenGLView.h
is selected. Then click Finish.
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 |
Save and close the MyOpenGLView.h
file.
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 |
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(); |
} |
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(); |
} |
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.
Click the window and choose Tools > Inspector.
In the Window Attributes pane of the inspector window, change the Title entry to Golden Triangle
.
Choose Tools > Library and type NSOpenGLView
in the Search field.
Drag an NSOpenGLView
object from the Library to the window. Resize the view to fit the window.
In the Identity pane of the inspector for the view, choose MyOpenGLView
from the Class pop-up menu.
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.
Choose File > Build & Go in Xcode. You should see content similar to the triangle shown in Figure 2-2.
This example is extremely simple. In a more complex application, you'd want to do the following:
In the interface for the view, declare a variable that indicates whether the view is ready to accept drawing. A view is ready for drawing only if it is bound to a rendering context and that context is set to be the current one.
Cocoa does not call initialization routines for objects created in Interface Builder. If you need to perform any initialization tasks, do so in the awakeFromNib
method for the view. Note that because you set attributes in the inspector, there is no need to set them up programmatically unless you need additional ones. There is also no need to create a pixel format object programmatically; it is created and loaded when Cocoa loads the nib file.
Your drawRect:
method should test whether the view is ready to draw into. You need to provide code that handles the case when the view is not ready to draw into.
OpenGL is at its best when doing real-time and interactive graphics. Your application will need to provide a timer or support user interaction.
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:
+ (NSOpenGLPixelFormat*) defaultPixelFormat
Use this method to allocate and initialize the NSOpenGLPixelFormat
object.
- (void) clearGLContext
Use this method to clear and release the NSOpenGLContext
object.
- (void) prepareOpenGL
Use this method to initialize the OpenGL state after creating the NSOpenGLContext
object.
You need to override the update
and initWithFrame:
methods of the NSView
class.
update
calls the update
method of the NSOpenGLContext
class.
initWithFrame:pixelFormat
retains the pixel format and sets up the notification NSViewGlobalFrameDidChangeNotification
. See Listing 2-6.
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]; |
} |
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:
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.”)
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.
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.
Release the pixel format object by calling the function aglDestroyPixelFormat
.
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
.
Bind the window to the rendering context by passing the port to the function aglSetDrawable
.
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.
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.
OpenGL sample code projects (ADC Reference Library):
Cocoa OpenGL sets up a window and handles events for drawing OpenGL content to a Cocoa view.
Custom Cocoa OpenGL uses a custom view in Cocoa for OpenGL drawing.
GLCarbonAGLWindow contains code that sets up a Carbon window for OpenGL drawing, handles events, and has a virtual trackball as well as a number of other features.
© 2004, 2008 Apple Inc. All Rights Reserved. (Last updated: 2008-06-09)