1use anyhow::Result;
2use core_graphics::display::CGDirectDisplayID;
3use dispatch2::{
4 _dispatch_source_type_data_add, DispatchObject, DispatchQueue, DispatchRetained, DispatchSource,
5};
6use std::ffi::c_void;
7use util::ResultExt;
8
9pub struct DisplayLink {
10 display_link: Option<sys::DisplayLink>,
11 frame_requests: DispatchRetained<DispatchSource>,
12}
13
14impl DisplayLink {
15 pub fn new(
16 display_id: CGDirectDisplayID,
17 data: *mut c_void,
18 callback: extern "C" fn(*mut c_void),
19 ) -> Result<DisplayLink> {
20 unsafe extern "C" fn display_link_callback(
21 _display_link_out: *mut sys::CVDisplayLink,
22 _current_time: *const sys::CVTimeStamp,
23 _output_time: *const sys::CVTimeStamp,
24 _flags_in: i64,
25 _flags_out: *mut i64,
26 frame_requests: *mut c_void,
27 ) -> i32 {
28 unsafe {
29 let frame_requests = &*(frame_requests as *const DispatchSource);
30 frame_requests.merge_data(1);
31 0
32 }
33 }
34
35 unsafe {
36 let frame_requests = DispatchSource::new(
37 &raw const _dispatch_source_type_data_add as *mut _,
38 0,
39 0,
40 Some(DispatchQueue::main()),
41 );
42 frame_requests.set_context(data);
43 frame_requests.set_event_handler_f(callback);
44
45 let display_link = sys::DisplayLink::new(
46 display_id,
47 display_link_callback,
48 &*frame_requests as *const DispatchSource as *mut c_void,
49 )?;
50
51 Ok(Self {
52 display_link: Some(display_link),
53 frame_requests,
54 })
55 }
56 }
57
58 pub fn start(&mut self) -> Result<()> {
59 unsafe {
60 self.frame_requests.resume();
61 self.display_link.as_mut().unwrap().start()?;
62 }
63 Ok(())
64 }
65
66 pub fn stop(&mut self) -> Result<()> {
67 unsafe {
68 self.frame_requests.suspend();
69 self.display_link.as_mut().unwrap().stop()?;
70 }
71 Ok(())
72 }
73}
74
75impl Drop for DisplayLink {
76 fn drop(&mut self) {
77 self.stop().log_err();
78 // We see occasional segfaults on the CVDisplayLink thread.
79 //
80 // It seems possible that this happens because CVDisplayLinkRelease releases the CVDisplayLink
81 // on the main thread immediately, but the background thread that CVDisplayLink uses for timers
82 // is still accessing it.
83 //
84 // We might also want to upgrade to CADisplayLink, but that requires dropping old macOS support.
85 std::mem::forget(self.display_link.take());
86 self.frame_requests.cancel();
87 // A suspended DispatchSource cannot be destroyed.
88 self.frame_requests.resume();
89 }
90}
91
92mod sys {
93 //! Derived from display-link crate under the following license:
94 //! <https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT>
95 //! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
96 #![allow(dead_code, non_upper_case_globals)]
97
98 use anyhow::Result;
99 use core_graphics::display::CGDirectDisplayID;
100 use foreign_types::{ForeignType, foreign_type};
101 use std::{
102 ffi::c_void,
103 fmt::{self, Debug, Formatter},
104 };
105
106 #[derive(Debug)]
107 pub enum CVDisplayLink {}
108
109 foreign_type! {
110 pub unsafe type DisplayLink {
111 type CType = CVDisplayLink;
112 fn drop = CVDisplayLinkRelease;
113 fn clone = CVDisplayLinkRetain;
114 }
115 }
116
117 impl Debug for DisplayLink {
118 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
119 formatter
120 .debug_tuple("DisplayLink")
121 .field(&self.as_ptr())
122 .finish()
123 }
124 }
125
126 #[repr(C)]
127 #[derive(Clone, Copy)]
128 pub(crate) struct CVTimeStamp {
129 pub version: u32,
130 pub video_time_scale: i32,
131 pub video_time: i64,
132 pub host_time: u64,
133 pub rate_scalar: f64,
134 pub video_refresh_period: i64,
135 pub smpte_time: CVSMPTETime,
136 pub flags: u64,
137 pub reserved: u64,
138 }
139
140 pub type CVTimeStampFlags = u64;
141
142 pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
143 pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
144 pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
145 pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
146 pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
147 pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
148 pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
149 pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
150 kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
151 pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
152 kCVTimeStampTopField | kCVTimeStampBottomField;
153
154 #[repr(C)]
155 #[derive(Clone, Copy, Default)]
156 pub(crate) struct CVSMPTETime {
157 pub subframes: i16,
158 pub subframe_divisor: i16,
159 pub counter: u32,
160 pub time_type: u32,
161 pub flags: u32,
162 pub hours: i16,
163 pub minutes: i16,
164 pub seconds: i16,
165 pub frames: i16,
166 }
167
168 pub type CVSMPTETimeType = u32;
169
170 pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
171 pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
172 pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
173 pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
174 pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
175 pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
176 pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
177 pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
178
179 pub type CVSMPTETimeFlags = u32;
180
181 pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
182 pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
183
184 pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
185 display_link_out: *mut CVDisplayLink,
186 // A pointer to the current timestamp. This represents the timestamp when the callback is called.
187 current_time: *const CVTimeStamp,
188 // A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
189 output_time: *const CVTimeStamp,
190 // Unused
191 flags_in: i64,
192 // Unused
193 flags_out: *mut i64,
194 // A pointer to app-defined data.
195 display_link_context: *mut c_void,
196 ) -> i32;
197
198 #[link(name = "CoreFoundation", kind = "framework")]
199 #[link(name = "CoreVideo", kind = "framework")]
200 #[allow(improper_ctypes, unknown_lints, clippy::duplicated_attributes)]
201 unsafe extern "C" {
202 pub fn CVDisplayLinkCreateWithActiveCGDisplays(
203 display_link_out: *mut *mut CVDisplayLink,
204 ) -> i32;
205 pub fn CVDisplayLinkSetCurrentCGDisplay(
206 display_link: &mut DisplayLinkRef,
207 display_id: u32,
208 ) -> i32;
209 pub fn CVDisplayLinkSetOutputCallback(
210 display_link: &mut DisplayLinkRef,
211 callback: CVDisplayLinkOutputCallback,
212 user_info: *mut c_void,
213 ) -> i32;
214 pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
215 pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
216 pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
217 pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
218 }
219
220 impl DisplayLink {
221 /// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
222 pub unsafe fn new(
223 display_id: CGDirectDisplayID,
224 callback: CVDisplayLinkOutputCallback,
225 user_info: *mut c_void,
226 ) -> Result<Self> {
227 unsafe {
228 let mut display_link: *mut CVDisplayLink = 0 as _;
229
230 let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
231 anyhow::ensure!(code == 0, "could not create display link, code: {}", code);
232
233 let mut display_link = DisplayLink::from_ptr(display_link);
234
235 let code = CVDisplayLinkSetOutputCallback(&mut display_link, callback, user_info);
236 anyhow::ensure!(code == 0, "could not set output callback, code: {}", code);
237
238 let code = CVDisplayLinkSetCurrentCGDisplay(&mut display_link, display_id);
239 anyhow::ensure!(
240 code == 0,
241 "could not assign display to display link, code: {}",
242 code
243 );
244
245 Ok(display_link)
246 }
247 }
248 }
249
250 impl DisplayLinkRef {
251 /// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
252 pub unsafe fn start(&mut self) -> Result<()> {
253 unsafe {
254 let code = CVDisplayLinkStart(self);
255 anyhow::ensure!(code == 0, "could not start display link, code: {}", code);
256 Ok(())
257 }
258 }
259
260 /// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
261 pub unsafe fn stop(&mut self) -> Result<()> {
262 unsafe {
263 let code = CVDisplayLinkStop(self);
264 anyhow::ensure!(code == 0, "could not stop display link, code: {}", code);
265 Ok(())
266 }
267 }
268 }
269}