Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterDisplayLink.mm
Go to the documentation of this file.
1#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDisplayLink.h"
2
3#include "flutter/fml/logging.h"
4
5#include <algorithm>
6#include <optional>
7#include <thread>
8#include <vector>
9
10// Note on thread safety and locking:
11//
12// There are three mutexes used within the scope of this file:
13// - CVDisplayLink internal mutex. This is locked during every CVDisplayLink method
14// and is also held while display link calls the output handler.
15// - DisplayLinkManager mutex.
16// - _FlutterDisplayLink mutex (through @synchronized blocks).
17//
18// Special care must be taken to avoid deadlocks. Because CVDisplayLink holds the
19// mutex for the entire duration of the output handler, it is necessary for
20// DisplayLinkManager to not call any CVDisplayLink methods while holding its
21// mutex. Instead it must retain the display link instance and then call the
22// appropriate method with the mutex unlocked.
23//
24// Similarly _FlutterDisplayLink must not call any DisplayLinkManager methods
25// within the @synchronized block.
26
28
31 std::optional<CGDirectDisplayID> _display_id;
33}
34
35- (void)didFireWithTimestamp:(CFTimeInterval)timestamp
36 targetTimestamp:(CFTimeInterval)targetTimestamp;
37
38@end
39
40namespace {
42 public:
43 static DisplayLinkManager& Instance() {
45 return instance;
46 }
47
48 void UnregisterDisplayLink(_FlutterDisplayLink* display_link);
49 void RegisterDisplayLink(_FlutterDisplayLink* display_link, CGDirectDisplayID display_id);
50 void PausedDidChange(_FlutterDisplayLink* display_link);
51 CFTimeInterval GetNominalOutputPeriod(CGDirectDisplayID display_id);
52
53 private:
54 void OnDisplayLink(CVDisplayLinkRef display_link,
55 const CVTimeStamp* in_now,
56 const CVTimeStamp* in_output_time,
57 CVOptionFlags flags_in,
58 CVOptionFlags* flags_out);
59
60 struct ScreenEntry {
61 CGDirectDisplayID display_id;
62 std::vector<_FlutterDisplayLink*> clients;
63
64 /// Display link for this screen. It is not safe to call display link methods
65 /// on this object while holding the mutex. Instead the instance should be
66 /// retained, mutex unlocked and then released.
67 CVDisplayLinkRef display_link_locked;
68
69 bool ShouldBeRunning() {
70 return std::any_of(clients.begin(), clients.end(),
71 [](FlutterDisplayLink* link) { return !link.paused; });
72 }
73 };
74 std::vector<ScreenEntry> entries_;
75 std::mutex mutex_;
76};
77
78void RunOrStopDisplayLink(CVDisplayLinkRef display_link, bool should_be_running) {
79 bool is_running = CVDisplayLinkIsRunning(display_link);
80 if (should_be_running && !is_running) {
81 if (CVDisplayLinkStart(display_link) == kCVReturnError) {
82 // CVDisplayLinkStart will fail if it was called from the display link thread.
83 // The problem is that it CVDisplayLinkStop doesn't clean the pthread_t value in the display
84 // link itself. If the display link is started and stopped before before the UI thread is
85 // started (*), pthread_self() of the UI thread may have same value as the one stored in
86 // CVDisplayLink. Because this can happen at most once starting the display link from a
87 // temporary thread is a reasonable workaround.
88 //
89 // (*) Display link is started before UI thread because FlutterVSyncWaiter will run display
90 // link for one tick at the beginning to determine vsync phase.
91 //
92 // http://www.openradar.me/radar?id=5520107644125184
93 CVDisplayLinkRef retained = CVDisplayLinkRetain(display_link);
94 [NSThread detachNewThreadWithBlock:^{
95 CVDisplayLinkStart(retained);
96 CVDisplayLinkRelease(retained);
97 }];
98 }
99 } else if (!should_be_running && is_running) {
100 CVDisplayLinkStop(display_link);
101 }
102}
103
104void DisplayLinkManager::UnregisterDisplayLink(_FlutterDisplayLink* display_link) {
105 std::unique_lock<std::mutex> lock(mutex_);
106 for (auto entry = entries_.begin(); entry != entries_.end(); ++entry) {
107 auto it = std::find(entry->clients.begin(), entry->clients.end(), display_link);
108 if (it != entry->clients.end()) {
109 entry->clients.erase(it);
110 if (entry->clients.empty()) {
111 // Erasing the entry - take the display link instance and stop / release it
112 // outside of the mutex.
113 CVDisplayLinkRef display_link = entry->display_link_locked;
114 entries_.erase(entry);
115 lock.unlock();
116 CVDisplayLinkStop(display_link);
117 CVDisplayLinkRelease(display_link);
118 } else {
119 // Update the display link state outside of the mutex.
120 bool should_be_running = entry->ShouldBeRunning();
121 CVDisplayLinkRef display_link = CVDisplayLinkRetain(entry->display_link_locked);
122 lock.unlock();
123 RunOrStopDisplayLink(display_link, should_be_running);
124 CVDisplayLinkRelease(display_link);
125 }
126 return;
127 }
128 }
129}
130
131void DisplayLinkManager::RegisterDisplayLink(_FlutterDisplayLink* display_link,
132 CGDirectDisplayID display_id) {
133 std::unique_lock<std::mutex> lock(mutex_);
134 for (ScreenEntry& entry : entries_) {
135 if (entry.display_id == display_id) {
136 entry.clients.push_back(display_link);
137 bool should_be_running = entry.ShouldBeRunning();
138 CVDisplayLinkRef display_link = CVDisplayLinkRetain(entry.display_link_locked);
139 lock.unlock();
140 RunOrStopDisplayLink(display_link, should_be_running);
141 CVDisplayLinkRelease(display_link);
142 return;
143 }
144 }
145
146 ScreenEntry entry;
147 entry.display_id = display_id;
148 entry.clients.push_back(display_link);
149 CVDisplayLinkCreateWithCGDisplay(display_id, &entry.display_link_locked);
150
151 CVDisplayLinkSetOutputHandler(
152 entry.display_link_locked,
153 ^(CVDisplayLinkRef display_link, const CVTimeStamp* in_now, const CVTimeStamp* in_output_time,
154 CVOptionFlags flags_in, CVOptionFlags* flags_out) {
155 OnDisplayLink(display_link, in_now, in_output_time, flags_in, flags_out);
156 return 0;
157 });
158
159 // This is a new display link so it is safe to start it with mutex held.
160 bool should_be_running = entry.ShouldBeRunning();
161 RunOrStopDisplayLink(entry.display_link_locked, should_be_running);
162 entries_.push_back(entry);
163}
164
165void DisplayLinkManager::PausedDidChange(_FlutterDisplayLink* display_link) {
166 std::unique_lock<std::mutex> lock(mutex_);
167 for (ScreenEntry& entry : entries_) {
168 auto it = std::find(entry.clients.begin(), entry.clients.end(), display_link);
169 if (it != entry.clients.end()) {
170 bool running = entry.ShouldBeRunning();
171 CVDisplayLinkRef display_link = CVDisplayLinkRetain(entry.display_link_locked);
172 lock.unlock();
173 RunOrStopDisplayLink(display_link, running);
174 CVDisplayLinkRelease(display_link);
175 return;
176 }
177 }
178}
179
180CFTimeInterval DisplayLinkManager::GetNominalOutputPeriod(CGDirectDisplayID display_id) {
181 std::unique_lock<std::mutex> lock(mutex_);
182 for (ScreenEntry& entry : entries_) {
183 if (entry.display_id == display_id) {
184 CVDisplayLinkRef display_link = CVDisplayLinkRetain(entry.display_link_locked);
185 lock.unlock();
186 CVTime latency = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
187 CVDisplayLinkRelease(display_link);
188 return (CFTimeInterval)latency.timeValue / (CFTimeInterval)latency.timeScale;
189 }
190 }
191 return 0;
192}
193
194void DisplayLinkManager::OnDisplayLink(CVDisplayLinkRef display_link,
195 const CVTimeStamp* in_now,
196 const CVTimeStamp* in_output_time,
197 CVOptionFlags flags_in,
198 CVOptionFlags* flags_out) {
199 // Hold the mutex only while copying clients.
200 std::vector<_FlutterDisplayLink*> clients;
201 {
202 std::lock_guard<std::mutex> lock(mutex_);
203 for (ScreenEntry& entry : entries_) {
204 if (entry.display_link_locked == display_link) {
205 clients = entry.clients;
206 break;
207 }
208 }
209 }
210
211 CFTimeInterval timestamp = (CFTimeInterval)in_now->hostTime / CVGetHostClockFrequency();
212 CFTimeInterval target_timestamp =
213 (CFTimeInterval)in_output_time->hostTime / CVGetHostClockFrequency();
214
215 for (_FlutterDisplayLink* client : clients) {
216 [client didFireWithTimestamp:timestamp targetTimestamp:target_timestamp];
217 }
218}
219} // namespace
220
221@interface _FlutterDisplayLinkView : NSView {
222}
223
224@end
225
227 @"FlutterDisplayLinkViewDidMoveToWindow";
228
229@implementation _FlutterDisplayLinkView
230
231- (void)viewDidMoveToWindow {
232 [super viewDidMoveToWindow];
233 [[NSNotificationCenter defaultCenter] postNotificationName:kFlutterDisplayLinkViewDidMoveToWindow
234 object:self];
235}
236
237@end
238
239@implementation _FlutterDisplayLink
240
241@synthesize delegate = _delegate;
242
243- (instancetype)initWithView:(NSView*)view {
244 FML_DCHECK([NSThread isMainThread]);
245 if (self = [super init]) {
246 self->_view = [[_FlutterDisplayLinkView alloc] initWithFrame:CGRectZero];
247 [view addSubview:self->_view];
248 _paused = YES;
249 [[NSNotificationCenter defaultCenter] addObserver:self
250 selector:@selector(viewDidChangeWindow:)
251 name:kFlutterDisplayLinkViewDidMoveToWindow
252 object:self->_view];
253 [[NSNotificationCenter defaultCenter] addObserver:self
254 selector:@selector(windowDidChangeScreen:)
255 name:NSWindowDidChangeScreenNotification
256 object:nil];
257 [self updateScreen];
258 }
259 return self;
260}
261
262- (void)invalidate {
263 @synchronized(self) {
264 FML_DCHECK([NSThread isMainThread]);
265 // Unregister observer before removing the view to ensure
266 // that the viewDidChangeWindow notification is not received
267 // while in @synchronized block.
268 [[NSNotificationCenter defaultCenter] removeObserver:self];
269 [_view removeFromSuperview];
270 _view = nil;
271 _delegate = nil;
272 }
273 DisplayLinkManager::Instance().UnregisterDisplayLink(self);
274}
275
276- (void)updateScreen {
277 DisplayLinkManager::Instance().UnregisterDisplayLink(self);
278 std::optional<CGDirectDisplayID> displayId;
279 @synchronized(self) {
280 NSScreen* screen = _view.window.screen;
281 if (screen != nil) {
282 // https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc
283 _display_id = (CGDirectDisplayID)[
284 [[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
285 } else {
286 _display_id = std::nullopt;
287 }
288 displayId = _display_id;
289 }
290 if (displayId.has_value()) {
291 DisplayLinkManager::Instance().RegisterDisplayLink(self, *displayId);
292 }
293}
294
295- (void)viewDidChangeWindow:(NSNotification*)notification {
296 NSView* view = notification.object;
297 if (_view == view) {
298 [self updateScreen];
299 }
300}
301
302- (void)windowDidChangeScreen:(NSNotification*)notification {
303 NSWindow* window = notification.object;
304 if (_view.window == window) {
305 [self updateScreen];
306 }
307}
308
309- (void)didFireWithTimestamp:(CFTimeInterval)timestamp
310 targetTimestamp:(CFTimeInterval)targetTimestamp {
311 @synchronized(self) {
312 if (!_paused) {
313 id<FlutterDisplayLinkDelegate> delegate = _delegate;
314 [delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp];
315 }
316 }
317}
318
319- (BOOL)paused {
320 @synchronized(self) {
321 return _paused;
322 }
323}
324
325- (void)setPaused:(BOOL)paused {
326 @synchronized(self) {
327 if (_paused == paused) {
328 return;
329 }
330 _paused = paused;
331 }
332 DisplayLinkManager::Instance().PausedDidChange(self);
333}
334
335- (CFTimeInterval)nominalOutputRefreshPeriod {
336 CGDirectDisplayID display_id;
337 @synchronized(self) {
338 if (_display_id.has_value()) {
339 display_id = *_display_id;
340 } else {
341 return 0;
342 }
343 }
344 return DisplayLinkManager::Instance().GetNominalOutputPeriod(display_id);
345}
346
347@end
348
349@implementation FlutterDisplayLink
350+ (instancetype)displayLinkWithView:(NSView*)view {
351 return [[_FlutterDisplayLink alloc] initWithView:view];
352}
353
354- (void)invalidate {
355 [self doesNotRecognizeSelector:_cmd];
356}
357
358@end
GLFWwindow * window
Definition main.cc:45
VkInstance instance
Definition main.cc:48
#define FML_DCHECK(condition)
Definition logging.h:103
int BOOL