| Flutter macOS Embedder |

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