#![cfg(all(
target_os = "macos",
any(all(feature = "cocoa-legacy", widgets_unstripped), feature = "macos")
))]
use core::ffi::c_void;
use core::ptr;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};
const CV_RETURN_SUCCESS: i32 = 0;
const CV_TIME_STAMP_HOST_TIME_VALID: u64 = 1 << 1;
type CvOptionFlags = u64;
type CvDisplayLinkRef = *mut c_void;
#[repr(C)]
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct CvSmpTeTime {
subframes: i16,
subframe_divisor: i16,
counter: u32,
time_type: u32,
flags: u32,
hours: i16,
minutes: i16,
seconds: i16,
frames: i16,
}
#[repr(C)]
#[derive(Clone, Copy)]
#[allow(dead_code)]
struct CvTimeStamp {
version: u32,
video_time_scale: i32,
video_time: i64,
host_time: u64,
rate_scalar: f64,
video_refresh_period: i64,
smpte_time: CvSmpTeTime,
flags: u64,
reserved: u64,
}
type CvDisplayLinkOutputCallback = extern "C" fn(
CvDisplayLinkRef,
*const CvTimeStamp,
*const CvTimeStamp,
CvOptionFlags,
*mut CvOptionFlags,
*mut c_void,
) -> i32;
#[link(name = "CoreVideo", kind = "framework")]
unsafe extern "C" {
fn CVDisplayLinkCreateWithActiveCGDisplays(display_link: *mut CvDisplayLinkRef) -> 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;
fn CVDisplayLinkRelease(display_link: CvDisplayLinkRef);
}
#[repr(C)]
struct MachTimebaseInfo {
numer: u32,
denom: u32,
}
#[link(name = "System")]
unsafe extern "C" {
fn mach_absolute_time() -> u64;
fn mach_timebase_info(info: *mut MachTimebaseInfo) -> i32;
}
static NEXT_PRESENTED_HOST_TIME: AtomicU64 = AtomicU64::new(0);
static LAST_CALLBACK_HOST_TIME: AtomicU64 = AtomicU64::new(0);
static RUNNING: AtomicBool = AtomicBool::new(false);
static OBSERVERS_INSTALLED: AtomicBool = AtomicBool::new(false);
struct LinkState {
link: usize,
running: bool,
}
static STATE: Mutex<LinkState> = Mutex::new(LinkState { link: 0, running: false });
static TIMEBASE: OnceLock<(u32, u32)> = OnceLock::new();
fn lock_state() -> std::sync::MutexGuard<'static, LinkState> {
STATE.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub(crate) fn start() -> bool {
let mut state = lock_state();
if state.running {
return true;
}
let link = if state.link == 0 {
let mut created: CvDisplayLinkRef = ptr::null_mut();
let create_result = unsafe { CVDisplayLinkCreateWithActiveCGDisplays(&mut created) };
if create_result != CV_RETURN_SUCCESS || created.is_null() {
return false;
}
let callback_result = unsafe {
CVDisplayLinkSetOutputCallback(created, display_link_callback, ptr::null_mut())
};
if callback_result != CV_RETURN_SUCCESS {
unsafe { CVDisplayLinkRelease(created) };
return false;
}
state.link = created as usize;
created
} else {
state.link as CvDisplayLinkRef
};
let start_result = unsafe { CVDisplayLinkStart(link) };
if start_result != CV_RETURN_SUCCESS {
return false;
}
state.running = true;
RUNNING.store(true, Ordering::Release);
true
}
pub(crate) fn stop() {
let mut state = lock_state();
if state.link != 0 {
let link = state.link as CvDisplayLinkRef;
if state.running {
unsafe { CVDisplayLinkStop(link) };
}
unsafe { CVDisplayLinkRelease(link) };
state.link = 0;
}
state.running = false;
RUNNING.store(false, Ordering::Release);
NEXT_PRESENTED_HOST_TIME.store(0, Ordering::Release);
LAST_CALLBACK_HOST_TIME.store(0, Ordering::Release);
}
pub(crate) fn is_running() -> bool {
RUNNING.load(Ordering::Acquire)
}
pub(crate) fn observers_installed() -> bool {
OBSERVERS_INSTALLED.load(Ordering::Relaxed)
}
pub(crate) fn mark_observers_installed() {
OBSERVERS_INSTALLED.store(true, Ordering::Relaxed);
}
pub(crate) fn next_deadline() -> Option<Instant> {
if !is_running() {
return None;
}
restart_if_stalled();
deadline_from_host_time(NEXT_PRESENTED_HOST_TIME.load(Ordering::Acquire))
}
fn restart_if_stalled() {
let last = LAST_CALLBACK_HOST_TIME.load(Ordering::Acquire);
if last == 0 {
return;
}
let now = unsafe { mach_absolute_time() };
if now <= last {
return;
}
const STALL_NANOS: u64 = 1_000_000_000;
if host_ticks_to_nanos(now - last) < STALL_NANOS {
return;
}
restart();
}
pub(crate) fn restart() {
stop();
let _ = start();
}
fn deadline_from_host_time(host_time: u64) -> Option<Instant> {
if host_time == 0 {
return None;
}
let now_host = unsafe { mach_absolute_time() };
let now = Instant::now();
if host_time <= now_host {
return Some(now);
}
Some(
now.checked_add(Duration::from_nanos(host_ticks_to_nanos(host_time - now_host)))
.unwrap_or(now),
)
}
fn host_ticks_to_nanos(ticks: u64) -> u64 {
let (numer, denom) = timebase();
let nanos = u128::from(ticks) * u128::from(numer) / u128::from(denom);
u64::try_from(nanos).unwrap_or(u64::MAX)
}
fn timebase() -> (u32, u32) {
*TIMEBASE.get_or_init(|| {
let mut info = MachTimebaseInfo { numer: 0, denom: 0 };
let result = unsafe { mach_timebase_info(&mut info) };
if result != 0 || info.numer == 0 || info.denom == 0 {
(1, 1)
} else {
(info.numer, info.denom)
}
})
}
extern "C" fn display_link_callback(
_link: CvDisplayLinkRef,
_in_now: *const CvTimeStamp,
in_output_time: *const CvTimeStamp,
_flags_in: CvOptionFlags,
_flags_out: *mut CvOptionFlags,
_context: *mut c_void,
) -> i32 {
LAST_CALLBACK_HOST_TIME.store(unsafe { mach_absolute_time() }, Ordering::Release);
if !in_output_time.is_null() {
let stamp = unsafe { &*in_output_time };
if stamp.flags & CV_TIME_STAMP_HOST_TIME_VALID != 0 {
NEXT_PRESENTED_HOST_TIME.store(stamp.host_time, Ordering::Release);
}
}
crate::platform::frame_wait::wake();
CV_RETURN_SUCCESS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_timebase_is_never_zero() {
let (numer, denom) = timebase();
assert!(numer > 0 && denom > 0);
}
#[test]
fn an_unrecorded_presentation_has_no_deadline() {
assert_eq!(deadline_from_host_time(0), None);
}
#[test]
fn a_past_presentation_maps_to_now() {
let deadline = deadline_from_host_time(1).expect("a host time of 1 is still a time");
assert!(deadline <= Instant::now() + Duration::from_millis(1));
}
}