display_link.rs

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