Use the new solution for CADisplayLink duration calculation based on WWDC 10147
This visually fix visionOS (90Hz) animated image duration We don't use Media Time because it's not correct when lag or VSync not callback in current runloop Instead, we use the `next targetTimestamp - previous targetTimestamp` to get the actual time during callbacks
This commit is contained in:
parent
1b9a2e902c
commit
112c74c1b9
|
@ -17,6 +17,8 @@
|
|||
|
||||
#if SD_MAC
|
||||
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
|
||||
#else
|
||||
static BOOL kSDDisplayLinkUseTargetTimestamp = NO; // Use `next` fire time, or `previous` fire time (only for CADisplayLink)
|
||||
#endif
|
||||
|
||||
#define kSDDisplayLinkInterval 1.0 / 60
|
||||
|
@ -65,6 +67,12 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||
_selector = sel;
|
||||
// CA/CV/NSTimer will retain to the target, we need to break this using weak proxy
|
||||
SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self];
|
||||
#if SD_UIKIT
|
||||
if (@available(iOS 10.0, tvOS 10.0, *)) {
|
||||
// Use static bool, which is a little faster than runtime OS version check
|
||||
kSDDisplayLinkUseTargetTimestamp = YES;
|
||||
}
|
||||
#endif
|
||||
#if SD_MAC
|
||||
CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
|
||||
// Simulate retain for target, the target is weak proxy to self
|
||||
|
@ -92,18 +100,32 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||
duration = (double)outputTime.videoRefreshPeriod / periodPerSecond;
|
||||
}
|
||||
#elif SD_UIKIT
|
||||
// iOS 10+/watchOS use `nextTime`
|
||||
if (@available(iOS 10.0, tvOS 10.0, *)) {
|
||||
duration = self.nextFireTime - CACurrentMediaTime();
|
||||
// iOS 10+ use current `targetTimestamp` - previous `targetTimestamp`
|
||||
// See: WWDC Session 10147 - Optimize for variable refresh rate displays
|
||||
if (kSDDisplayLinkUseTargetTimestamp) {
|
||||
NSTimeInterval nextFireTime = self.nextFireTime;
|
||||
if (nextFireTime != 0) {
|
||||
duration = self.displayLink.targetTimestamp - nextFireTime;
|
||||
} else {
|
||||
// Invalid, fallback `duration`
|
||||
duration = self.displayLink.duration;
|
||||
}
|
||||
} else {
|
||||
// iOS 9 use `previousTime`
|
||||
duration = CACurrentMediaTime() - self.previousFireTime;
|
||||
// iOS 9 use current `timestamp` - previous `timestamp`
|
||||
NSTimeInterval previousFireTime = self.previousFireTime;
|
||||
if (previousFireTime != 0) {
|
||||
duration = self.displayLink.timestamp - previousFireTime;
|
||||
} else {
|
||||
// Invalid, fallback `duration`
|
||||
duration = self.displayLink.duration;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (self.nextFireTime != 0) {
|
||||
NSTimeInterval nextFireTime = self.nextFireTime;
|
||||
if (nextFireTime != 0) {
|
||||
// `CFRunLoopTimerGetNextFireDate`: This time could be a date in the past if a run loop has not been able to process the timer since the firing time arrived.
|
||||
// Don't rely on this, always calculate based on elapsed time
|
||||
duration = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink) - self.nextFireTime;
|
||||
duration = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink) - nextFireTime;
|
||||
}
|
||||
#endif
|
||||
// When system sleep, the targetTimestamp will mass up, fallback refresh rate
|
||||
|
@ -219,17 +241,17 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||
}
|
||||
|
||||
- (void)displayLinkDidRefresh:(id)displayLink {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[_target performSelector:_selector withObject:self];
|
||||
#pragma clang diagnostic pop
|
||||
#if SD_UIKIT
|
||||
if (@available(iOS 10.0, tvOS 10.0, *)) {
|
||||
if (kSDDisplayLinkUseTargetTimestamp) {
|
||||
self.nextFireTime = self.displayLink.targetTimestamp;
|
||||
} else {
|
||||
self.previousFireTime = self.displayLink.timestamp;
|
||||
}
|
||||
#endif
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[_target performSelector:_selector withObject:self];
|
||||
#pragma clang diagnostic pop
|
||||
#if SD_WATCH
|
||||
self.nextFireTime = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue