4 #include "flutter/fml/logging.h"
20 - (void)didFireWithTimestamp:(CFTimeInterval)timestamp
21 targetTimestamp:(CFTimeInterval)targetTimestamp;
26 class DisplayLinkManager {
28 static DisplayLinkManager& Instance() {
29 static DisplayLinkManager instance;
36 CFTimeInterval GetNominalOutputPeriod(CGDirectDisplayID display_id);
39 void OnDisplayLink(CVDisplayLinkRef display_link,
40 const CVTimeStamp* in_now,
41 const CVTimeStamp* in_output_time,
42 CVOptionFlags flags_in,
43 CVOptionFlags* flags_out);
46 CGDirectDisplayID display_id;
47 std::vector<_FlutterDisplayLink*> clients;
48 CVDisplayLinkRef display_link;
50 bool ShouldBeRunning() {
51 return std::any_of(clients.begin(), clients.end(),
55 std::vector<ScreenEntry> entries_;
58 void RunOrStopDisplayLink(CVDisplayLinkRef display_link,
bool should_be_running) {
59 bool is_running = CVDisplayLinkIsRunning(display_link);
60 if (should_be_running && !is_running) {
61 CVDisplayLinkStart(display_link);
62 }
else if (!should_be_running && is_running) {
63 CVDisplayLinkStop(display_link);
68 FML_DCHECK(NSThread.isMainThread);
69 for (
auto entry = entries_.begin(); entry != entries_.end(); ++entry) {
70 auto it = std::find(entry->clients.begin(), entry->clients.end(), display_link);
71 if (it != entry->clients.end()) {
72 entry->clients.erase(it);
73 if (entry->clients.empty()) {
76 CVDisplayLinkStop(entry->display_link);
77 CVDisplayLinkRelease(entry->display_link);
78 entries_.erase(entry);
81 RunOrStopDisplayLink(entry->display_link, entry->ShouldBeRunning());
89 CGDirectDisplayID display_id) {
90 FML_DCHECK(NSThread.isMainThread);
91 for (ScreenEntry& entry : entries_) {
92 if (entry.display_id == display_id) {
93 entry.clients.push_back(display_link);
94 RunOrStopDisplayLink(entry.display_link, entry.ShouldBeRunning());
100 entry.display_id = display_id;
101 entry.clients.push_back(display_link);
102 CVDisplayLinkCreateWithCGDisplay(display_id, &entry.display_link);
104 CVDisplayLinkSetOutputHandler(
106 ^(CVDisplayLinkRef display_link,
const CVTimeStamp* in_now,
const CVTimeStamp* in_output_time,
107 CVOptionFlags flags_in, CVOptionFlags* flags_out) {
108 OnDisplayLink(display_link, in_now, in_output_time, flags_in, flags_out);
113 RunOrStopDisplayLink(entry.display_link, entry.ShouldBeRunning());
114 entries_.push_back(entry);
118 for (ScreenEntry& entry : entries_) {
119 auto it = std::find(entry.clients.begin(), entry.clients.end(), display_link);
120 if (it != entry.clients.end()) {
121 RunOrStopDisplayLink(entry.display_link, entry.ShouldBeRunning());
127 CFTimeInterval DisplayLinkManager::GetNominalOutputPeriod(CGDirectDisplayID display_id) {
128 for (ScreenEntry& entry : entries_) {
129 if (entry.display_id == display_id) {
130 CVTime latency = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(entry.display_link);
131 return (CFTimeInterval)latency.timeValue / (CFTimeInterval)latency.timeScale;
137 void DisplayLinkManager::OnDisplayLink(CVDisplayLinkRef display_link,
138 const CVTimeStamp* in_now,
139 const CVTimeStamp* in_output_time,
140 CVOptionFlags flags_in,
141 CVOptionFlags* flags_out) {
142 CVTimeStamp inNow = *in_now;
143 CVTimeStamp inOutputTime = *in_output_time;
145 std::vector<_FlutterDisplayLink*> clients;
146 for (ScreenEntry& entry : entries_) {
147 if (entry.display_link == display_link) {
148 clients = entry.clients;
153 CFTimeInterval timestamp = (CFTimeInterval)inNow.hostTime / CVGetHostClockFrequency();
154 CFTimeInterval target_timestamp =
155 (CFTimeInterval)inOutputTime.hostTime / CVGetHostClockFrequency();
157 for (_FlutterDisplayLink* client : clients) {
158 [client didFireWithTimestamp:timestamp targetTimestamp:target_timestamp];
170 @"FlutterDisplayLinkViewDidMoveToWindow";
174 - (void)viewDidMoveToWindow {
175 [
super viewDidMoveToWindow];
176 [[NSNotificationCenter defaultCenter] postNotificationName:kFlutterDisplayLinkViewDidMoveToWindow
186 - (instancetype)initWithView:(NSView*)view {
187 FML_DCHECK(NSThread.isMainThread);
188 if (
self = [super init]) {
190 [view addSubview:self->_view];
192 [[NSNotificationCenter defaultCenter] addObserver:self
193 selector:@selector(viewDidChangeWindow:)
194 name:kFlutterDisplayLinkViewDidMoveToWindow
196 [[NSNotificationCenter defaultCenter] addObserver:self
197 selector:@selector(windowDidChangeScreen:)
198 name:NSWindowDidChangeScreenNotification
206 FML_DCHECK(NSThread.isMainThread);
210 [[NSNotificationCenter defaultCenter] removeObserver:self];
211 [_view removeFromSuperview];
214 DisplayLinkManager::Instance().UnregisterDisplayLink(
self);
217 - (void)updateScreen {
218 FML_DCHECK(NSThread.isMainThread);
219 DisplayLinkManager::Instance().UnregisterDisplayLink(
self);
220 std::optional<CGDirectDisplayID> displayId;
221 NSScreen* screen =
_view.window.screen;
225 [[screen deviceDescription] objectForKey:
@"NSScreenNumber"] unsignedIntValue];
231 if (displayId.has_value()) {
232 DisplayLinkManager::Instance().RegisterDisplayLink(
self, *displayId);
236 - (void)viewDidChangeWindow:(NSNotification*)notification {
237 FML_DCHECK(NSThread.isMainThread);
238 NSView* view = notification.object;
244 - (void)windowDidChangeScreen:(NSNotification*)notification {
245 FML_DCHECK(NSThread.isMainThread);
246 NSWindow* window = notification.object;
247 if (
_view.window == window) {
252 - (void)didFireWithTimestamp:(CFTimeInterval)timestamp
253 targetTimestamp:(CFTimeInterval)targetTimestamp {
254 FML_DCHECK(NSThread.isMainThread);
256 id<FlutterDisplayLinkDelegate>
delegate = _delegate;
257 [delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp];
262 FML_DCHECK(NSThread.isMainThread);
266 - (void)setPaused:(BOOL)paused {
267 FML_DCHECK(NSThread.isMainThread);
272 DisplayLinkManager::Instance().PausedDidChange(
self);
276 FML_DCHECK(NSThread.isMainThread);
277 CGDirectDisplayID display_id;
283 return DisplayLinkManager::Instance().GetNominalOutputPeriod(display_id);
289 + (instancetype)displayLinkWithView:(NSView*)view {
294 [
self doesNotRecognizeSelector:_cmd];
static NSString *const kFlutterDisplayLinkViewDidMoveToWindow
std::optional< CGDirectDisplayID > _display_id
_FlutterDisplayLinkView * _view
void invalidate()
Invalidates the display link.
CFTimeInterval nominalOutputRefreshPeriod
BOOL paused
Pauses and resumes the display link.
id< FlutterDisplayLinkDelegate > delegate
void performBlock:(void(^ block)(void))