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