13
13
#elif SD_IOS || SD_TV
14
14
#import < QuartzCore/QuartzCore.h>
15
15
#endif
16
+ #include < mach/mach_time.h>
16
17
17
18
#if SD_MAC
18
19
static CVReturn DisplayLinkCallback (CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);
19
20
#endif
20
21
22
+ #if SD_WATCH
23
+ static CFTimeInterval CACurrentMediaTime (void )
24
+ {
25
+ mach_timebase_info_data_t timebase;
26
+ mach_timebase_info (&timebase);
27
+
28
+ uint64_t time = mach_absolute_time ();
29
+ double seconds = (double )time * (double )timebase.numer / (double )timebase.denom / 1e9 ;
30
+ return seconds;
31
+ }
32
+ #endif
33
+
21
34
#define kSDDisplayLinkInterval 1.0 / 60
22
35
23
36
@interface SDDisplayLink ()
24
37
38
+ @property (nonatomic , assign ) NSTimeInterval previousFireTime;
39
+ @property (nonatomic , assign ) NSTimeInterval nextFireTime;
40
+
25
41
#if SD_MAC
26
42
@property (nonatomic , assign ) CVDisplayLinkRef displayLink;
27
43
@property (nonatomic , assign ) CVTimeStamp outputTime;
@@ -32,7 +48,6 @@ @interface SDDisplayLink ()
32
48
@property (nonatomic , strong ) NSTimer *displayLink;
33
49
@property (nonatomic , strong ) NSRunLoop *runloop;
34
50
@property (nonatomic , copy ) NSRunLoopMode runloopMode;
35
- @property (nonatomic , assign ) NSTimeInterval currentFireDate;
36
51
#endif
37
52
38
53
@end
@@ -78,33 +93,47 @@ + (instancetype)displayLinkWithTarget:(id)target selector:(SEL)sel {
78
93
return displayLink;
79
94
}
80
95
81
- - (CFTimeInterval)duration {
96
+ - (NSTimeInterval )duration {
97
+ NSTimeInterval duration = 0 ;
82
98
#if SD_MAC
83
99
CVTimeStamp outputTime = self.outputTime ;
84
- NSTimeInterval duration = 0 ;
85
100
double periodPerSecond = (double )outputTime.videoTimeScale * outputTime.rateScalar ;
86
101
if (periodPerSecond > 0 ) {
87
102
duration = (double )outputTime.videoRefreshPeriod / periodPerSecond;
88
103
}
89
- #elif SD_IOS || SD_TV
90
- #pragma clang diagnostic push
91
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
92
- NSTimeInterval duration = 0 ;
93
- if (@available (iOS 10.0 , tvOS 10.0 , *)) {
94
- duration = self.displayLink .targetTimestamp - CACurrentMediaTime ();
95
- } else {
96
- duration = self.displayLink .duration * self.displayLink .frameInterval ;
97
- }
98
- #pragma clang diagnostic pop
99
104
#else
100
- NSTimeInterval duration = 0 ;
101
- if (self.displayLink .isValid && self.currentFireDate != 0 ) {
102
- NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate ((__bridge CFRunLoopTimerRef)self.displayLink );
103
- duration = nextFireDate - self.currentFireDate ;
105
+ // iOS 10+/watchOS use `nextTime`
106
+ if (@available (iOS 10.0 , tvOS 10.0 , watchOS 2.0 , *)) {
107
+ duration = self.nextFireTime - CACurrentMediaTime ();
108
+ } else {
109
+ // iOS 9 use `previousTime`
110
+ duration = CACurrentMediaTime () - self.previousFireTime ;
104
111
}
105
112
#endif
106
- if (duration <= 0 ) {
113
+ // When system sleep, the targetTimestamp will mass up, fallback refresh rate
114
+ if (duration < 0 ) {
115
+ #if SD_MAC
116
+ // Supports Pro display 120Hz
117
+ CGDirectDisplayID display = CVDisplayLinkGetCurrentCGDisplay (_displayLink);
118
+ CGDisplayModeRef mode = CGDisplayCopyDisplayMode (display);
119
+ if (mode) {
120
+ double refreshRate = CGDisplayModeGetRefreshRate (mode);
121
+ if (refreshRate > 0 ) {
122
+ duration = 1.0 / refreshRate;
123
+ } else {
124
+ duration = kSDDisplayLinkInterval ;
125
+ }
126
+ CGDisplayModeRelease (mode);
127
+ } else {
128
+ duration = kSDDisplayLinkInterval ;
129
+ }
130
+ #elif SD_IOS || SD_TV
131
+ // Fallback
132
+ duration = self.displayLink .duration ;
133
+ #else
134
+ // Watch always 60Hz
107
135
duration = kSDDisplayLinkInterval ;
136
+ #endif
108
137
}
109
138
return duration;
110
139
}
@@ -189,24 +218,25 @@ - (void)stop {
189
218
#else
190
219
[self .displayLink invalidate ];
191
220
#endif
221
+ self.previousFireTime = 0 ;
222
+ self.nextFireTime = 0 ;
192
223
}
193
224
194
225
- (void )displayLinkDidRefresh : (id )displayLink {
195
- #if SD_MAC
196
- // CVDisplayLink does not use runloop, but we can provide similar behavior for modes
197
- // May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
198
- NSString *runloopMode = self.runloopMode ;
199
- if (![runloopMode isEqualToString: NSRunLoopCommonModes ] && ![runloopMode isEqualToString: NSRunLoop .mainRunLoop.currentMode]) {
200
- return ;
226
+ #if SD_IOS || SD_TV
227
+ if (@available (iOS 10.0 , tvOS 10.0 , *)) {
228
+ self.nextFireTime = self.displayLink .targetTimestamp ;
229
+ } else {
230
+ self.previousFireTime = self.displayLink .timestamp ;
201
231
}
202
232
#endif
233
+ #if SD_WATCH
234
+ self.nextFireTime = CFRunLoopTimerGetNextFireDate ((__bridge CFRunLoopTimerRef)self.displayLink );
235
+ #endif
203
236
#pragma clang diagnostic push
204
237
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
205
238
[_target performSelector: _selector withObject: self ];
206
239
#pragma clang diagnostic pop
207
- #if SD_WATCH
208
- self.currentFireDate = CFRunLoopTimerGetNextFireDate ((__bridge CFRunLoopTimerRef)self.displayLink );
209
- #endif
210
240
}
211
241
212
242
@end
@@ -215,11 +245,16 @@ - (void)displayLinkDidRefresh:(id)displayLink {
215
245
static CVReturn DisplayLinkCallback (CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
216
246
// CVDisplayLink callback is not on main queue
217
247
SDDisplayLink *object = (__bridge SDDisplayLink *)displayLinkContext;
218
- if (inOutputTime) {
219
- object.outputTime = *inOutputTime;
248
+ // CVDisplayLink does not use runloop, but we can provide similar behavior for modes
249
+ // May use `default` runloop to avoid extra callback when in `eventTracking` (mouse drag, scroll) or `modalPanel` (modal panel)
250
+ NSString *runloopMode = object.runloopMode ;
251
+ if (![runloopMode isEqualToString: NSRunLoopCommonModes ] && ![runloopMode isEqualToString: NSRunLoop .mainRunLoop.currentMode]) {
252
+ return kCVReturnSuccess ;
220
253
}
254
+ CVTimeStamp outputTime = inOutputTime ? *inOutputTime : *inNow;
221
255
__weak SDDisplayLink *weakObject = object;
222
256
dispatch_async (dispatch_get_main_queue (), ^{
257
+ weakObject.outputTime = outputTime;
223
258
[weakObject displayLinkDidRefresh: (__bridge id )(displayLink)];
224
259
});
225
260
return kCVReturnSuccess ;
0 commit comments