| Flutter macOS Embedder |

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.
4 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDisplayLink.h"
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
15 @class _FlutterDisplayLinkView;
16
17 @interface _FlutterDisplayLink : FlutterDisplayLink {
18 _FlutterDisplayLinkView* _view;
19 std::optional<CGDirectDisplayID> _display_id;
20 BOOL _paused;
21 }
22
23 - (void)didFireWithTimestamp:(CFTimeInterval)timestamp
24 targetTimestamp:(CFTimeInterval)targetTimestamp;
25
26 @end
27
28 namespace {
29 class DisplayLinkManager {
30 public:
31 static DisplayLinkManager& Instance() {
32 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
61 void 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
70 void 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
91 void 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
120 void 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
130 CFTimeInterval 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
140 void 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
172 static NSString* const kFlutterDisplayLinkViewDidMoveToWindow =
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
FlutterDisplayLink.h
kFlutterDisplayLinkViewDidMoveToWindow
static NSString *const kFlutterDisplayLinkViewDidMoveToWindow
Definition: FlutterDisplayLink.mm:172
_FlutterDisplayLink
Definition: FlutterDisplayLink.mm:17
_FlutterDisplayLink::_display_id
std::optional< CGDirectDisplayID > _display_id
Definition: FlutterDisplayLink.mm:19
_FlutterDisplayLink::_view
_FlutterDisplayLinkView * _view
Definition: FlutterDisplayLink.mm:18
_FlutterDisplayLink::_paused
BOOL _paused
Definition: FlutterDisplayLink.mm:20
_FlutterDisplayLinkView
Definition: FlutterDisplayLink.mm:167
FlutterDisplayLink
Definition: FlutterDisplayLink.h:22
-[FlutterDisplayLink invalidate]
void invalidate()
Invalidates the display link.
Definition: FlutterDisplayLink.mm:296
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