//! Frame pacing for macOS windows, built on `CVDisplayLink`. //! //! CVDisplayLink has no safe teardown: `CVDisplayLinkStop` merely flags the //! link's io thread to exit "soon" and returns immediately, and there is no //! way to learn when (or whether) a final output callback has finished. Two //! crash classes came from ignoring this: //! //! * releasing the link after `stop` let the still-running io thread read the //! freed link's internals (segfault in `CVHWTime::reset`, fixed by leaking //! the link in #32116); //! * releasing the dispatch source that the output callback dereferenced via //! its context pointer let a straggler callback read freed memory (segfault //! in `dispatch_source_merge_data`, Sentry issue ZED-7XR). //! //! Instead of leaking one link and one source per teardown (and teardown //! happens on every resize tick, activation, and occlusion change), we keep a //! single immortal `CVDisplayLink` per display in a static registry and have //! windows subscribe to it: //! //! * Links are created lazily and never released, so the release race cannot //! occur. The output callback's context is the display id (an integer, not //! a pointer), and the only memory the callback dereferences is the static //! registry, so a straggler callback after `stop` is a harmless no-op. //! * Each window owns one dispatch source for the life of the window. On //! window close it is removed from the registry under the lock (making it //! unreachable from the callback), cancelled, and genuinely released. //! * A display's link runs iff it has subscribers; windows never start or //! stop links directly, so interleaved starts/stops of windows sharing a //! display cannot conflict. //! //! One tradeoff of immortal entries: a link created for a given //! `CGDirectDisplayID` is reused forever, including after the display is //! unplugged and one reappears with the same id, or after mode/refresh-rate //! changes. `CVDisplayLink` looks up display timing dynamically, so a cached //! link keeps pacing correctly; if that ever proves untrue the fix is to //! also refresh entries from a display-reconfiguration callback, not to //! release links (which would reintroduce the teardown race). //! //! Lock ordering: the output callback runs on the link's io thread and takes //! the registry lock, possibly while holding CVDisplayLink-internal locks. To //! avoid a lock cycle through those (undocumented) internals, we never call a //! CoreVideo function while holding the registry lock. Registry mutations and //! link start/stop happen on the main thread only, which keeps the `running` //! flag and the link's actual state consistent without holding the lock //! across the calls. //! //! `std::sync::Mutex` rather than `parking_lot` is deliberate: on macOS it is //! currently backed by `os_unfair_lock`, whose priority donation resolves //! inversions between the high-priority io thread and the main thread. (That //! is a std implementation detail, not a guarantee; if it changes, the cost //! is added latency under contention, not incorrectness.) use anyhow::Result; use core_graphics::display::CGDirectDisplayID; use dispatch2::{ _dispatch_source_type_data_add, DispatchObject, DispatchQueue, DispatchRetained, DispatchSource, }; use gpui_util::ResultExt; use std::{ collections::{BTreeMap, btree_map}, ffi::c_void, sync::{Mutex, MutexGuard, PoisonError}, }; static REGISTRY: Mutex = Mutex::new(Registry::new()); struct Registry { displays: BTreeMap, next_subscriber_id: u64, } impl Registry { const fn new() -> Self { Registry { displays: BTreeMap::new(), next_subscriber_id: 0, } } } struct DisplayEntry { link: sys::DisplayLink, running: bool, subscribers: Vec<(SubscriberId, DispatchRetained)>, } // SAFETY: Both fields wrapping raw pointers are refcounted handles to // thread-safe objects that are valid on any thread: `sys::DisplayLink` to a // CoreVideo object, and each subscriber's `DispatchRetained` // to a GCD object (which the display's io thread really does use, calling // `merge_data` from the output callback). All mutation of the entry itself // is serialized by the registry lock. unsafe impl Send for DisplayEntry {} #[derive(Copy, Clone, PartialEq, Eq)] struct SubscriberId(u64); fn lock_registry() -> MutexGuard<'static, Registry> { // Proceeding past poison is safe here (the map's invariants hold after // any partial mutation), and panicking instead would abort the process // when this is reached from the extern "C" output callback. REGISTRY.lock().unwrap_or_else(PoisonError::into_inner) } fn debug_assert_main_thread() { #[cfg(debug_assertions)] { use objc::{class, msg_send, sel, sel_impl}; let is_main_thread: objc::runtime::BOOL = unsafe { msg_send![class!(NSThread), isMainThread] }; debug_assert!( is_main_thread == objc::runtime::YES, "display link registry mutations must happen on the main thread; \ the registry's lock ordering and state consistency depend on it" ); } } unsafe extern "C" fn display_link_output_callback( _display_link_out: *mut sys::CVDisplayLink, _current_time: *const sys::CVTimeStamp, _output_time: *const sys::CVTimeStamp, _flags_in: i64, _flags_out: *mut i64, display_id: *mut c_void, ) -> i32 { let display_id = display_id as usize as CGDirectDisplayID; let registry = lock_registry(); if let Some(entry) = registry.displays.get(&display_id) { for (_, frame_requests) in &entry.subscribers { frame_requests.merge_data(1); } } 0 } fn subscribe( display_id: CGDirectDisplayID, frame_requests: DispatchRetained, ) -> Result { debug_assert_main_thread(); let needs_link = !lock_registry().displays.contains_key(&display_id); let new_link = if needs_link { // Created outside the registry lock; see the lock ordering note above. Some(unsafe { sys::DisplayLink::new( display_id, display_link_output_callback, display_id as usize as *mut c_void, )? }) } else { None }; let (subscriber_id, link_to_start) = { let mut registry = lock_registry(); let registry = &mut *registry; let subscriber_id = SubscriberId(registry.next_subscriber_id); registry.next_subscriber_id += 1; let entry = match (registry.displays.entry(display_id), new_link) { // If an entry appeared since the check above, dropping `new_link` // is safe: a never-started link has no io thread, so releasing it // doesn't race. (btree_map::Entry::Occupied(entry), _) => entry.into_mut(), (btree_map::Entry::Vacant(vacant), Some(link)) => vacant.insert(DisplayEntry { link, running: false, subscribers: Vec::new(), }), (btree_map::Entry::Vacant(_), None) => { // Entries are never removed, so an entry observed above cannot // have disappeared while subscriptions stay on the main thread. anyhow::bail!("display link registry entry vanished for display {display_id}"); } }; entry.subscribers.push((subscriber_id, frame_requests)); let link_to_start = if entry.running { None } else { entry.running = true; // Clone the refcounted handle so the CVDisplayLinkStart call can // happen after the lock is released. Some(entry.link.clone()) }; (subscriber_id, link_to_start) }; if let Some(mut link) = link_to_start { if let Err(error) = unsafe { link.start() } { let mut registry = lock_registry(); if let Some(entry) = registry.displays.get_mut(&display_id) { entry.running = false; entry.subscribers.retain(|(id, _)| *id != subscriber_id); } return Err(error); } } Ok(subscriber_id) } fn unsubscribe(display_id: CGDirectDisplayID, subscriber_id: SubscriberId) { debug_assert_main_thread(); let link_to_stop = { let mut registry = lock_registry(); let Some(entry) = registry.displays.get_mut(&display_id) else { return; }; entry.subscribers.retain(|(id, _)| *id != subscriber_id); if entry.subscribers.is_empty() && entry.running { entry.running = false; Some(entry.link.clone()) } else { None } }; if let Some(mut link) = link_to_stop { // A final output callback can still fire after this returns; it finds // no subscribers for this display and does nothing. unsafe { link.stop().log_err() }; } } /// A per-window source of frame requests, paced by the display the window is /// on. The wrapped dispatch source coalesces vsync ticks from the display's /// io thread and invokes `callback(data)` on the main queue. pub struct WindowFrameSource { frame_requests: DispatchRetained, registration: Option<(CGDirectDisplayID, SubscriberId)>, } impl WindowFrameSource { pub fn new(data: *mut c_void, callback: extern "C" fn(*mut c_void)) -> Self { let frame_requests = unsafe { let frame_requests = DispatchSource::new( &raw const _dispatch_source_type_data_add as *mut _, 0, 0, Some(DispatchQueue::main()), ); frame_requests.set_context(data); frame_requests.set_event_handler_f(callback); // Resume before this source can ever be dropped: destroying a // suspended dispatch source is undefined behavior (#50875). frame_requests.resume(); frame_requests }; Self { frame_requests, registration: None, } } pub fn start(&mut self, display_id: CGDirectDisplayID) -> Result<()> { self.stop(); let subscriber_id = subscribe(display_id, self.frame_requests.clone())?; self.registration = Some((display_id, subscriber_id)); Ok(()) } pub fn stop(&mut self) { if let Some((display_id, subscriber_id)) = self.registration.take() { unsubscribe(display_id, subscriber_id); } } } impl Drop for WindowFrameSource { fn drop(&mut self) { self.stop(); // Unsubscribing makes this source unreachable from the output // callback, so unlike before (ZED-7XR) it is safe to actually release // it. Cancelling first guarantees the event handler never runs again; // its context points at the window's native view, which may be // deallocated after this. self.frame_requests.cancel(); } } mod sys { //! Derived from display-link crate under the following license: //! //! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc) #![allow(dead_code, non_upper_case_globals)] use anyhow::Result; use core_graphics::display::CGDirectDisplayID; use foreign_types::{ForeignType, foreign_type}; use std::{ ffi::c_void, fmt::{self, Debug, Formatter}, }; #[derive(Debug)] pub enum CVDisplayLink {} foreign_type! { pub unsafe type DisplayLink { type CType = CVDisplayLink; fn drop = CVDisplayLinkRelease; fn clone = CVDisplayLinkRetain; } } impl Debug for DisplayLink { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { formatter .debug_tuple("DisplayLink") .field(&self.as_ptr()) .finish() } } #[repr(C)] #[derive(Clone, Copy)] pub(crate) 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, } pub type CVTimeStampFlags = u64; pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0; pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1; pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2; pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3; pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4; pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16; pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17; pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags = kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid; pub const kCVTimeStampIsInterlaced: CVTimeStampFlags = kCVTimeStampTopField | kCVTimeStampBottomField; #[repr(C)] #[derive(Clone, Copy, Default)] pub(crate) struct CVSMPTETime { pub subframes: i16, pub subframe_divisor: i16, pub counter: u32, pub time_type: u32, pub flags: u32, pub hours: i16, pub minutes: i16, pub seconds: i16, pub frames: i16, } pub type CVSMPTETimeType = u32; pub const kCVSMPTETimeType24: CVSMPTETimeType = 0; pub const kCVSMPTETimeType25: CVSMPTETimeType = 1; pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2; pub const kCVSMPTETimeType30: CVSMPTETimeType = 3; pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4; pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5; pub const kCVSMPTETimeType60: CVSMPTETimeType = 6; pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7; pub type CVSMPTETimeFlags = u32; pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0; pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1; pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn( display_link_out: *mut CVDisplayLink, // A pointer to the current timestamp. This represents the timestamp when the callback is called. current_time: *const CVTimeStamp, // A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed. output_time: *const CVTimeStamp, // Unused flags_in: i64, // Unused flags_out: *mut i64, // A pointer to app-defined data. display_link_context: *mut c_void, ) -> i32; #[link(name = "CoreFoundation", kind = "framework")] #[link(name = "CoreVideo", kind = "framework")] #[allow(improper_ctypes, unknown_lints, clippy::duplicated_attributes)] unsafe extern "C" { pub fn CVDisplayLinkCreateWithActiveCGDisplays( display_link_out: *mut *mut CVDisplayLink, ) -> i32; pub fn CVDisplayLinkSetCurrentCGDisplay( display_link: &mut DisplayLinkRef, display_id: u32, ) -> i32; pub fn CVDisplayLinkSetOutputCallback( display_link: &mut DisplayLinkRef, callback: CVDisplayLinkOutputCallback, user_info: *mut c_void, ) -> i32; pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32; pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32; pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink); pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink; } impl DisplayLink { /// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc) pub unsafe fn new( display_id: CGDirectDisplayID, callback: CVDisplayLinkOutputCallback, user_info: *mut c_void, ) -> Result { unsafe { let mut display_link: *mut CVDisplayLink = 0 as _; let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link); anyhow::ensure!(code == 0, "could not create display link, code: {}", code); let mut display_link = DisplayLink::from_ptr(display_link); let code = CVDisplayLinkSetOutputCallback(&mut display_link, callback, user_info); anyhow::ensure!(code == 0, "could not set output callback, code: {}", code); let code = CVDisplayLinkSetCurrentCGDisplay(&mut display_link, display_id); anyhow::ensure!( code == 0, "could not assign display to display link, code: {}", code ); Ok(display_link) } } } impl DisplayLinkRef { /// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc) pub unsafe fn start(&mut self) -> Result<()> { unsafe { let code = CVDisplayLinkStart(self); anyhow::ensure!(code == 0, "could not start display link, code: {}", code); Ok(()) } } /// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc) pub unsafe fn stop(&mut self) -> Result<()> { unsafe { let code = CVDisplayLinkStop(self); anyhow::ensure!(code == 0, "could not stop display link, code: {}", code); Ok(()) } } } }