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