3 #include "flutter/fml/logging.h"
35 - (void)didFireWithTimestamp:(CFTimeInterval)timestamp
36 targetTimestamp:(CFTimeInterval)targetTimestamp;
41 class DisplayLinkManager {
43 static DisplayLinkManager& Instance() {
44 static DisplayLinkManager instance;
51 CFTimeInterval GetNominalOutputPeriod(CGDirectDisplayID display_id);
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);
61 CGDirectDisplayID display_id;
62 std::vector<_FlutterDisplayLink*> clients;
67 CVDisplayLinkRef display_link_locked;
69 bool ShouldBeRunning() {
70 return std::any_of(clients.begin(), clients.end(),
74 std::vector<ScreenEntry> entries_;
78 void 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) {
93 CVDisplayLinkRef retained = CVDisplayLinkRetain(display_link);
94 [NSThread detachNewThreadWithBlock:^{
95 CVDisplayLinkStart(retained);
96 CVDisplayLinkRelease(retained);
99 }
else if (!should_be_running && is_running) {
100 CVDisplayLinkStop(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()) {
113 CVDisplayLinkRef display_link = entry->display_link_locked;
114 entries_.erase(entry);
116 CVDisplayLinkStop(display_link);
117 CVDisplayLinkRelease(display_link);
120 bool should_be_running = entry->ShouldBeRunning();
121 CVDisplayLinkRef display_link = CVDisplayLinkRetain(entry->display_link_locked);
123 RunOrStopDisplayLink(display_link, should_be_running);
124 CVDisplayLinkRelease(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);
140 RunOrStopDisplayLink(display_link, should_be_running);
141 CVDisplayLinkRelease(display_link);
147 entry.display_id = display_id;
148 entry.clients.push_back(display_link);
149 CVDisplayLinkCreateWithCGDisplay(display_id, &entry.display_link_locked);
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);
160 bool should_be_running = entry.ShouldBeRunning();
161 RunOrStopDisplayLink(entry.display_link_locked, should_be_running);
162 entries_.push_back(entry);
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);
173 RunOrStopDisplayLink(display_link, running);
174 CVDisplayLinkRelease(display_link);
180 CFTimeInterval 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);
186 CVTime latency = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
187 CVDisplayLinkRelease(display_link);
188 return (CFTimeInterval)latency.timeValue / (CFTimeInterval)latency.timeScale;
194 void DisplayLinkManager::OnDisplayLink(CVDisplayLinkRef display_link,
195 const CVTimeStamp* in_now,
196 const CVTimeStamp* in_output_time,
197 CVOptionFlags flags_in,
198 CVOptionFlags* flags_out) {
200 std::vector<_FlutterDisplayLink*> clients;
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;
211 CFTimeInterval timestamp = (CFTimeInterval)in_now->hostTime / CVGetHostClockFrequency();
212 CFTimeInterval target_timestamp =
213 (CFTimeInterval)in_output_time->hostTime / CVGetHostClockFrequency();
216 [client didFireWithTimestamp:timestamp targetTimestamp:target_timestamp];
227 @"FlutterDisplayLinkViewDidMoveToWindow";
231 - (void)viewDidMoveToWindow {
232 [
super viewDidMoveToWindow];
233 [[NSNotificationCenter defaultCenter] postNotificationName:kFlutterDisplayLinkViewDidMoveToWindow
243 - (instancetype)initWithView:(NSView*)view {
244 FML_DCHECK([NSThread isMainThread]);
245 if (
self = [super init]) {
247 [view addSubview:self->_view];
249 [[NSNotificationCenter defaultCenter] addObserver:self
250 selector:@selector(viewDidChangeWindow:)
251 name:kFlutterDisplayLinkViewDidMoveToWindow
253 [[NSNotificationCenter defaultCenter] addObserver:self
254 selector:@selector(windowDidChangeScreen:)
255 name:NSWindowDidChangeScreenNotification
263 @
synchronized(
self) {
264 FML_DCHECK([NSThread isMainThread]);
268 [[NSNotificationCenter defaultCenter] removeObserver:self];
269 [_view removeFromSuperview];
273 DisplayLinkManager::Instance().UnregisterDisplayLink(
self);
276 - (void)updateScreen {
277 DisplayLinkManager::Instance().UnregisterDisplayLink(
self);
278 std::optional<CGDirectDisplayID> displayId;
279 @
synchronized(
self) {
280 NSScreen* screen =
_view.window.screen;
284 [[screen deviceDescription] objectForKey:
@"NSScreenNumber"] unsignedIntValue];
290 if (displayId.has_value()) {
291 DisplayLinkManager::Instance().RegisterDisplayLink(
self, *displayId);
295 - (void)viewDidChangeWindow:(NSNotification*)notification {
296 NSView* view = notification.object;
302 - (void)windowDidChangeScreen:(NSNotification*)notification {
303 NSWindow* window = notification.object;
304 if (
_view.window == window) {
309 - (void)didFireWithTimestamp:(CFTimeInterval)timestamp
310 targetTimestamp:(CFTimeInterval)targetTimestamp {
311 @
synchronized(
self) {
313 id<FlutterDisplayLinkDelegate>
delegate = _delegate;
314 [delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp];
320 @
synchronized(
self) {
325 - (void)setPaused:(BOOL)paused {
326 @
synchronized(
self) {
332 DisplayLinkManager::Instance().PausedDidChange(
self);
336 CGDirectDisplayID display_id;
337 @
synchronized(
self) {
344 return DisplayLinkManager::Instance().GetNominalOutputPeriod(display_id);
350 + (instancetype)displayLinkWithView:(NSView*)view {
355 [
self doesNotRecognizeSelector:_cmd];