diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 36de3b932b..ec212c7a68 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -8,8 +8,8 @@ pub use model_context::*; use refineable::Refineable; use crate::{ - current_platform, image_cache::ImageCache, AssetSource, Context, Executor, LayoutId, - MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle, + current_platform, image_cache::ImageCache, AssetSource, Context, DisplayLinker, Executor, + LayoutId, MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; @@ -51,15 +51,15 @@ impl App { http_client: Arc, ) -> Self { let executor = platform.executor(); - let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); let unit_entity = entities.insert(entities.reserve(), ()); Self(Arc::new_cyclic(|this| { Mutex::new(AppContext { this: this.clone(), + display_linker: Arc::new(DisplayLinker::new(platform.display_linker())), + text_system: Arc::new(TextSystem::new(platform.text_system())), platform: MainThreadOnly::new(platform, executor.clone()), executor, - text_system, svg_renderer: SvgRenderer::new(asset_source), image_cache: ImageCache::new(http_client), pending_updates: 0, @@ -97,6 +97,7 @@ pub struct AppContext { text_system: Arc, pending_updates: usize, pub(crate) executor: Executor, + pub(crate) display_linker: Arc, pub(crate) svg_renderer: SvgRenderer, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, @@ -359,9 +360,15 @@ impl MainThread { let id = cx.windows.insert(None); let handle = WindowHandle::new(id); let mut window = Window::new(handle.into(), options, cx); + let display_id = window.display_id; let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); window.root_view.replace(root_view.into_any()); cx.windows.get_mut(id).unwrap().replace(window); + + cx.display_linker.on_next_frame(display_id, |_, _| { + dbg!("next frame"); + }); + handle }) } diff --git a/crates/gpui3/src/display_linker.rs b/crates/gpui3/src/display_linker.rs new file mode 100644 index 0000000000..1be32a23e2 --- /dev/null +++ b/crates/gpui3/src/display_linker.rs @@ -0,0 +1,55 @@ +use crate::{DisplayId, PlatformDisplayLinker, VideoTimestamp}; +use collections::HashMap; +use parking_lot::Mutex; +use std::sync::Arc; + +type FrameCallback = Box; + +pub struct DisplayLinker { + platform_linker: Arc, + next_frame_callbacks: Arc>>>, +} + +impl DisplayLinker { + pub(crate) fn new(platform_linker: Arc) -> Self { + Self { + platform_linker, + next_frame_callbacks: Default::default(), + } + } + + pub(crate) fn on_next_frame( + &self, + display_id: DisplayId, + callback: impl FnOnce(&VideoTimestamp, &VideoTimestamp) + Send + 'static, + ) { + let next_frame_callbacks = self.next_frame_callbacks.clone(); + let callback = Box::new(callback); + match self.next_frame_callbacks.lock().entry(display_id) { + collections::hash_map::Entry::Occupied(mut entry) => { + if entry.get().is_empty() { + self.platform_linker.start(display_id) + } + entry.get_mut().push(callback) + } + collections::hash_map::Entry::Vacant(entry) => { + // let platform_linker = self.platform_linker.clone(); + self.platform_linker.set_output_callback( + display_id, + Box::new(move |current_time, output_time| { + for callback in next_frame_callbacks + .lock() + .get_mut(&display_id) + .unwrap() + .drain(..) + { + callback(current_time, output_time); + } + // platform_linker.stop(display_id); + }), + ); + entry.insert(vec![callback]); + } + } + } +} diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 92a8043b7c..e8276488e4 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -1,6 +1,7 @@ mod app; mod assets; mod color; +mod display_linker; mod element; mod elements; mod executor; @@ -22,6 +23,7 @@ pub use anyhow::Result; pub use app::*; pub use assets::*; pub use color::*; +pub use display_linker::*; pub use element::*; pub use elements::*; pub use executor::*; diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 3b97ef097d..53a35fadcc 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -42,6 +42,7 @@ pub(crate) fn current_platform() -> Arc { pub trait Platform: 'static { fn executor(&self) -> Executor; + fn display_linker(&self) -> Arc; fn text_system(&self) -> Arc; fn run(&self, on_finish_launching: Box); @@ -99,7 +100,6 @@ pub trait PlatformDisplay: Debug { fn id(&self) -> DisplayId; fn as_any(&self) -> &dyn Any; fn bounds(&self) -> Bounds; - fn link(&self) -> Box; } #[derive(PartialEq, Eq, Hash, Copy, Clone)] @@ -156,10 +156,14 @@ pub trait PlatformDispatcher: Send + Sync { fn dispatch_on_main_thread(&self, task: Runnable); } -pub trait PlatformDisplayLink { - // fn set_output_callback(&mut self, callback: Box); - fn start(&mut self); - fn stop(&mut self); +pub trait PlatformDisplayLinker: Send + Sync { + fn set_output_callback( + &self, + display_id: DisplayId, + callback: Box, + ); + fn start(&self, display_id: DisplayId); + fn stop(&self, display_id: DisplayId); } pub trait PlatformTextSystem: Send + Sync { diff --git a/crates/gpui3/src/platform/mac.rs b/crates/gpui3/src/platform/mac.rs index 882d5efcd1..462aa209cc 100644 --- a/crates/gpui3/src/platform/mac.rs +++ b/crates/gpui3/src/platform/mac.rs @@ -2,7 +2,7 @@ ///! an origin at the bottom left of the main display. mod dispatcher; mod display; -// mod display_link; +mod display_linker; mod events; mod metal_atlas; mod metal_renderer; @@ -33,7 +33,7 @@ use std::{ pub use dispatcher::*; pub use display::*; -// pub use display_link::*; +pub use display_linker::*; pub use metal_atlas::*; pub use platform::*; pub use text_system::*; diff --git a/crates/gpui3/src/platform/mac/display.rs b/crates/gpui3/src/platform/mac/display.rs index 55148f0e28..dc064293f3 100644 --- a/crates/gpui3/src/platform/mac/display.rs +++ b/crates/gpui3/src/platform/mac/display.rs @@ -98,9 +98,4 @@ impl PlatformDisplay for MacDisplay { display_bounds_from_native(native_bounds) } } - - fn link(&self) -> Box { - unimplemented!() - // Box::new(unsafe { MacDisplayLink::new(self.0) }) - } } diff --git a/crates/gpui3/src/platform/mac/display_link.rs b/crates/gpui3/src/platform/mac/display_linker.rs similarity index 55% rename from crates/gpui3/src/platform/mac/display_link.rs rename to crates/gpui3/src/platform/mac/display_linker.rs index 72961bd28a..05381845b2 100644 --- a/crates/gpui3/src/platform/mac/display_link.rs +++ b/crates/gpui3/src/platform/mac/display_linker.rs @@ -1,41 +1,77 @@ -use crate::PlatformDisplayLink; use std::ffi::c_void; +use crate::{DisplayId, PlatformDisplayLinker}; +use collections::HashMap; +use parking_lot::Mutex; pub use sys::CVTimeStamp as VideoTimestamp; -pub struct MacDisplayLink { - sys_link: sys::DisplayLink, - output_callback: Option>, +pub struct MacDisplayLinker { + links: Mutex>, } -impl MacDisplayLink { - pub unsafe fn new(display_id: u32) -> Self { - Self { - sys_link: sys::DisplayLink::on_display(display_id).unwrap(), - output_callback: None, +struct MacDisplayLink { + output_callback: Box, + system_link: sys::DisplayLink, +} + +unsafe impl Send for MacDisplayLink {} + +impl MacDisplayLinker { + pub fn new() -> Self { + MacDisplayLinker { + links: Default::default(), } } } -impl PlatformDisplayLink for MacDisplayLink { - fn set_output_callback(&mut self, callback: Box) { - unsafe { - self.sys_link.set_output_callback( - trampoline, - self.output_callback.as_mut().unwrap() - as *mut dyn FnMut(&VideoTimestamp, &VideoTimestamp) - as *mut c_void, +impl PlatformDisplayLinker for MacDisplayLinker { + fn set_output_callback( + &self, + display_id: DisplayId, + mut output_callback: Box, + ) { + if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } { + unsafe { + system_link.set_output_callback( + trampoline, + output_callback.as_mut() as *mut dyn FnMut(_, _) as *mut c_void, + ) + } + + let previous = self.links.lock().insert( + display_id, + MacDisplayLink { + output_callback, + system_link, + }, ); + assert!( + previous.is_none(), + "You can currently only set an output callback once per display." + ) + } else { + return log::warn!("DisplayLink could not be obtained for {:?}", display_id); } - self.output_callback = Some(callback); } - fn start(&mut self) { - unsafe { self.sys_link.start() } + fn start(&self, display_id: DisplayId) { + if let Some(link) = self.links.lock().get_mut(&display_id) { + unsafe { + link.system_link.start(); + } + } else { + log::warn!("No DisplayLink callback registered for {:?}", display_id) + } } - fn stop(&mut self) { - unsafe { self.sys_link.stop() } + fn stop(&self, display_id: DisplayId) { + if let Some(link) = self.links.lock().get_mut(&display_id) { + unsafe { + link.system_link.stop(); + } + } else { + log::warn!("No DisplayLink callback registered for {:?}", display_id) + } } } @@ -48,11 +84,8 @@ unsafe extern "C" fn trampoline( context: *mut c_void, ) -> i32 { let output_callback = &mut (*(context as *mut MacDisplayLink)).output_callback; - if let Some(callback) = output_callback { - if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) { - // convert sys::CVTimeStamp to VideoTimestamp - callback(¤t_time, &output_time); - } + if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) { + output_callback(¤t_time, &output_time); } 0 } @@ -61,9 +94,8 @@ mod sys { //! Derived from display-link crate under the fololwing license: //! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT //! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc) - #![allow(dead_code)] + #![allow(dead_code, non_upper_case_globals)] - pub use cocoa::quartzcore::CVTimeStamp; use foreign_types::{foreign_type, ForeignType}; use std::{ ffi::c_void, @@ -90,6 +122,64 @@ mod sys { } } + #[repr(C)] + #[derive(Clone, Copy)] + 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, + } + + 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)] + pub 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. diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index a499f90877..a3f6fbfbe6 100644 --- a/crates/gpui3/src/platform/mac/platform.rs +++ b/crates/gpui3/src/platform/mac/platform.rs @@ -1,8 +1,9 @@ use super::BoolExt; use crate::{ AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher, - MacDisplay, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformDisplay, - PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions, + MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform, + PlatformDisplay, PlatformDisplayLinker, PlatformTextSystem, PlatformWindow, Result, + SemanticVersion, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -347,6 +348,10 @@ impl Platform for MacPlatform { self.0.lock().executor.clone() } + fn display_linker(&self) -> Arc { + Arc::new(MacDisplayLinker::new()) + } + fn text_system(&self) -> Arc { self.0.lock().text_system.clone() } diff --git a/crates/gpui3/src/platform/test.rs b/crates/gpui3/src/platform/test.rs index 562e91b2b2..88ec46e833 100644 --- a/crates/gpui3/src/platform/test.rs +++ b/crates/gpui3/src/platform/test.rs @@ -15,6 +15,10 @@ impl Platform for TestPlatform { unimplemented!() } + fn display_linker(&self) -> std::sync::Arc { + unimplemented!() + } + fn text_system(&self) -> std::sync::Arc { unimplemented!() } diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index f255172c77..a4ff0cfa83 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,10 +1,11 @@ use crate::{ image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace, - BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, - GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, - MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, - RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, - TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, + BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect, Element, EntityId, + FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, + MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, + PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, + SharedString, Size, Style, TaffyLayoutEngine, Task, WeakHandle, WindowOptions, + SUBPIXEL_VARIANTS, }; use anyhow::Result; use smallvec::SmallVec; @@ -16,6 +17,7 @@ pub struct AnyWindow {} pub struct Window { handle: AnyWindowHandle, platform_window: MainThreadOnly>, + pub(crate) display_id: DisplayId, // todo!("make private again?") sprite_atlas: Arc, rem_size: Pixels, content_size: Size, @@ -35,6 +37,7 @@ impl Window { cx: &mut MainThread, ) -> Self { let platform_window = cx.platform().open_window(handle, options); + let display_id = platform_window.display().id(); let sprite_atlas = platform_window.sprite_atlas(); let mouse_position = platform_window.mouse_position(); let content_size = platform_window.content_size(); @@ -46,6 +49,12 @@ impl Window { cx.update_window(handle, |cx| { cx.window.scene = Scene::new(scale_factor); cx.window.content_size = content_size; + cx.window.display_id = cx + .window + .platform_window + .borrow_on_main_thread() + .display() + .id(); cx.window.dirty = true; }) .log_err(); @@ -57,6 +66,7 @@ impl Window { Window { handle, platform_window, + display_id, sprite_atlas, rem_size: px(16.), content_size,