Retired Document
Important: This document may not represent best practices for current development. Links to downloads and other resources may no longer be valid.
Using the QCRenderer Class to Play a Composition
The QCRenderer
class is a simplified runtime object that can load and play a composition to an arbitrary OpenGL context. QCRenderer
also provides an interface to pass data to the input ports or retrieve data from the output ports of the root macro patch of a composition.
This chapter shows how to create a QCRenderer
object for a full screen OpenGL context and then load and render a Quartz Composer composition to that context. The sample code is part of a Cocoa application created using Xcode. Before reading this chapter, you may want to look at QCRenderer Class Reference.
Each section in this chapter provides a code fragment and an explanation of the essential parts of the code fragment. To see how to put all the code together into a complete, working application, you will need to open, build, and run the Player sample application that’s provided with the OS X v10.5 developer tools. See Building and Running the Player Sample Project.
The following tasks are essential to creating a sample application that plays a full-screen composition using a QCRenderer
object. Each is described in more detail in the rest of the chapter.
Declaring the Application Interface
This application is one of the rare ones that requires you to subclass NSApplication
. You need to create a subclass so that you can override the sendEvent:
method to catch user events while the application is displaying a full-screen OpenGL context, and also to set the NSApplication
instance as its own delegate. See NSApplication Class Reference for more information.
You need to replace the principal class (NSApplication
) by your custom subclass of NSApplication
(in this example, PlayerApplication) in the Info.plist
file. Open the Info.plist
file from within Xcode, then find the following key-value pair:
<key>NSPrincipalClass</key> |
<string>NSApplication</string> |
Then replace NSApplication
with the name of the subclass. For this example, the revised key-value pair would look as follows:
<key>NSPrincipalClass</key> |
<string>PlayerApplication</string> |
Listing 3-1 shows the interface for PlayerApplication, which requires variables to create the OpenGL context, QCRenderer
, and other objects that support rendering a composition.
Listing 3-1 The interface for PlayerApplication
@interface PlayerApplication : NSApplication |
{ |
NSOpenGLContext* _openGLContext; |
QCRenderer* _renderer; |
NSString* _filePath; |
NSTimer* _renderTimer; |
NSTimeInterval _startTime; |
NSSize _screenSize; |
} |
@end |
Getting a Composition File
A QCRenderer
object requires an OpenGL context and a Quartz Composer file for its creation. If the user drags a composition to your application icon, implement this NSApplication
delegate method to retain the file pathname for later use when you create the QCRenderer
object.
- (BOOL) application:(NSApplication*)theApplication |
openFile:(NSString*)filename |
{ |
_filePath = [filename retain]; |
return YES; |
} |
If the user opens your application by double-clicking its icon, ask the user to specify a composition and then retain the file pathname by including the following code in the applicationDidFinishLaunching:
delegate method.
NSOpenPanel *openPanel; |
if(_filePath == nil) |
{ |
openPanel = [NSOpenPanel openPanel]; |
[openPanel setAllowsMultipleSelection:NO]; |
[openPanel setCanChooseDirectories:NO]; |
[openPanel setCanChooseFiles:YES]; |
if([openPanel runModalForDirectory:nil |
file:nil |
types:[NSArray arrayWithObject:@"qtz"]] != NSOKButton) |
{ |
NSLog(@"No composition file specified"); |
[NSApp terminate:nil]; |
} |
_filePath = [[[openPanel filenames] objectAtIndex:0] retain]; |
} |
Capturing the Main Display
The Quartz Services programming interface provides functions that configure and control displays. Use its functions to capture the main screen and cache its dimensions. See Quartz Display Services Reference for more information on these functions.
You can capture the main display by using the code in Listing 3-2. A detailed explanation for each numbered line of code follows the listing.
Listing 3-2 Code that captures the main display
CGDisplayCapture (kCGDirectMainDisplay);// 1 |
CGDisplayHideCursor (kCGDirectMainDisplay); |
_screenSize.width = CGDisplayPixelsWide(kCGDirectMainDisplay);// 2 |
_screenSize.height = CGDisplayPixelsHigh(kCGDirectMainDisplay); |
Here what the code does:
Captures the main display. Capturing the screen is important because later you’ll set the receiver of the OpenGL context to full screen mode. A captured display prevents contention from other applications and system services. In addition, applications are not notified of display changes, preventing them from repositioning their windows and the Finder from repositioning desktop icons.
Caches the screen dimensions. The rendering method uses the screen dimensions to normalize the mouse coordinates. See Writing the Rendering Routine.
Setting Up the OpenGL Context
An OpenGL context pixel format requires a pixel format that specifies the buffers (depth buffer, alpha buffer, stencil buffer, and accumulation buffer) as well as other attributes of a context. Listing 3-3 shows how to set up an OpenGL context. A detailed explanation for each numbered line of code follows the listing.
Listing 3-3 Setting up an OpenGL context
NSOpenGLPixelFormat *format;// 1 |
NSOpenGLPixelFormatAttribute attributes[] = {// 2 |
NSOpenGLPFAFullScreen, |
NSOpenGLPFAScreenMask, |
CGDisplayIDToOpenGLDisplayMask(kCGDirectMainDisplay), |
NSOpenGLPFANoRecovery, |
NSOpenGLPFADoubleBuffer, |
NSOpenGLPFAAccelerated, |
NSOpenGLPFADepthSize, |
24, |
(NSOpenGLPixelFormatAttribute) 0 |
}; |
format = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes] |
autorelease];// 3 |
_openGLContext = [[NSOpenGLContext alloc] // 4 |
initWithFormat:format |
shareContext:nil]; |
if(_openGLContext == nil) // 5 |
{ |
NSLog(@"Cannot create OpenGL context"); |
[NSApp terminate:nil]; |
} |
Here’s what the code does:
Declares storage for an
NSOpenGLPixelFormat
object. You specify a format when you create an OpenGL context.Sets up the attributes for the pixel format. These attributes specify, among other things, a full-screen context and a depth buffer. NSOpenGLPixelFormat Class Reference provides a complete description of the available format attributes. At the very least, you must provide a color buffer and a depth buffer for the
QCRenderer
object.Allocates a pixel format object and initializes it with the pixel format attributes.
Allocates an OpenGL context and initializes it with the pixel format object.
Checks to make sure the OpenGL context is not nil. If it is, the application must terminate.
Setting Up Full Screen Display and Syncing
You need to set the OpenGL context to full-screen mode and then set the swap interval to 1
to ensure that the buffers are swapped only during the vertical retrace of the monitor. If the buffers aren’t synchronized with the retrace, the composition could render with tearing artifacts. (For more information on swap intervals, see OpenGL Programming Guide for Mac.)
long value = 1; |
[_openGLContext setFullScreen]; |
[_openGLContext setValues:&value forParameter:kCGLCPSwapInterval]; |
Creating a QCRenderer Object
A QCRenderer
object requires an OpenGL context, the OpenGL pixel format, and a file pathname. If for some reason the renderer can’t be created, the application must terminate. Use the following code to create the renderer and check for its creation.
_renderer = [[QCRenderer alloc] |
initWithOpenGLContext:_openGLContext |
pixelFormat:format |
file:_filePath]; |
if(_renderer == nil) |
{ |
NSLog(@"Cannot create QCRenderer"); |
[NSApp terminate:nil]; |
} |
Creating a Timer
You need to set up a timer to regularly render the composition. This timer set up in the following code is scheduled to fire 60 times per second. Each time it fires, it invokes the render routine that’s created in the next section. If the timer can’t be created, the application must terminate, so make sure you include code to check for the existence of the timer. For more information on times, see NSTimer Class Reference.
#define kRendererFPS 60.0 |
_renderTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0 / |
(NSTimeInterval)kRendererFPS) |
target:self |
selector:@selector(_render:) |
userInfo:nil |
repeats:YES] |
retain]; |
if(_renderTimer == nil) |
{ |
NSLog(@"Cannot create NSTimer"); |
[NSApp terminate:nil]; |
} |
Writing the Rendering Routine
When the timer fires or when a user event needs to be processed, the renderWithEvent:
method is invoked. Recall that for this application the timer is set to fire 60 times per second. Listing 3-4 shows the _render
and renderWithEvent:
methods. A detailed explanation for each numbered line of code follows the listing.
Listing 3-4 The rendering methods
- (void) _render:(NSTimer*)timer |
{ |
[self renderWithEvent:nil]; |
} |
- (void) renderWithEvent:(NSEvent*)event |
{ |
NSTimeInterval time = [NSDate timeIntervalSinceReferenceDate]; |
NSPoint mouseLocation; |
NSMutableDictionary *arguments; |
if(_startTime == 0)// 1 |
{ |
_startTime = time; |
time = 0; |
} |
else |
time -= _startTime; |
mouseLocation = [NSEvent mouseLocation];// 2 |
mouseLocation.x /= _screenSize.width;// 3 |
mouseLocation.y /= _screenSize.height;// 4 |
arguments = [NSMutableDictionary dictionaryWithObject:[NSValue // 5 |
valueWithPoint:mouseLocation] |
forKey:QCRendererMouseLocationKey]; |
if(event)// 6 |
[arguments setObject:event forKey:QCRendererEventKey]; |
// Your code to set input port values // 7 |
if(![_renderer renderAtTime:time arguments:arguments])// 8 |
NSLog(@"Rendering failed at time %.3fs", time); |
// Your code to get output port values// 9 |
[_openGLContext flushBuffer];// 10 |
} |
Here’s what the code does:
Computes the composition time as the difference between the current time and the time at which rendering started.
Gets the current mouse position, in screen coordinates. Mouse coordinates need to be normalized relative to the OpenGL context viewport (
[0,1],x[0,1]
) with the origin(0,0)
at the lower-left corner.Normalizes the x mouse coordinate.
Normalizes the y mouse coordinate.
Creates a dictionary and writes the normalized mouse coordinates to it. Coordinates are specified as an
NSPoint
object stored in anNSValue
object.If there is a user event, adds it to the arguments dictionary.
This is where you could add code to set the value for an input parameter that’s published to the root macro patch of a composition. You use the method
setValue:forInputKey:
, making sure to pass a valid key.Renders a frame at the specified time, passing the arguments dictionary.
This is where you could add code to retrieve the value of a published output parameter. You use the method
valueForOutputKey:
, making sure to pass a valid key.Flushes the OpenGL context to display the rendered frame onscreen.
Overriding the sendEvent Method
Recall that this example subclasses NSApplication
so that it could override the sendEvent:
method to ensure that user events are processed while there is a full screen OpenGL context on screen. The sendEvent:
method in Listing 3-5 checks for:
An Escape key press, and terminates the application if there is one.
A meaningful event for the composition, and invokes the renderer immediately with such an event.
Listing 3-5 The overridden event-sending method
#define kRendererEventMask (NSLeftMouseDownMask|NSLeftMouseDraggedMask | |
NSLeftMouseUpMask | NSRightMouseDownMask | |
NSRightMouseDraggedMask | NSRightMouseUpMask | |
NSOtherMouseDownMask | NSOtherMouseUpMask | |
NSOtherMouseDraggedMask | NSKeyDownMask | |
NSKeyUpMask | NSFlagsChangedMask | |
NSScrollWheelMask) |
- (void) sendEvent:(NSEvent*)event |
{ |
if(([event type] == NSKeyDown) && ([event keyCode] == 0x35)) |
[NSApp terminate:nil]; |
if(_renderer && (NSEventMaskFromType ([event type]) & |
kRendererEventMask)) |
[self renderWithEvent:event]; |
else |
[ super sendEvent:event]; |
} |
Building and Running the Player Sample Project
The best way to experiment with using the QCRenderer
class is to build and run the Player sample application that’s supplied with the OS X v10.5 developer tools. After installing the developer tools, you can find the Player Xcode project in the following location:
/Developer/Examples/Quartz Composer Sample Code
After you compile the Player application, you can open a composition in two ways. Either launch the application and specify a composition to play or drag a composition onto the Player application icon. To support opening a composition by dragging it to the application icon, you need to change the Info.plist file. Listing 3-6 shows the Info.plist file for the Player sample project. You can view this file from within Xcode by double-clicking Info.plist in the file list. Note that the listing has a CFBundleTypeExtensions
key followed by the qtz
extension.
Listing 3-6 Specifying the qtz extension in the Info.plist file
<key>CFBundleDocumentTypes</key> |
<array> |
<dict> |
<key>CFBundleTypeExtensions</key> |
<array> |
<string>qtz</string> |
</array> |
</dict> |
</array> |
See Also
NSApplication Class Reference discusses the class, its methods, and the cases for which you might want to subclass NSApplication
.
Copyright © 2004, 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-04-23