Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 Graphics & Games

WWDC21 · 23 min · Graphics & Games

Optimize for variable refresh rate displays

Discover how to achieve smooth screen updates on all Apple platforms that support dynamic display timing. Learn techniques for pacing full-screen game updates on Adaptive Sync displays in macOS, and find out how Low Power Mode and other system states affect frame rate availability on ProMotion displays. We’ll also share best practices for driving custom drawing using display link APIs.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 10 snippets

Is Adaptive-Sync scheduling enabled objectivec · at 5:51 ↗
// Detecting an Adaptive-Sync display

- (BOOL) isAdaptiveSyncSupported:(NSScreen *)screen {
    NSTimeInterval minInterval = screen.minimumRefreshInterval;  
    NSTimeInterval maxInterval = screen.maximumRefreshInterval;  
    return minInterval != maxInterval;
}

// Detecting full-screen

- (BOOL) isWindowFullscreen:(NSWindow *)window {
    return ([window styleMask] &= NSFullScreenWindowMask) == NSFullScreenWindowMask;
}

// Tying it all together

- (BOOL) isAdaptiveSyncSchedulingEnabled:(NSScreen *)window {
    NSScreen* windowScreen = [window screen];
    return [self isWindowFullscreen:window] && [self isAdaptiveSyncSupported:windowScreen];
}
Leverage Drawable present calls objectivec · at 6:49 ↗
// Drawable present APIs with frame-pacing

[commandBuffer presentDrawable:drawable afterMinimumDuration:interval];
[commandBuffer presentDrawable:drawable atTime:t];

// Drawable present API without frame-pacing

[commandBuffer presentDrawable:drawable];
A simple example objectivec · at 7:11 ↗
id<CAMetalDrawable> currentDrawable = [metalLayer nextDrawable];

// Your encoder and command buffers here

[commandBuffer presentDrawable:currentDrawable];
Adaptive-Sync in your app 1 objectivec · at 7:55 ↗
id<CAMetalDrawable> currentDrawable = [metalLayer nextDrawable];

NSTimeInterval userFramerateCap = 78.0;
NSTimeInterval userInterval     =  1.0 / userFramerateCap;

// Your encoders and command buffers are still here
[commandBuffer presentDrawable:currentDrawable afterMinimumDuration:userInterval];
Adaptive-Sync in your app 2 objectivec · at 8:43 ↗
id<CAMetalDrawable> currentDrawable = [metalLayer nextDrawable];

// Your encoders and command buffers are still available!

NSTimeInterval averageGPUTime = screen.minimumRefreshInterval;

[commandBuffer presentDrawable:currentDrawable afterMinimumDuration:averageGPUTime];

[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
  const NSTimeInterval GPUTime = buffer.GPUEndTime - buffer.GPUStartTime;

  // Use an exponential moving average
  const double alpha = .25;

  averageGPUTime = (GPUTime * alpha) + (averageGPUTime * (1.0 - alpha));
}];
Query the display refresh rate at runtime objectivec · at 15:36 ↗
// Maximum frame rate from UIKit

NSInteger maxRate = [[UIScreen mainScreen] maximumFramesPerSecond];

// Current maximum frame rate from CoreAnimation

NSInteger currentMaxRate = round(1 / link.duration);
Use the actual frame rate of the CADisplayLink objectivec · at 17:06 ↗
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self 
                                                  selector:@selector(displayLinkCallback:)];

[link setPreferredFramesPerSecond:40];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

- (void)displayLinkCallback:(CADisplayLink *)link {
    CFTimeInterval interval = link.targetTimestamp - link.timestamp;

    //...
}
Dynamically compute the time delta 1 objectivec · at 21:47 ↗
- (void)displayLinkCallback:(CADisplayLink *)link {
    progress += link.targetTimestamp - link.timestamp;
    [self renderAnimationWithProgress:progress];
}
Dynamically compute the time delta 2 objectivec · at 21:57 ↗
- (void)displayLinkCallback:(CADisplayLink *)link {
    progress += link.targetTimestamp - previousTargetTimestamp;
    previousTargetTimestamp = link.targetTimestamp;

    [self renderAnimationWithProgress:progress];
}
Dynamically compute the time delta 3 objectivec · at 22:08 ↗
- (void)displayLinkCallback:(CADisplayLink *)link {
    progress += link.targetTimestamp - previousTargetTimestamp;
    previousTargetTimestamp = link.targetTimestamp;

    [self renderAnimationWithProgress:progress withDeadline:link.targetTimestamp];
}

Resources