Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/native/apple/frameworks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,65 @@ extern "C" {
pub fn MTLCopyAllDevices() -> ObjcId; //TODO: Array
}

#[cfg(target_os = "macos")]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CVTime {
pub time_value: i64,
pub time_scale: i32,
pub flags: i32,
pub epoch: i64,
}

#[cfg(target_os = "macos")]
pub type CVDisplayLinkRef = *mut c_void;

#[cfg(target_os = "macos")]
pub type CVOptionFlags = u64;

#[cfg(target_os = "macos")]
#[repr(C)]
#[derive(Clone, Copy, Debug)]
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: i64,
pub flags: u64,
pub reserved: u64,
}

#[cfg(target_os = "macos")]
pub type CVDisplayLinkOutputCallback = extern "C" fn(
display_link: CVDisplayLinkRef,
now: *const CVTimeStamp,
output_time: *const CVTimeStamp,
flags_in: CVOptionFlags,
flags_out: *mut CVOptionFlags,
display_link_context: *mut c_void,
) -> i32;

#[cfg(target_os = "macos")]
pub const kCVTimeIsIndefinite: i32 = 1 << 0;

#[cfg(target_os = "macos")]
#[link(name = "CoreVideo", kind = "framework")]
extern "C" {
pub fn CVDisplayLinkCreateWithActiveCGDisplays(display_link_out: *mut CVDisplayLinkRef) -> i32;
pub fn CVDisplayLinkSetOutputCallback(
display_link: CVDisplayLinkRef,
callback: CVDisplayLinkOutputCallback,
user_info: *mut c_void,
) -> i32;
pub fn CVDisplayLinkStart(display_link: CVDisplayLinkRef) -> i32;
pub fn CVDisplayLinkStop(display_link: CVDisplayLinkRef) -> i32;
pub fn CVDisplayLinkRelease(display_link: CVDisplayLinkRef);
pub fn CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link: CVDisplayLinkRef) -> CVTime;
}

// Foundation

#[repr(C)]
Expand Down
112 changes: 110 additions & 2 deletions src/native/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use {
std::{
collections::HashMap,
os::raw::c_void,
sync::mpsc::Receiver,
sync::{
Condvar, Mutex,
mpsc::Receiver,
},
time::{Duration, Instant},
},
};
Expand Down Expand Up @@ -994,7 +997,9 @@ unsafe fn create_opengl_view(

display.gl_context = msg_send![gl_context, initWithFormat: glpixelformat_obj shareContext: nil];

let mut swap_interval = 1;
// Set the swap interval to 0 to disable vsync, because we're using CVDisplayLink for frame pacing
// and we don't want to have an extra vsync delay on top of that.
let mut swap_interval = 0;
let () = msg_send![display.gl_context,
setValues:&mut swap_interval
forParameter:NSOpenGLContextParameterSwapInterval];
Expand All @@ -1016,6 +1021,97 @@ impl crate::native::Clipboard for MacosClipboard {
fn set(&mut self, _data: &str) {}
}

struct FrameSignal {
frame_ready: Mutex<bool>,
cond: Condvar,
}

extern "C" fn display_link_frame_ready_callback(
_display_link: CVDisplayLinkRef,
_now: *const CVTimeStamp,
_output_time: *const CVTimeStamp,
_flags_in: CVOptionFlags,
_flags_out: *mut CVOptionFlags,
display_link_context: *mut c_void,
) -> i32 {
unsafe {
let frame_signal = &*(display_link_context as *const FrameSignal);
if let Ok(mut frame_ready) = frame_signal.frame_ready.lock() {
*frame_ready = true;
frame_signal.cond.notify_one();
}
}
0
}

struct FramePacer {
display_link: CVDisplayLinkRef,
frame_signal: Box<FrameSignal>,
}

impl FramePacer {
fn new() -> Option<Self> {
unsafe {
let mut display_link: CVDisplayLinkRef = std::ptr::null_mut();
if CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link as *mut _) != 0
|| display_link.is_null()
{
return None;
}

let frame_signal = Box::new(FrameSignal {
frame_ready: Mutex::new(false),
cond: Condvar::new(),
});

if CVDisplayLinkSetOutputCallback(
display_link,
display_link_frame_ready_callback,
(&*frame_signal as *const FrameSignal) as *mut c_void,
) != 0
{
CVDisplayLinkRelease(display_link);
return None;
}

if CVDisplayLinkStart(display_link) != 0 {
CVDisplayLinkRelease(display_link);
return None;
}

Some(Self {
display_link,
frame_signal,
})
}
}

fn wait_next_frame(&self, timeout: Duration) {
let mut frame_ready = self.frame_signal.frame_ready.lock().unwrap();
if *frame_ready {
*frame_ready = false;
return;
}

let (mut frame_ready, _) = self.frame_signal.cond.wait_timeout(frame_ready, timeout).unwrap();

if *frame_ready {
*frame_ready = false;
}
}
}

impl Drop for FramePacer {
fn drop(&mut self) {
unsafe {
if !self.display_link.is_null() {
let _ = CVDisplayLinkStop(self.display_link);
CVDisplayLinkRelease(self.display_link);
}
}
}
}

unsafe extern "C" fn release_data(info: *mut c_void, _: *const c_void, _: usize) {
drop(Box::from_raw(info as *mut &[u8]));
}
Expand Down Expand Up @@ -1305,8 +1401,19 @@ where
// Basically reimplementing msg_send![ns_app, run] here
let distant_future: ObjcId = msg_send![class!(NSDate), distantFuture];
let distant_past: ObjcId = msg_send![class!(NSDate), distantPast];
let frame_pacer = if conf.platform.blocking_event_loop {
None
} else {
FramePacer::new()
};
let mut done = false;
while !(done || crate::native_display().lock().unwrap().quit_ordered) {

// Wait at the top for just in time rendering
if let Some(frame_pacer) = frame_pacer.as_ref() {
frame_pacer.wait_next_frame(Duration::from_millis(50));
}

while let Ok(request) = display.native_requests.try_recv() {
display.process_request(request);
}
Expand Down Expand Up @@ -1336,5 +1443,6 @@ where
if !conf.platform.blocking_event_loop || display.update_requested {
perform_redraw(&mut display, conf.platform.apple_gfx_api, false);
}

}
}
Loading