display_link.rs

  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}