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