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
 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}