use std::ffi::c_void;
use std::ptr;
use core_graphics::display::CGDirectDisplayID;
use super::dispatcher::{dispatch_get_main_queue, dispatch_sys::*};
use super::ffi::CVDisplayLinkRelease;
use super::window::WindowId;
pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
display_link: CVDisplayLinkRef,
current_time: *const CVTimeStamp,
output_time: *const CVTimeStamp,
flags_in: i64,
flags_out: *mut i64,
user_info: *mut c_void,
) -> i32;
#[repr(C)]
pub struct CVTimeStamp {
pub version: u32,
pub video_time_scale: i32,
pub video_time: i64,
pub host_time: u64,
pub rate_scalar: f64,
pub video_refresh_period: i64,
pub smpte_time: CVSMPTETime,
pub flags: u64,
pub reserved: u64,
}
#[repr(C)]
pub struct CVSMPTETime {
pub subframes: i16,
pub subframe_divisor: i16,
pub counter: u32,
pub type_: u32,
pub flags: u32,
pub hours: i16,
pub minutes: i16,
pub seconds: i16,
pub frames: i16,
}
use super::ffi::CVDisplayLinkRef;
#[link(name = "CoreVideo", kind = "framework")]
extern "C" {
fn CVDisplayLinkCreateWithActiveCGDisplays(
display_link_out: *mut CVDisplayLinkRef,
) -> i32;
fn CVDisplayLinkSetCurrentCGDisplay(
display_link: CVDisplayLinkRef,
display_id: CGDirectDisplayID,
) -> i32;
fn CVDisplayLinkSetOutputCallback(
display_link: CVDisplayLinkRef,
callback: CVDisplayLinkOutputCallback,
user_info: *mut c_void,
) -> i32;
fn CVDisplayLinkStart(display_link: CVDisplayLinkRef) -> i32;
fn CVDisplayLinkStop(display_link: CVDisplayLinkRef) -> i32;
}
#[repr(C)]
#[derive(Debug)]
pub struct DisplayLinkUserData {
pub window_id: WindowId,
pub dispatch_source: dispatch_source_t,
pub view_ptr: *mut std::ffi::c_void, }
#[derive(Debug)]
pub struct DisplayLink {
display_link: CVDisplayLinkRef,
dispatch_source: dispatch_source_t,
user_data: Box<DisplayLinkUserData>,
is_running: std::cell::Cell<bool>,
}
unsafe impl Send for DisplayLink {}
unsafe impl Sync for DisplayLink {}
impl DisplayLink {
pub fn new(
display_id: CGDirectDisplayID,
window_id: WindowId,
view_ptr: *mut c_void,
callback: unsafe extern "C" fn(*mut c_void),
) -> Result<Self, &'static str> {
unsafe {
let dispatch_source = dispatch_source_create(
&_dispatch_source_type_data_add,
0,
0,
dispatch_get_main_queue(),
);
if dispatch_source.is_null() {
return Err("Failed to create GCD dispatch source");
}
let user_data = Box::new(DisplayLinkUserData {
window_id,
dispatch_source,
view_ptr,
});
dispatch_set_context(
super::dispatcher::dispatch_sys::dispatch_object_t {
_ds: dispatch_source,
},
&*user_data as *const _ as *mut c_void,
);
dispatch_source_set_event_handler_f(dispatch_source, Some(callback));
let mut display_link: CVDisplayLinkRef = ptr::null_mut();
let result = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
if result != 0 {
dispatch_source_cancel(dispatch_source);
return Err("Failed to create CVDisplayLink");
}
let result = CVDisplayLinkSetCurrentCGDisplay(display_link, display_id);
if result != 0 {
CVDisplayLinkRelease(display_link);
dispatch_source_cancel(dispatch_source);
return Err("Failed to set display for CVDisplayLink");
}
let user_data_ptr = &*user_data as *const DisplayLinkUserData as *mut c_void;
let result = CVDisplayLinkSetOutputCallback(
display_link,
display_link_callback,
user_data_ptr,
);
if result != 0 {
CVDisplayLinkRelease(display_link);
dispatch_source_cancel(dispatch_source);
return Err("Failed to set callback for CVDisplayLink");
}
tracing::info!(
"CVDisplayLink created with GCD for window {:?} on display {}",
window_id,
display_id
);
Ok(DisplayLink {
display_link,
dispatch_source,
user_data,
is_running: std::cell::Cell::new(false),
})
}
}
pub fn start(&self) -> Result<(), &'static str> {
if self.is_running.get() {
tracing::debug!(
"Display link already running for window {:?}",
self.user_data.window_id
);
return Ok(());
}
unsafe {
dispatch_resume(super::dispatcher::dispatch_sys::dispatch_object_t {
_ds: self.dispatch_source,
});
let result = CVDisplayLinkStart(self.display_link);
if result != 0 {
dispatch_suspend(super::dispatcher::dispatch_sys::dispatch_object_t {
_ds: self.dispatch_source,
});
Err("Failed to start CVDisplayLink")
} else {
self.is_running.set(true);
tracing::info!(
"CVDisplayLink started - VSync callbacks active for window {:?}",
self.user_data.window_id
);
Ok(())
}
}
}
pub fn stop(&self) -> Result<(), &'static str> {
if !self.is_running.get() {
tracing::debug!(
"Display link already stopped for window {:?}",
self.user_data.window_id
);
return Ok(());
}
unsafe {
let result = CVDisplayLinkStop(self.display_link);
dispatch_suspend(super::dispatcher::dispatch_sys::dispatch_object_t {
_ds: self.dispatch_source,
});
if result != 0 {
Err("Failed to stop CVDisplayLink")
} else {
self.is_running.set(false);
tracing::info!(
"CVDisplayLink stopped for window {:?}",
self.user_data.window_id
);
Ok(())
}
}
}
}
impl Drop for DisplayLink {
fn drop(&mut self) {
unsafe {
let _ = self.stop();
dispatch_source_cancel(self.dispatch_source);
CVDisplayLinkRelease(self.display_link);
}
}
}
unsafe extern "C" fn display_link_callback(
_display_link: CVDisplayLinkRef,
current_time: *const CVTimeStamp,
output_time: *const CVTimeStamp,
_flags_in: i64,
_flags_out: *mut i64,
user_info: *mut c_void,
) -> i32 {
if user_info.is_null() {
return 0;
}
unsafe {
let user_data = &*(user_info as *const DisplayLinkUserData);
if tracing::enabled!(tracing::Level::TRACE) {
let _current_host_time = (*current_time).host_time;
let _output_host_time = (*output_time).host_time;
let refresh_period = (*output_time).video_refresh_period;
let refresh_rate = if refresh_period > 0 {
(*output_time).video_time_scale as f64 / refresh_period as f64
} else {
60.0
};
tracing::trace!(
"VSync callback on CVDisplayLink thread: window={:?}, refresh_rate={:.1}Hz",
user_data.window_id,
refresh_rate
);
}
dispatch_source_merge_data(user_data.dispatch_source, 1);
}
0 }
pub trait DisplayLinkSupport {
fn setup_display_link(&self) -> Result<(), &'static str>;
fn start_display_link(&self) -> Result<(), &'static str>;
fn stop_display_link(&self) -> Result<(), &'static str>;
}