12 #include "flutter/fml/logging.h"
13 #import "flutter/shell/platform/darwin/macos/InternalFlutterSwift/InternalFlutterSwift.h"
23 - (void)didFireWithTimestamp:(CFTimeInterval)timestamp
24 targetTimestamp:(CFTimeInterval)targetTimestamp;
29 class DisplayLinkManager {
31 static DisplayLinkManager& Instance() {
32 static DisplayLinkManager instance;
39 CFTimeInterval GetNominalOutputPeriod(CGDirectDisplayID display_id);
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);
49 CGDirectDisplayID display_id;
50 std::vector<_FlutterDisplayLink*> clients;
51 CVDisplayLinkRef display_link;
53 bool ShouldBeRunning() {
54 return std::any_of(clients.begin(), clients.end(),
58 std::vector<ScreenEntry> entries_;
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);
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()) {
79 CVDisplayLinkStop(entry->display_link);
80 CVDisplayLinkRelease(entry->display_link);
81 entries_.erase(entry);
84 RunOrStopDisplayLink(entry->display_link, entry->ShouldBeRunning());
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());
103 entry.display_id = display_id;
104 entry.clients.push_back(display_link);
105 CVDisplayLinkCreateWithCGDisplay(display_id, &entry.display_link);
107 CVDisplayLinkSetOutputHandler(
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);
116 RunOrStopDisplayLink(entry.display_link, entry.ShouldBeRunning());
117 entries_.push_back(entry);
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());
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;
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;
156 CFTimeInterval timestamp = (CFTimeInterval)inNow.hostTime / CVGetHostClockFrequency();
157 CFTimeInterval target_timestamp =
158 (CFTimeInterval)inOutputTime.hostTime / CVGetHostClockFrequency();
160 for (_FlutterDisplayLink* client : clients) {
161 [client didFireWithTimestamp:timestamp targetTimestamp:target_timestamp];
173 @"FlutterDisplayLinkViewDidMoveToWindow";
177 - (void)viewDidMoveToWindow {
178 [
super viewDidMoveToWindow];
179 [[NSNotificationCenter defaultCenter] postNotificationName:kFlutterDisplayLinkViewDidMoveToWindow
189 - (instancetype)initWithView:(NSView*)view {
190 FML_DCHECK(NSThread.isMainThread);
191 if (
self = [super init]) {
193 [view addSubview:self->_view];
195 [[NSNotificationCenter defaultCenter] addObserver:self
196 selector:@selector(viewDidChangeWindow:)
197 name:kFlutterDisplayLinkViewDidMoveToWindow
199 [[NSNotificationCenter defaultCenter] addObserver:self
200 selector:@selector(windowDidChangeScreen:)
201 name:NSWindowDidChangeScreenNotification
209 FML_DCHECK(NSThread.isMainThread);
213 [[NSNotificationCenter defaultCenter] removeObserver:self];
214 [_view removeFromSuperview];
217 DisplayLinkManager::Instance().UnregisterDisplayLink(
self);
220 - (void)updateScreen {
221 FML_DCHECK(NSThread.isMainThread);
222 DisplayLinkManager::Instance().UnregisterDisplayLink(
self);
223 std::optional<CGDirectDisplayID> displayId;
224 NSScreen* screen =
_view.window.screen;
228 [[screen deviceDescription] objectForKey:
@"NSScreenNumber"] unsignedIntValue];
234 if (displayId.has_value()) {
235 DisplayLinkManager::Instance().RegisterDisplayLink(
self, *displayId);
239 - (void)viewDidChangeWindow:(NSNotification*)notification {
240 FML_DCHECK(NSThread.isMainThread);
241 NSView* view = notification.object;
247 - (void)windowDidChangeScreen:(NSNotification*)notification {
248 FML_DCHECK(NSThread.isMainThread);
249 NSWindow* window = notification.object;
250 if (
_view.window == window) {
255 - (void)didFireWithTimestamp:(CFTimeInterval)timestamp
256 targetTimestamp:(CFTimeInterval)targetTimestamp {
257 FML_DCHECK(NSThread.isMainThread);
259 id<FlutterDisplayLinkDelegate>
delegate = _delegate;
260 [delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp];
265 FML_DCHECK(NSThread.isMainThread);
269 - (void)setPaused:(BOOL)paused {
270 FML_DCHECK(NSThread.isMainThread);
275 DisplayLinkManager::Instance().PausedDidChange(
self);
279 FML_DCHECK(NSThread.isMainThread);
280 CGDirectDisplayID display_id;
286 return DisplayLinkManager::Instance().GetNominalOutputPeriod(display_id);
292 + (instancetype)displayLinkWithView:(NSView*)view {
297 [
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