Try to fix display link callback duration calculation issue, using next timestamp for iOS 10+ / watchOS, previous timestamp for iOS 9
This commit is contained in:
parent
8bd4e72c33
commit
271e8d8b12
|
@ -15,7 +15,7 @@
|
|||
|
||||
@property (readonly, nonatomic, weak, nullable) id target;
|
||||
@property (readonly, nonatomic, assign, nonnull) SEL selector;
|
||||
@property (readonly, nonatomic) CFTimeInterval duration;
|
||||
@property (readonly, nonatomic) NSTimeInterval duration; // elapsed time in seconds of previous callback. (or it's first callback, use the time between `start` and callback)
|
||||
@property (readonly, nonatomic) BOOL isRunning;
|
||||
|
||||
+ (nonnull instancetype)displayLinkWithTarget:(nonnull id)target selector:(nonnull SEL)sel;
|
||||
|
|
|
@ -10,18 +10,32 @@
|
|||
#import "SDWeakProxy.h"
|
||||
#if SD_MAC
|
||||
#import <CoreVideo/CoreVideo.h>
|
||||
#elif SD_IOS || SD_TV
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#endif
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
#if SD_MAC
|
||||
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
|
||||
#endif
|
||||
|
||||
#if SD_WATCH
|
||||
static CFTimeInterval CACurrentMediaTime(void)
|
||||
{
|
||||
mach_timebase_info_data_t timebase;
|
||||
mach_timebase_info(&timebase);
|
||||
|
||||
uint64_t time = mach_absolute_time();
|
||||
double seconds = (double)time * (double)timebase.numer / (double)timebase.denom / 1e9;
|
||||
return seconds;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define kSDDisplayLinkInterval 1.0 / 60
|
||||
|
||||
@interface SDDisplayLink ()
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval previousFireTime;
|
||||
@property (nonatomic, assign) NSTimeInterval nextFireTime;
|
||||
|
||||
#if SD_MAC
|
||||
@property (nonatomic, assign) CVDisplayLinkRef displayLink;
|
||||
@property (nonatomic, assign) CVTimeStamp outputTime;
|
||||
|
@ -32,7 +46,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||
@property (nonatomic, strong) NSTimer *displayLink;
|
||||
@property (nonatomic, strong) NSRunLoop *runloop;
|
||||
@property (nonatomic, copy) NSRunLoopMode runloopMode;
|
||||
@property (nonatomic, assign) NSTimeInterval currentFireDate;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
@ -78,32 +91,24 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||
return displayLink;
|
||||
}
|
||||
|
||||
- (CFTimeInterval)duration {
|
||||
- (NSTimeInterval)duration {
|
||||
NSTimeInterval duration;
|
||||
#if SD_MAC
|
||||
CVTimeStamp outputTime = self.outputTime;
|
||||
NSTimeInterval duration = 0;
|
||||
double periodPerSecond = (double)outputTime.videoTimeScale * outputTime.rateScalar;
|
||||
if (periodPerSecond > 0) {
|
||||
duration = (double)outputTime.videoRefreshPeriod / periodPerSecond;
|
||||
}
|
||||
#elif SD_IOS || SD_TV
|
||||
duration = (double)outputTime.videoRefreshPeriod / periodPerSecond;
|
||||
#else
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
NSTimeInterval duration = 0;
|
||||
if (@available(iOS 10.0, tvOS 10.0, *)) {
|
||||
duration = self.displayLink.targetTimestamp - CACurrentMediaTime();
|
||||
// iOS 10+/watchOS use `nextTime`
|
||||
if (@available(iOS 10.0, tvOS 10.0, watchOS 2.0, *)) {
|
||||
duration = self.nextFireTime - CACurrentMediaTime();
|
||||
} else {
|
||||
duration = self.displayLink.duration * self.displayLink.frameInterval;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
#else
|
||||
NSTimeInterval duration = 0;
|
||||
if (self.displayLink.isValid && self.currentFireDate != 0) {
|
||||
NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
|
||||
duration = nextFireDate - self.currentFireDate;
|
||||
// iOS 9 use `previousTime`
|
||||
duration = CACurrentMediaTime() - self.previousFireTime;
|
||||
}
|
||||
#endif
|
||||
if (duration <= 0) {
|
||||
if (duration < 0) {
|
||||
duration = kSDDisplayLinkInterval;
|
||||
}
|
||||
return duration;
|
||||
|
@ -189,24 +194,25 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||
#else
|
||||
[self.displayLink invalidate];
|
||||
#endif
|
||||
self.previousFireTime = 0;
|
||||
self.nextFireTime = 0;
|
||||
}
|
||||
|
||||
- (void)displayLinkDidRefresh:(id)displayLink {
|
||||
#if SD_MAC
|
||||
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
|
||||
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
|
||||
NSString *runloopMode = self.runloopMode;
|
||||
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
|
||||
return;
|
||||
#if SD_IOS || SD_TV
|
||||
if (@available(iOS 10.0, tvOS 10.0, *)) {
|
||||
self.nextFireTime = self.displayLink.targetTimestamp;
|
||||
} else {
|
||||
self.previousFireTime = self.displayLink.timestamp;
|
||||
}
|
||||
#endif
|
||||
#if SD_WATCH
|
||||
self.nextFireTime = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
|
||||
#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.currentFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -215,11 +221,16 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
|||
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
|
||||
// CVDisplayLink callback is not on main queue
|
||||
SDDisplayLink *object = (__bridge SDDisplayLink *)displayLinkContext;
|
||||
if (inOutputTime) {
|
||||
object.outputTime = *inOutputTime;
|
||||
// CVDisplayLink does not use runloop, but we can provide similar behavior for modes
|
||||
// May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
|
||||
NSString *runloopMode = object.runloopMode;
|
||||
if (![runloopMode isEqualToString:NSRunLoopCommonModes] && ![runloopMode isEqualToString:NSRunLoop.mainRunLoop.currentMode]) {
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
CVTimeStamp outputTime = inOutputTime ? *inOutputTime : *inNow;
|
||||
__weak SDDisplayLink *weakObject = object;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
weakObject.outputTime = outputTime;
|
||||
[weakObject displayLinkDidRefresh:(__bridge id)(displayLink)];
|
||||
});
|
||||
return kCVReturnSuccess;
|
||||
|
|
Loading…
Reference in New Issue