| Flutter macOS Embedder |

FlutterVSyncWaiter.mm
Go to the documentation of this file.
1 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.h"
2 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDisplayLink.h"
3 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRunLoop.h"
4
5 #include "flutter/fml/logging.h"
6
7 #include <optional>
8 #include <vector>
9
10 #if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE)
11 #define VSYNC_TRACING_ENABLED 1
12 #endif
13
14 #if VSYNC_TRACING_ENABLED
15 #include <OSLog/OSLog.h>
16
17 // Trace vsync events using os_signpost so that they can be seen in Instruments "Points of
18 // Interest".
19 #define TRACE_VSYNC(event_type, baton) \
20 do { \
21 os_log_t log = os_log_create("FlutterVSync", "PointsOfInterest"); \
22 os_signpost_event_emit(log, OS_SIGNPOST_ID_EXCLUSIVE, event_type, "baton %lx", baton); \
23 } while (0)
24 #else
25 #define TRACE_VSYNC(event_type, baton) \
26 do { \
27 } while (0)
28 #endif
29
30 @interface FlutterVSyncWaiter () <FlutterDisplayLinkDelegate>
31 @end
32
33 // It's preferable to fire the timers slightly early than too late due to scheduling latency.
34 // 1ms before vsync should be late enough for all events to be processed.
35 static const CFTimeInterval kTimerLatencyCompensation = 0.001;
36
37 @implementation FlutterVSyncWaiter {
38 std::optional<std::uintptr_t> _pendingBaton;
39 FlutterDisplayLink* _displayLink;
40 void (^_block)(CFTimeInterval, CFTimeInterval, uintptr_t);
41 CFTimeInterval _lastTargetTimestamp;
42 BOOL _warmUpFrame;
43 }
44
45 - (instancetype)initWithDisplayLink:(FlutterDisplayLink*)displayLink
46 block:(void (^)(CFTimeInterval timestamp,
47 CFTimeInterval targetTimestamp,
48 uintptr_t baton))block {
49 FML_DCHECK([NSThread isMainThread]);
50 if (self = [super init]) {
51 _block = block;
52
53 _displayLink = displayLink;
54 _displayLink.delegate = self;
55 // Get at least one callback to initialize _lastTargetTimestamp.
56 _displayLink.paused = NO;
57 _warmUpFrame = YES;
58 }
59 return self;
60 }
61
62 // Called from display link thread.
63 - (void)onDisplayLink:(CFTimeInterval)timestamp targetTimestamp:(CFTimeInterval)targetTimestamp {
64 if (_lastTargetTimestamp == 0) {
65 // Initial vsync - timestamp will be used to determine vsync phase.
66 _lastTargetTimestamp = targetTimestamp;
67 _displayLink.paused = YES;
68 } else {
69 _lastTargetTimestamp = targetTimestamp;
70
71 // CVDisplayLink callback is called one and a half frame before the target
72 // timestamp. That can cause frame-pacing issues if the frame is rendered too early,
73 // it may also trigger frame start before events are processed.
74 CFTimeInterval minStart = targetTimestamp - _displayLink.nominalOutputRefreshPeriod;
75 CFTimeInterval current = CACurrentMediaTime();
76 CFTimeInterval remaining = std::max(minStart - current - kTimerLatencyCompensation, 0.0);
77
78 TRACE_VSYNC("DisplayLinkCallback-Original", _pendingBaton.value_or(0));
79
80 [FlutterRunLoop.mainRunLoop
81 performBlock:^{
82 if (!_pendingBaton.has_value()) {
83 TRACE_VSYNC("DisplayLinkPaused", size_t(0));
84 _displayLink.paused = YES;
85 return;
86 }
87 TRACE_VSYNC("DisplayLinkCallback-Delayed", _pendingBaton.value_or(0));
88 _block(minStart, targetTimestamp, *_pendingBaton);
89 _pendingBaton = std::nullopt;
90 }
91 afterDelay:remaining];
92 }
93 }
94
95 // Called from UI thread.
96 - (void)waitForVSync:(uintptr_t)baton {
97 FML_DCHECK([NSThread isMainThread]);
98 // CVDisplayLink start -> callback latency is two frames, there is
99 // no need to delay the warm-up frame.
100 if (_warmUpFrame) {
101 _warmUpFrame = NO;
102 TRACE_VSYNC("WarmUpFrame", baton);
103 CFTimeInterval now = CACurrentMediaTime();
104 _block(now, now, baton);
105 return;
106 }
107
108 if (_pendingBaton.has_value()) {
109 FML_LOG(WARNING) << "Engine requested vsync while another was pending";
110 _block(0, 0, *_pendingBaton);
111 _pendingBaton = std::nullopt;
112 }
113
114 TRACE_VSYNC("VSyncRequest", _pendingBaton.value_or(0));
115
116 CFTimeInterval tick_interval = _displayLink.nominalOutputRefreshPeriod;
117 if (_displayLink.paused || tick_interval == 0) {
118 // When starting display link the first notification will come in the middle
119 // of next frame, which would incur a whole frame period of latency.
120 // To avoid that, first vsync notification will be fired using a timer
121 // scheduled to fire where the next frame is expected to start.
122 // Also use a timer if display link does not belong to any display
123 // (nominalOutputRefreshPeriod being 0)
124
125 // Start of the vsync interval.
126 CFTimeInterval start = CACurrentMediaTime();
127
128 // Timer delay is calculated as the time to the next frame start.
129 CFTimeInterval delay = 0;
130
131 if (tick_interval != 0 && _lastTargetTimestamp != 0) {
132 CFTimeInterval phase = fmod(_lastTargetTimestamp, tick_interval);
133 CFTimeInterval now = start;
134 start = now - (fmod(now, tick_interval)) + phase;
135 if (start < now) {
136 start += tick_interval;
137 }
138 delay = std::max(start - now - kTimerLatencyCompensation, 0.0);
139 }
140
141 [FlutterRunLoop.mainRunLoop
142 performBlock:^{
143 CFTimeInterval targetTimestamp = start + tick_interval;
144 TRACE_VSYNC("SynthesizedInitialVSync", baton);
145 _block(start, targetTimestamp, baton);
146 }
147 afterDelay:delay];
148 _displayLink.paused = NO;
149 } else {
150 _pendingBaton = baton;
151 }
152 }
153
154 - (void)dealloc {
155 if (_pendingBaton.has_value()) {
156 FML_LOG(WARNING) << "Deallocating FlutterVSyncWaiter with a pending vsync";
157 }
158 // It is possible that block running on UI thread held the last reference to
159 // the waiter, in which case reschedule to main thread.
160 FlutterDisplayLink* link = _displayLink;
161 dispatch_async(dispatch_get_main_queue(), ^{
162 [link invalidate];
163 });
164 }
165
166 @end
FlutterDisplayLink.h
FlutterRunLoop.h
FlutterVSyncWaiter.h
_displayLink
FlutterDisplayLink * _displayLink
Definition: FlutterVSyncWaiter.mm:37
kTimerLatencyCompensation
static const CFTimeInterval kTimerLatencyCompensation
Definition: FlutterVSyncWaiter.mm:35
TRACE_VSYNC
#define TRACE_VSYNC(event_type, baton)
Definition: FlutterVSyncWaiter.mm:19
_lastTargetTimestamp
CFTimeInterval _lastTargetTimestamp
Definition: FlutterVSyncWaiter.mm:41
_block
void(^ _block)(CFTimeInterval, CFTimeInterval, uintptr_t)
_warmUpFrame
BOOL _warmUpFrame
Definition: FlutterVSyncWaiter.mm:42
FlutterDisplayLink
Definition: FlutterDisplayLink.h:19
-[FlutterDisplayLink invalidate]
void invalidate()
Invalidates the display link.
Definition: FlutterDisplayLink.mm:293
FlutterDisplayLink::nominalOutputRefreshPeriod
CFTimeInterval nominalOutputRefreshPeriod
Definition: FlutterDisplayLink.h:35
FlutterDisplayLink::paused
BOOL paused
Pauses and resumes the display link.
Definition: FlutterDisplayLink.h:30
FlutterDisplayLink::delegate
id< FlutterDisplayLinkDelegate > delegate
Definition: FlutterDisplayLink.h:27
FlutterRunLoop
Definition: FlutterRunLoop.h:16
-[FlutterRunLoop performBlock:afterDelay:]
void performBlock:afterDelay:(void(^ block)(void),[afterDelay] NSTimeInterval delay)
Definition: FlutterRunLoop.mm:95
FlutterVSyncWaiter
Definition: FlutterVSyncWaiter.h:9
FlutterDisplayLinkDelegate-p
Definition: FlutterDisplayLink.h:6