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];
static NSString *const kFlutterDisplayLinkViewDidMoveToWindow
std::optional< CGDirectDisplayID > _display_id
_FlutterDisplayLinkView * _view
void invalidate()
Invalidates the display link. Must be called on the main thread.
CFTimeInterval nominalOutputRefreshPeriod
BOOL paused
Pauses and resumes the display link. May be called from any thread.
id< FlutterDisplayLinkDelegate > delegate