|
Q: How do I drive the drawing loop of my Cocoa OpenGL application?A: On Mac OS X 10.4 and later, applications should use a Core Video display link ( OpenGL applications should avoid frame tearing and wasting resources drawing pixels the user will never see. To do this, applications should not draw faster than the display can refresh and should swap or draw during the vertical blanking interval. @interface MyView : NSOpenGLView { CVDisplayLinkRef displayLink; //display link for managing rendering thread } @end - (void)prepareOpenGL { // Synchronize buffer swaps with vertical refresh rate GLint swapInt = 1; [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // Create a display link capable of being used with all active displays CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); // Set the renderer output callback function CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, self); // Set the display link for the current renderer CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat); // Activate the display link CVDisplayLinkStart(displayLink); } // This is the renderer output callback function static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { CVReturn result = [(MyView*)displayLinkContext getFrameForTime:outputTime]; return result; } - (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime { // Add your drawing codes here return kCVReturnSuccess; } - (void)dealloc { // Release the display link CVDisplayLinkRelease(displayLink); [super dealloc]; } When using a Cocoa timer (
When vertical synchronization is enabled in your OpenGL application, during each retrace period, when the timer fires, the application starts preparing data for the next frame; when the drawing is done, the application blocks waiting for the next vertical retrace. The timer can not fire until the swap completes. In order to let your application have enough time to submit the drawing commands, you will want to let the timer fire as soon as the application returns to the event loop. Recall that there is no correlation at all between when the timer fires and when the vertical refresh occurs. If you use a fairly big time interval (such as 0.0167 seconds to yield 60 frames per second), the timer may fire anywhere in the retrace period, giving your application the remainder of the frame to prepare the data for the next frame. You should set the timer to a very small interval such as 0.001 seconds or 1000 fps. This makes sure that the timer fires right after the swap completes, giving your application the entire retrace period to do the drawing. Notice that if vertical synchronization is not enabled in your OpenGL application, by creating a timer with an exceptionally small interval (such as 0.001 seconds or 1000 fps), the application will burn a lot of CPU time just firing off the timer, even though the drawing loop hasn't even completed its last run. The net effect of this will be moderate to severe performance problems, depending on how busy the drawing loop is and how fast the machine can service the timer. However, if you synchronize buffer swaps to the vertical refresh rate, this small time interval will not overdrive the pipeline, because the timer does nothing when the application blocks during swap regardless of the time interval, thus taking no extra CPU time. // Synchronize buffer swaps with vertical refresh rate - (void)prepareOpenGL { GLint swapInt = 1; [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; } // Put our timer in -awakeFromNib, so it can start up right from the beginning -(void)awakeFromNib { renderTimer = [[NSTimer timerWithTimeInterval:0.001 //a 1ms time interval target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] addTimer:renderTimer forMode:NSEventTrackingRunLoopMode]; //Ensure timer fires during resize } // Timer callback method - (void)timerFired:(id)sender { // It is good practice in a Cocoa application to allow the system to send the -drawRect: // message when it needs to draw, and not to invoke it directly from the timer. // All we do here is tell the display it needs a refresh [self setNeedsDisplay:YES]; } Document Revision History
Posted: 2009-05-01 |
|