If your program displays animated content, because it is a game or other multimedia-based application, your code should avoid updating your window content faster than the screen refresh rate. Drawing content to your local window buffer and flushing that content to the screen at more than 30 frames per second is usually a waste of CPU time. Most users cannot perceive updates at rates greater than 30 frames per second, so flushing more frequently is rarely needed. Changes introduced in Mac OS X v10.4 to eliminate visual “tearing” and other display artifacts are also likely to cause performance problems in code that flushes too frequently.
Prior to Mac OS X v10.4, when you called QDFlushPortBuffer
or similar functions to flush the contents of the window buffer to the screen, the operation occurred immediately. Thus, it was possible to begin modifying the window buffer shortly after issuing the flush call. This behavior allowed developers to achieve frame rates greater than the screen refresh rate, despite that fact that many of those frames never actually made it to the screen.
In Mac OS X v10.4 and later, QDFlushPortBuffer
and similar functions no longer flush the window buffer immediately. Instead, flush requests are deferred until it is time to refresh the display. At that time, the window server coalesces the updates and pushes the changes to the graphics card. This new “coalesced update” behavior can cause a performance problem for applications that try to update their window buffers more frequently than the screen refresh rate. Drawing routines block the current thread until the window buffer has been completely flushed. With coalesced updates, this means your code could block for as much as 1/60th of a second.
To avoid performance problems, you should never draw or flush your window buffers faster than the screen refresh rate. If you typically draw your content and then immediately flush it to the screen, you can use timers to notify your code when it is time to draw. Simply set a timer to fire at the same frequency as the screen refresh rate and have it call your drawing routine.
Note: For compatibility, Core Graphics does not enable coalesced updates for CFM applications and applications built prior to Mac OS X v10.4. Those applications continue to flush immediately.
Determining If Your Application is Flushing Too Often
Guidelines for Drawing With Coalesced Updates
Getting the Refresh Rate
If an application that has been built and linked on Mac OS X v10.4 is spending more time drawing (or has a reduced frame rate) then the same application built and linked on a previous system, it is probably being affected by coalesced updates. There are two tools you can use to determine if your application is affected: Quartz Debug and Shark. The Quartz Debug tool by itself lets you detect whether your application may be experiencing problems because of coalesced updates, while Shark helps you find out where the problem is in your code.
Quartz Debug is a debugging tool for the Quartz graphics system with several powerful features to help you identify a number of graphics display and performance problems. Quartz Debug is located in the /Developer/Applications/Performance Tools/
directory.
To determine if coalesced updates are affecting your application, you use the beam sync tools and frame meter of Quartz Debug. With your application running, launch Quartz Debug, and do the following:
Choose Tools > Show Beam Sync Tools.
In the Beam Sync Tools dialog, choose the Force Beam Synchronization option. This causes coalesced updates to be used by all applications.
Choose Tools > Show Frame Meter.
If your application is affected by coalesced updates, its frame rate will be lower when beam synchronization is enabled. This lowered frame rate will often coincide with increased CPU usage as well. To learn more about using Quartz Debug to see the affect of coalesced updates see Q&A 1236, Debugging Graphics with Quartz Debug.
Shark is a profiling tool included with the Mac OS X developer tools distribution. Shark can be used to profile an application and see where time is being spent in drawing operations. You can use this information to diagnose specific parts of your code that are affected by coalesced updates. Shark is located in the /Developer/Applications/Performance Tools/
directory.
To use Shark to determine where your code is affected by coalesced updates, do the following:
In Quartz Debug, choose Tools > Show Beam Sync Tools.
In the Beam Sync Tools dialog, choose the Disable Beam Synchronization option.
In Shark, sample your application using the "Time Profile (All Thread States)" mode.
In Quartz Debug, choose the Force Beam Synchronization option from the Beam Sync Tools dialog.
In Shark, sample your application again.
After sampling your application with beam synchronization enabled and disabled, compare the results. If there is little difference between the two sample sets, your application is not running into problems with coalesced updates enabled. On the other hand, if you see time more time spent in CGContext
drawing operations when beam synchronization is enabled, your application may be running into problems with coalesced updates enabled. You can use Shark to trace these drawing calls back to the parts of your code that use them to find out where the problems lie.
To ensure that your application's performance does not deteriorate when coalesced updates are enabled, you should follow the guidelines listed in the sections that follow. For additional drawing guidelines, see “Carbon Drawing Tips” and “Cocoa Drawing Tips.”
If you are using Quartz, you should avoid calling CGContextFlush
to force the automatic update of the window. Instead call CGContextSynchronize
to let Quartz determine the appropriate time at which to update the window.
If you are using Cocoa, you should avoid using display
and its related method to force updates. Instead, use the setNeedsDisplay:
and setNeedsDisplayInRect:
methods and let the run loop handle updates to those areas during the next update cycle.
If you are writing directly to the window buffer using QuickDraw, you should avoid calling QDFlushPortBuffer
to force updates. Instead, call QDSetDirtyRegion
to mark the area of the window buffer that needs to be updated.
If you must flush to the buffers, use a timer to synchronize your drawing cycles with the screen refresh rate. Flushing is also still appropriate in cases where your application needs to display some content once and cannot wait for the event loop, such as when displaying a splash screen.
Applications generally should not draw or flush faster than the user can see. For most graphics, a refresh rate of 30 frames per second is sufficient for smooth transitions. If your software needs to update at a faster rate, make sure that the rate does not exceed the refresh rate of the screen. For information about how to get the screen refresh rate, see “Getting the Refresh Rate.”
In your drawing routines, you should minimize the amount of time between when you first touch the graphics context and when you are done with it. This might mean decoupling your data engine from the graphics engine and reorganizing your code to perform any needed calculations prior to drawing. By performing any needed calculations first, you delay the point at which you need to actually touch the graphics context. If the context is currently being flushed, this can help minimize the time your application spends waiting for it.
You can get the current screen refresh rate from Quartz. The CGDisplayCurrentMode
function returns a dictionary of display properties. The refresh rate for the specified screen is associated with the kCGDisplayRefreshRate
key. If the value of this key is 0, the screen is an LCD and you should assume a refresh rate of 60 Hz.
Listing 1 shows a sample function that gets the current refresh rate for the screen.
Listing 1 Getting the screen refresh rate
int GetMainScreenRefreshRate() |
{ |
CFDictionaryRef modeInfo; |
int refreshRate = 60; // Assume LCD screen |
modeInfo = CGDisplayCurrentMode(CGMainDisplayID()); |
if (modeInfo) |
{ |
CFNumberRef value = (CFNumberRef) CFDictionaryGetValue(modeInfo, kCGDisplayRefreshRate); |
if (value) |
{ |
CFNumberGetValue(value, kCFNumberIntType, &refreshRate); |
if (refreshRate == 0) |
refreshRate = 60; |
} |
} |
return refreshRate; |
} |
© 2003, 2006 Apple Computer, Inc. All Rights Reserved. (Last updated: 2006-04-04)