Flutter Engine
 
Loading...
Searching...
No Matches
FlutterDisplayLink.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.
5
6#include <algorithm>
7#include <mutex>
8#include <optional>
9#include <thread>
10#include <vector>
11
12#include "flutter/fml/logging.h"
13#import "flutter/shell/platform/darwin/macos/InternalFlutterSwift/InternalFlutterSwift.h"
14
16
19 std::optional<CGDirectDisplayID> _display_id;
21}
22
23- (void)didFireWithTimestamp:(CFTimeInterval)timestamp
24 targetTimestamp:(CFTimeInterval)targetTimestamp;
25
26@end
27
28namespace {
30 public:
31 static DisplayLinkManager& Instance() {
33 return instance;
34 }
35
36 void UnregisterDisplayLink(_FlutterDisplayLink* display_link);
37 void RegisterDisplayLink(_FlutterDisplayLink* display_link, CGDirectDisplayID display_id);
38 void PausedDidChange(_FlutterDisplayLink* display_link);
39 CFTimeInterval GetNominalOutputPeriod(CGDirectDisplayID display_id);
40
41 private:
42 void OnDisplayLink(CVDisplayLinkRef display_link,
43 const CVTimeStamp* in_now,
44 const CVTimeStamp* in_output_time,
45 CVOptionFlags flags_in,
46 CVOptionFlags* flags_out);
47
48 struct ScreenEntry {
49 CGDirectDisplayID display_id;
50 std::vector<_FlutterDisplayLink*> clients;
51 CVDisplayLinkRef display_link;
52
53 bool ShouldBeRunning() {
54 return std::any_of(clients.begin(), clients.end(),
55 [](FlutterDisplayLink* link) { return !link.paused; });
56 }
57 };
58 std::vector<ScreenEntry> entries_;
59};
60
61void RunOrStopDisplayLink(CVDisplayLinkRef display_link, bool should_be_running) {
62 bool is_running = CVDisplayLinkIsRunning(display_link);
63 if (should_be_running && !is_running) {
64 CVDisplayLinkStart(display_link);
65 } else if (!should_be_running && is_running) {
66 CVDisplayLinkStop(display_link);
67 }
68}
69
70void DisplayLinkManager::UnregisterDisplayLink(_FlutterDisplayLink* display_link) {
71 FML_DCHECK(NSThread.isMainThread);
72 for (auto entry = entries_.begin(); entry != entries_.end(); ++entry) {
73 auto it = std::find(entry->clients.begin(), entry->clients.end(), display_link);
74 if (it != entry->clients.end()) {
75 entry->clients.erase(it);
76 if (entry->clients.empty()) {
77 // Erasing the entry - take the display link instance and stop / release it
78 // outside of the mutex.
79 CVDisplayLinkStop(entry->display_link);
80 CVDisplayLinkRelease(entry->display_link);
81 entries_.erase(entry);
82 } else {
83 // Update the display link state outside of the mutex.
84 RunOrStopDisplayLink(entry->display_link, entry->ShouldBeRunning());
85 }
86 return;
87 }
88 }
89}
90
91void DisplayLinkManager::RegisterDisplayLink(_FlutterDisplayLink* display_link,
92 CGDirectDisplayID display_id) {
93 FML_DCHECK(NSThread.isMainThread);
94 for (ScreenEntry& entry : entries_) {
95 if (entry.display_id == display_id) {
96 entry.clients.push_back(display_link);
97 RunOrStopDisplayLink(entry.display_link, entry.ShouldBeRunning());
98 return;
99 }
100 }
101
102 ScreenEntry entry;
103 entry.display_id = display_id;
104 entry.clients.push_back(display_link);
105 CVDisplayLinkCreateWithCGDisplay(display_id, &entry.display_link);
106
107 CVDisplayLinkSetOutputHandler(
108 entry.display_link,
109 ^(CVDisplayLinkRef display_link, const CVTimeStamp* in_now, const CVTimeStamp* in_output_time,
110 CVOptionFlags flags_in, CVOptionFlags* flags_out) {
111 OnDisplayLink(display_link, in_now, in_output_time, flags_in, flags_out);
112 return 0;
113 });
114
115 // This is a new display link so it is safe to start it with mutex held.
116 RunOrStopDisplayLink(entry.display_link, entry.ShouldBeRunning());
117 entries_.push_back(entry);
118}
119
120void DisplayLinkManager::PausedDidChange(_FlutterDisplayLink* display_link) {
121 for (ScreenEntry& entry : entries_) {
122 auto it = std::find(entry.clients.begin(), entry.clients.end(), display_link);
123 if (it != entry.clients.end()) {
124 RunOrStopDisplayLink(entry.display_link, entry.ShouldBeRunning());
125 return;
126 }
127 }
128}
129
130CFTimeInterval DisplayLinkManager::GetNominalOutputPeriod(CGDirectDisplayID display_id) {
131 for (ScreenEntry& entry : entries_) {
132 if (entry.display_id == display_id) {
133 CVTime latency = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(entry.display_link);
134 return (CFTimeInterval)latency.timeValue / (CFTimeInterval)latency.timeScale;
135 }
136 }
137 return 0;
138}
139
140void DisplayLinkManager::OnDisplayLink(CVDisplayLinkRef display_link,
141 const CVTimeStamp* in_now,
142 const CVTimeStamp* in_output_time,
143 CVOptionFlags flags_in,
144 CVOptionFlags* flags_out) {
145 CVTimeStamp inNow = *in_now;
146 CVTimeStamp inOutputTime = *in_output_time;
147 [FlutterRunLoop.mainRunLoop performBlock:^{
148 std::vector<_FlutterDisplayLink*> clients;
149 for (ScreenEntry& entry : entries_) {
150 if (entry.display_link == display_link) {
151 clients = entry.clients;
152 break;
153 }
154 }
155
156 CFTimeInterval timestamp = (CFTimeInterval)inNow.hostTime / CVGetHostClockFrequency();
157 CFTimeInterval target_timestamp =
158 (CFTimeInterval)inOutputTime.hostTime / CVGetHostClockFrequency();
159
160 for (_FlutterDisplayLink* client : clients) {
161 [client didFireWithTimestamp:timestamp targetTimestamp:target_timestamp];
162 }
163 }];
164}
165} // namespace
166
167@interface _FlutterDisplayLinkView : NSView {
168}
169
170@end
171
173 @"FlutterDisplayLinkViewDidMoveToWindow";
174
175@implementation _FlutterDisplayLinkView
176
177- (void)viewDidMoveToWindow {
178 [super viewDidMoveToWindow];
179 [[NSNotificationCenter defaultCenter] postNotificationName:kFlutterDisplayLinkViewDidMoveToWindow
180 object:self];
181}
182
183@end
184
185@implementation _FlutterDisplayLink
186
187@synthesize delegate = _delegate;
188
189- (instancetype)initWithView:(NSView*)view {
190 FML_DCHECK(NSThread.isMainThread);
191 if (self = [super init]) {
192 self->_view = [[_FlutterDisplayLinkView alloc] initWithFrame:CGRectZero];
193 [view addSubview:self->_view];
194 _paused = YES;
195 [[NSNotificationCenter defaultCenter] addObserver:self
196 selector:@selector(viewDidChangeWindow:)
197 name:kFlutterDisplayLinkViewDidMoveToWindow
198 object:self->_view];
199 [[NSNotificationCenter defaultCenter] addObserver:self
200 selector:@selector(windowDidChangeScreen:)
201 name:NSWindowDidChangeScreenNotification
202 object:nil];
203 [self updateScreen];
204 }
205 return self;
206}
207
208- (void)invalidate {
209 FML_DCHECK(NSThread.isMainThread);
210 // Unregister observer before removing the view to ensure
211 // that the viewDidChangeWindow notification is not received
212 // while in @synchronized block.
213 [[NSNotificationCenter defaultCenter] removeObserver:self];
214 [_view removeFromSuperview];
215 _view = nil;
216 _delegate = nil;
217 DisplayLinkManager::Instance().UnregisterDisplayLink(self);
218}
219
220- (void)updateScreen {
221 FML_DCHECK(NSThread.isMainThread);
222 DisplayLinkManager::Instance().UnregisterDisplayLink(self);
223 std::optional<CGDirectDisplayID> displayId;
224 NSScreen* screen = _view.window.screen;
225 if (screen != nil) {
226 // https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc
227 _display_id = (CGDirectDisplayID)[
228 [[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
229 } else {
230 _display_id = std::nullopt;
231 }
232 displayId = _display_id;
233
234 if (displayId.has_value()) {
235 DisplayLinkManager::Instance().RegisterDisplayLink(self, *displayId);
236 }
237}
238
239- (void)viewDidChangeWindow:(NSNotification*)notification {
240 FML_DCHECK(NSThread.isMainThread);
241 NSView* view = notification.object;
242 if (_view == view) {
243 [self updateScreen];
244 }
245}
246
247- (void)windowDidChangeScreen:(NSNotification*)notification {
248 FML_DCHECK(NSThread.isMainThread);
249 NSWindow* window = notification.object;
250 if (_view.window == window) {
251 [self updateScreen];
252 }
253}
254
255- (void)didFireWithTimestamp:(CFTimeInterval)timestamp
256 targetTimestamp:(CFTimeInterval)targetTimestamp {
257 FML_DCHECK(NSThread.isMainThread);
258 if (!_paused) {
259 id<FlutterDisplayLinkDelegate> delegate = _delegate;
260 [delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp];
261 }
262}
263
264- (BOOL)paused {
265 FML_DCHECK(NSThread.isMainThread);
266 return _paused;
267}
268
269- (void)setPaused:(BOOL)paused {
270 FML_DCHECK(NSThread.isMainThread);
271 if (_paused == paused) {
272 return;
273 }
274 _paused = paused;
275 DisplayLinkManager::Instance().PausedDidChange(self);
276}
277
278- (CFTimeInterval)nominalOutputRefreshPeriod {
279 FML_DCHECK(NSThread.isMainThread);
280 CGDirectDisplayID display_id;
281 if (_display_id.has_value()) {
282 display_id = *_display_id;
283 } else {
284 return 0;
285 }
286 return DisplayLinkManager::Instance().GetNominalOutputPeriod(display_id);
287}
288
289@end
290
291@implementation FlutterDisplayLink
292+ (instancetype)displayLinkWithView:(NSView*)view {
293 return [[_FlutterDisplayLink alloc] initWithView:view];
294}
295
296- (void)invalidate {
297 [self doesNotRecognizeSelector:_cmd];
298}
299
300@end
GLFWwindow * window
Definition main.cc:60
VkInstance instance
Definition main.cc:64
FlView * view
#define FML_DCHECK(condition)
Definition logging.h:122
int BOOL