mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 15:44:20 +03:00
Replace CADisplayLink
with CVDisplayLink
(#7583)
Release Notes: - Fixed a bug that caused Zed to render at 60fps even on ProMotion displays. - Fixed a bug that could saturate the main thread event loop in certain circumstances. --------- Co-authored-by: Thorsten <thorsten@zed.dev> Co-authored-by: Nathan <nathan@zed.dev> Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
9e538e7916
commit
67b96b2b40
@ -27,6 +27,7 @@ fn generate_dispatch_bindings() {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header("src/platform/mac/dispatch.h")
|
||||
.allowlist_var("_dispatch_main_q")
|
||||
.allowlist_var("_dispatch_source_type_data_add")
|
||||
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
|
||||
.allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
|
||||
.allowlist_var("DISPATCH_TIME_NOW")
|
||||
@ -34,6 +35,13 @@ fn generate_dispatch_bindings() {
|
||||
.allowlist_function("dispatch_async_f")
|
||||
.allowlist_function("dispatch_after_f")
|
||||
.allowlist_function("dispatch_time")
|
||||
.allowlist_function("dispatch_source_merge_data")
|
||||
.allowlist_function("dispatch_source_create")
|
||||
.allowlist_function("dispatch_source_set_event_handler_f")
|
||||
.allowlist_function("dispatch_resume")
|
||||
.allowlist_function("dispatch_suspend")
|
||||
.allowlist_function("dispatch_source_cancel")
|
||||
.allowlist_function("dispatch_set_context")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||
.layout_tests(false)
|
||||
.generate()
|
||||
|
@ -18,8 +18,8 @@ use crate::WindowAppearance;
|
||||
use crate::{
|
||||
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
|
||||
Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
|
||||
DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
|
||||
LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render,
|
||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
|
||||
TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId,
|
||||
};
|
||||
@ -193,7 +193,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
|
||||
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
||||
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
||||
type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
|
||||
@ -213,8 +212,6 @@ pub struct AppContext {
|
||||
pending_updates: usize,
|
||||
pub(crate) actions: Rc<ActionRegistry>,
|
||||
pub(crate) active_drag: Option<AnyDrag>,
|
||||
pub(crate) next_frame_callbacks: FxHashMap<DisplayId, Vec<FrameCallback>>,
|
||||
pub(crate) frame_consumers: FxHashMap<DisplayId, Task<()>>,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
pub(crate) svg_renderer: SvgRenderer,
|
||||
@ -275,8 +272,6 @@ impl AppContext {
|
||||
flushing_effects: false,
|
||||
pending_updates: 0,
|
||||
active_drag: None,
|
||||
next_frame_callbacks: FxHashMap::default(),
|
||||
frame_consumers: FxHashMap::default(),
|
||||
background_executor: executor,
|
||||
foreground_executor,
|
||||
svg_renderer: SvgRenderer::new(asset_source.clone()),
|
||||
|
@ -81,14 +81,6 @@ pub(crate) trait Platform: 'static {
|
||||
/// Returns the appearance of the application's windows.
|
||||
fn window_appearance(&self) -> WindowAppearance;
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut() + Send>,
|
||||
);
|
||||
fn start_display_link(&self, display_id: DisplayId);
|
||||
fn stop_display_link(&self, display_id: DisplayId);
|
||||
|
||||
fn open_url(&self, url: &str);
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
||||
fn prompt_for_paths(
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! an origin at the bottom left of the main display.
|
||||
mod dispatcher;
|
||||
mod display;
|
||||
mod display_linker;
|
||||
mod display_link;
|
||||
mod events;
|
||||
mod metal_atlas;
|
||||
mod metal_renderer;
|
||||
@ -23,7 +23,7 @@ use std::ops::Range;
|
||||
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use display_linker::*;
|
||||
pub(crate) use display_link::*;
|
||||
pub(crate) use metal_atlas::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use text_system::*;
|
||||
|
@ -1 +1,2 @@
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <dispatch/source.h>
|
||||
|
@ -1,93 +1,96 @@
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
mem,
|
||||
sync::{Arc, Weak},
|
||||
use crate::{
|
||||
dispatch_get_main_queue,
|
||||
dispatch_sys::{
|
||||
_dispatch_source_type_data_add, dispatch_resume, dispatch_set_context,
|
||||
dispatch_source_cancel, dispatch_source_create, dispatch_source_merge_data,
|
||||
dispatch_source_set_event_handler_f, dispatch_source_t, dispatch_suspend,
|
||||
},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use core_graphics::display::CGDirectDisplayID;
|
||||
use std::ffi::c_void;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::DisplayId;
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub(crate) struct MacDisplayLinker {
|
||||
links: HashMap<DisplayId, MacDisplayLink>,
|
||||
pub struct DisplayLink {
|
||||
display_link: sys::DisplayLink,
|
||||
frame_requests: dispatch_source_t,
|
||||
}
|
||||
|
||||
struct MacDisplayLink {
|
||||
system_link: sys::DisplayLink,
|
||||
_output_callback: Arc<OutputCallback>,
|
||||
}
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn new() -> Self {
|
||||
MacDisplayLinker {
|
||||
links: Default::default(),
|
||||
impl DisplayLink {
|
||||
pub fn new(
|
||||
display_id: CGDirectDisplayID,
|
||||
data: *mut c_void,
|
||||
callback: unsafe extern "C" fn(*mut c_void),
|
||||
) -> Result<DisplayLink> {
|
||||
unsafe extern "C" fn display_link_callback(
|
||||
_display_link_out: *mut sys::CVDisplayLink,
|
||||
_current_time: *const sys::CVTimeStamp,
|
||||
_output_time: *const sys::CVTimeStamp,
|
||||
_flags_in: i64,
|
||||
_flags_out: *mut i64,
|
||||
frame_requests: *mut c_void,
|
||||
) -> i32 {
|
||||
let frame_requests = frame_requests as dispatch_source_t;
|
||||
dispatch_source_merge_data(frame_requests, 1);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutputCallback = Mutex<Box<dyn FnMut() + Send>>;
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn set_output_callback(
|
||||
&mut self,
|
||||
display_id: DisplayId,
|
||||
output_callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
||||
let callback = Arc::new(Mutex::new(output_callback));
|
||||
let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
|
||||
unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
|
||||
|
||||
self.links.insert(
|
||||
display_id,
|
||||
MacDisplayLink {
|
||||
_output_callback: callback,
|
||||
system_link,
|
||||
},
|
||||
unsafe {
|
||||
let frame_requests = dispatch_source_create(
|
||||
&_dispatch_source_type_data_add,
|
||||
0,
|
||||
0,
|
||||
dispatch_get_main_queue(),
|
||||
);
|
||||
} else {
|
||||
log::warn!("DisplayLink could not be obtained for {:?}", display_id);
|
||||
dispatch_set_context(
|
||||
crate::dispatch_sys::dispatch_object_t {
|
||||
_ds: frame_requests,
|
||||
},
|
||||
data,
|
||||
);
|
||||
dispatch_source_set_event_handler_f(frame_requests, Some(callback));
|
||||
|
||||
let display_link = sys::DisplayLink::new(
|
||||
display_id,
|
||||
display_link_callback,
|
||||
frame_requests as *mut c_void,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
display_link,
|
||||
frame_requests,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.start();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
unsafe {
|
||||
dispatch_resume(crate::dispatch_sys::dispatch_object_t {
|
||||
_ds: self.frame_requests,
|
||||
});
|
||||
self.display_link.start()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.stop();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
pub fn stop(&mut self) -> Result<()> {
|
||||
unsafe {
|
||||
dispatch_suspend(crate::dispatch_sys::dispatch_object_t {
|
||||
_ds: self.frame_requests,
|
||||
});
|
||||
self.display_link.stop()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn trampoline(
|
||||
_display_link_out: *mut sys::CVDisplayLink,
|
||||
current_time: *const sys::CVTimeStamp,
|
||||
output_time: *const sys::CVTimeStamp,
|
||||
_flags_in: i64,
|
||||
_flags_out: *mut i64,
|
||||
user_data: *mut c_void,
|
||||
) -> i32 {
|
||||
if let Some((_current_time, _output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
|
||||
let output_callback: Weak<OutputCallback> =
|
||||
Weak::from_raw(user_data as *mut OutputCallback);
|
||||
if let Some(output_callback) = output_callback.upgrade() {
|
||||
(output_callback.lock())()
|
||||
impl Drop for DisplayLink {
|
||||
fn drop(&mut self) {
|
||||
self.stop().log_err();
|
||||
unsafe {
|
||||
dispatch_source_cancel(self.frame_requests);
|
||||
}
|
||||
mem::forget(output_callback);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
mod sys {
|
||||
@ -96,10 +99,12 @@ mod sys {
|
||||
//! 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::{foreign_type, ForeignType};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
fmt::{Debug, Formatter, Result},
|
||||
fmt::{self, Debug, Formatter},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -114,7 +119,7 @@ mod sys {
|
||||
}
|
||||
|
||||
impl Debug for DisplayLink {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> Result {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
formatter
|
||||
.debug_tuple("DisplayLink")
|
||||
.field(&self.as_ptr())
|
||||
@ -201,19 +206,15 @@ mod sys {
|
||||
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
||||
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
display_id: u32,
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetOutputCallback(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
display_id: u32,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
|
||||
@ -221,52 +222,46 @@ mod sys {
|
||||
}
|
||||
|
||||
impl DisplayLink {
|
||||
/// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
|
||||
pub unsafe fn new() -> Option<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
|
||||
pub unsafe fn on_display(display_id: u32) -> Option<Self> {
|
||||
pub unsafe fn new(
|
||||
display_id: CGDirectDisplayID,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) -> Result<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
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: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
|
||||
pub unsafe fn set_output_callback(
|
||||
&mut self,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) {
|
||||
assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
|
||||
pub unsafe fn set_current_display(&mut self, display_id: u32) {
|
||||
assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
|
||||
pub unsafe fn start(&mut self) {
|
||||
assert_eq!(CVDisplayLinkStart(self), 0);
|
||||
pub unsafe fn start(&mut self) -> Result<()> {
|
||||
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) {
|
||||
assert_eq!(CVDisplayLinkStop(self), 0);
|
||||
pub unsafe fn stop(&mut self) -> Result<()> {
|
||||
let code = CVDisplayLinkStop(self);
|
||||
anyhow::ensure!(code == 0, "could not stop display link, code: {}", code);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ const INSTANCE_BUFFER_SIZE: usize = 2 * 1024 * 1024; // This is an arbitrary dec
|
||||
pub(crate) struct MetalRenderer {
|
||||
device: metal::Device,
|
||||
layer: metal::MetalLayer,
|
||||
presents_with_transaction: bool,
|
||||
command_queue: CommandQueue,
|
||||
paths_rasterization_pipeline_state: metal::RenderPipelineState,
|
||||
path_sprites_pipeline_state: metal::RenderPipelineState,
|
||||
@ -60,8 +61,8 @@ impl MetalRenderer {
|
||||
let layer = metal::MetalLayer::new();
|
||||
layer.set_device(&device);
|
||||
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||
layer.set_presents_with_transaction(true);
|
||||
layer.set_opaque(true);
|
||||
layer.set_maximum_drawable_count(3);
|
||||
unsafe {
|
||||
let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
|
||||
let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
|
||||
@ -174,6 +175,7 @@ impl MetalRenderer {
|
||||
Self {
|
||||
device,
|
||||
layer,
|
||||
presents_with_transaction: false,
|
||||
command_queue,
|
||||
paths_rasterization_pipeline_state,
|
||||
path_sprites_pipeline_state,
|
||||
@ -198,6 +200,12 @@ impl MetalRenderer {
|
||||
&self.sprite_atlas
|
||||
}
|
||||
|
||||
pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {
|
||||
self.presents_with_transaction = presents_with_transaction;
|
||||
self.layer
|
||||
.set_presents_with_transaction(presents_with_transaction);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
let layer = self.layer.clone();
|
||||
let viewport_size = layer.drawable_size();
|
||||
@ -347,11 +355,17 @@ impl MetalRenderer {
|
||||
});
|
||||
let block = block.copy();
|
||||
command_buffer.add_completed_handler(&block);
|
||||
command_buffer.commit();
|
||||
|
||||
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
|
||||
|
||||
command_buffer.wait_until_scheduled();
|
||||
drawable.present();
|
||||
if self.presents_with_transaction {
|
||||
command_buffer.commit();
|
||||
command_buffer.wait_until_scheduled();
|
||||
drawable.present();
|
||||
} else {
|
||||
command_buffer.present_drawable(drawable);
|
||||
command_buffer.commit();
|
||||
}
|
||||
}
|
||||
|
||||
fn rasterize_paths(
|
||||
|
@ -1,10 +1,9 @@
|
||||
use super::{events::key_to_native, BoolExt};
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
||||
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowAppearance,
|
||||
WindowOptions,
|
||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacTextSystem, MacWindow, Menu,
|
||||
MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
|
||||
PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, WindowOptions,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use block::ConcreteBlock;
|
||||
@ -146,7 +145,6 @@ pub(crate) struct MacPlatformState {
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<MacTextSystem>,
|
||||
display_linker: MacDisplayLinker,
|
||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
pasteboard: id,
|
||||
text_hash_pasteboard_type: id,
|
||||
@ -177,7 +175,6 @@ impl MacPlatform {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher),
|
||||
text_system: Arc::new(MacTextSystem::new()),
|
||||
display_linker: MacDisplayLinker::new(),
|
||||
instance_buffer_pool: Arc::default(),
|
||||
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
|
||||
text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
|
||||
@ -514,25 +511,6 @@ impl Platform for MacPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
display_id: DisplayId,
|
||||
callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
self.0
|
||||
.lock()
|
||||
.display_linker
|
||||
.set_output_callback(display_id, callback);
|
||||
}
|
||||
|
||||
fn start_display_link(&self, display_id: DisplayId) {
|
||||
self.0.lock().display_linker.start(display_id);
|
||||
}
|
||||
|
||||
fn stop_display_link(&self, display_id: DisplayId) {
|
||||
self.0.lock().display_linker.stop(display_id);
|
||||
}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
unsafe {
|
||||
let url = NSURL::alloc(nil)
|
||||
|
@ -1,10 +1,11 @@
|
||||
use super::{global_bounds_from_ns_rect, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||
use crate::{
|
||||
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
||||
Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent,
|
||||
Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
||||
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
|
||||
Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
|
||||
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||
PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind,
|
||||
WindowOptions,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
@ -16,11 +17,11 @@ use cocoa::{
|
||||
},
|
||||
base::{id, nil},
|
||||
foundation::{
|
||||
NSArray, NSAutoreleasePool, NSDefaultRunLoopMode, NSDictionary, NSFastEnumeration,
|
||||
NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||
NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
|
||||
NSSize, NSString, NSUInteger,
|
||||
},
|
||||
};
|
||||
use core_graphics::display::CGRect;
|
||||
use core_graphics::display::{CGDirectDisplayID, CGRect};
|
||||
use ctor::ctor;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use futures::channel::oneshot;
|
||||
@ -50,6 +51,7 @@ use std::{
|
||||
sync::{Arc, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const WINDOW_STATE_IVAR: &str = "windowState";
|
||||
|
||||
@ -168,7 +170,6 @@ unsafe fn build_classes() {
|
||||
sel!(displayLayer:),
|
||||
display_layer as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
decl.add_method(sel!(step:), step as extern "C" fn(&Object, Sel, id));
|
||||
|
||||
decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
|
||||
decl.add_method(
|
||||
@ -330,7 +331,7 @@ struct MacWindowState {
|
||||
executor: ForegroundExecutor,
|
||||
native_window: id,
|
||||
native_view: NonNull<Object>,
|
||||
display_link: id,
|
||||
display_link: Option<DisplayLink>,
|
||||
renderer: MetalRenderer,
|
||||
kind: WindowKind,
|
||||
request_frame_callback: Option<Box<dyn FnMut()>>,
|
||||
@ -402,6 +403,21 @@ impl MacWindowState {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_display_link(&mut self) {
|
||||
self.stop_display_link();
|
||||
let display_id = unsafe { display_id_for_screen(self.native_window.screen()) };
|
||||
if let Some(mut display_link) =
|
||||
DisplayLink::new(display_id, self.native_view.as_ptr() as *mut c_void, step).log_err()
|
||||
{
|
||||
display_link.start().log_err();
|
||||
self.display_link = Some(display_link);
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_display_link(&mut self) {
|
||||
self.display_link = None;
|
||||
}
|
||||
|
||||
fn is_fullscreen(&self) -> bool {
|
||||
unsafe {
|
||||
let style_mask = self.native_window.styleMask();
|
||||
@ -506,11 +522,8 @@ impl MacWindow {
|
||||
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
||||
for i in 0..count {
|
||||
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
||||
let screen_number = device_description.objectForKey_(screen_number_key);
|
||||
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
|
||||
if screen_number as u32 == display.id().0 {
|
||||
let display_id = display_id_for_screen(screen);
|
||||
if display_id == display.id().0 {
|
||||
target_screen = screen;
|
||||
break;
|
||||
}
|
||||
@ -539,7 +552,7 @@ impl MacWindow {
|
||||
executor,
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
display_link: nil,
|
||||
display_link: None,
|
||||
renderer: MetalRenderer::new(instance_buffer_pool),
|
||||
kind: options.kind,
|
||||
request_frame_callback: None,
|
||||
@ -695,19 +708,11 @@ impl MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn start_display_link(native_screen: id, native_view: id) -> id {
|
||||
let display_link: id =
|
||||
msg_send![native_screen, displayLinkWithTarget: native_view selector: sel!(step:)];
|
||||
let main_run_loop: id = msg_send![class!(NSRunLoop), mainRunLoop];
|
||||
let _: () = msg_send![display_link, addToRunLoop: main_run_loop forMode: NSDefaultRunLoopMode];
|
||||
display_link
|
||||
}
|
||||
|
||||
impl Drop for MacWindow {
|
||||
fn drop(&mut self) {
|
||||
let mut this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.display_link = nil;
|
||||
this.display_link.take();
|
||||
this.executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
@ -1336,17 +1341,16 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||
|
||||
extern "C" fn window_did_change_occlusion_state(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.lock();
|
||||
let lock = &mut *window_state.lock();
|
||||
unsafe {
|
||||
if lock
|
||||
.native_window
|
||||
.occlusionState()
|
||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible)
|
||||
{
|
||||
lock.display_link =
|
||||
start_display_link(lock.native_window.screen(), lock.native_view.as_ptr());
|
||||
lock.start_display_link();
|
||||
} else {
|
||||
lock.display_link = nil;
|
||||
lock.stop_display_link();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1387,14 +1391,7 @@ extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
|
||||
extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
unsafe {
|
||||
let screen = lock.native_window.screen();
|
||||
if screen == nil {
|
||||
lock.display_link = nil;
|
||||
} else {
|
||||
lock.display_link = start_display_link(screen, lock.native_view.as_ptr());
|
||||
}
|
||||
}
|
||||
lock.start_display_link();
|
||||
}
|
||||
|
||||
extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
|
||||
@ -1540,26 +1537,27 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.lock();
|
||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||
lock.renderer.set_presents_with_transaction(true);
|
||||
lock.stop_display_link();
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().request_frame_callback = Some(callback);
|
||||
|
||||
let mut lock = window_state.lock();
|
||||
lock.request_frame_callback = Some(callback);
|
||||
lock.renderer.set_presents_with_transaction(false);
|
||||
lock.start_display_link();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn step(this: &Object, _: Sel, display_link: id) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
unsafe extern "C" fn step(view: *mut c_void) {
|
||||
let view = view as id;
|
||||
let window_state = unsafe { get_window_state(&*view) };
|
||||
let mut lock = window_state.lock();
|
||||
|
||||
if lock.display_link == display_link {
|
||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().request_frame_callback = Some(callback);
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
let _: () = msg_send![display_link, invalidate];
|
||||
}
|
||||
if let Some(mut callback) = lock.request_frame_callback.take() {
|
||||
drop(lock);
|
||||
callback();
|
||||
window_state.lock().request_frame_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1882,3 +1880,11 @@ where
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
||||
let screen_number = device_description.objectForKey_(screen_number_key);
|
||||
let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
|
||||
screen_number as CGDirectDisplayID
|
||||
}
|
||||
|
@ -186,18 +186,6 @@ impl Platform for TestPlatform {
|
||||
WindowAppearance::Light
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
&self,
|
||||
_display_id: DisplayId,
|
||||
mut callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
callback()
|
||||
}
|
||||
|
||||
fn start_display_link(&self, _display_id: DisplayId) {}
|
||||
|
||||
fn stop_display_link(&self, _display_id: DisplayId) {}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
*self.opened_url.borrow_mut() = Some(url.to_string())
|
||||
}
|
||||
|
@ -12,10 +12,7 @@ use crate::{
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::FxHashSet;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
StreamExt,
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::RwLock;
|
||||
use slotmap::SlotMap;
|
||||
use smallvec::SmallVec;
|
||||
@ -23,7 +20,6 @@ use std::{
|
||||
any::{Any, TypeId},
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::{Cell, RefCell},
|
||||
collections::hash_map::Entry,
|
||||
fmt::{Debug, Display},
|
||||
future::Future,
|
||||
hash::{Hash, Hasher},
|
||||
@ -243,6 +239,8 @@ impl<M: FocusableView + EventEmitter<DismissEvent>> ManagedView for M {}
|
||||
/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal.
|
||||
pub struct DismissEvent;
|
||||
|
||||
type FrameCallback = Box<dyn FnOnce(&mut WindowContext)>;
|
||||
|
||||
// Holds the state for a specific window.
|
||||
#[doc(hidden)]
|
||||
pub struct Window {
|
||||
@ -259,6 +257,7 @@ pub struct Window {
|
||||
pub(crate) element_id_stack: GlobalElementId,
|
||||
pub(crate) rendered_frame: Frame,
|
||||
pub(crate) next_frame: Frame,
|
||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
|
||||
@ -338,14 +337,27 @@ impl Window {
|
||||
let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
|
||||
let dirty = Rc::new(Cell::new(true));
|
||||
let active = Rc::new(Cell::new(false));
|
||||
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
|
||||
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
|
||||
|
||||
platform_window.on_request_frame(Box::new({
|
||||
let mut cx = cx.to_async();
|
||||
let dirty = dirty.clone();
|
||||
let active = active.clone();
|
||||
let next_frame_callbacks = next_frame_callbacks.clone();
|
||||
let last_input_timestamp = last_input_timestamp.clone();
|
||||
move || {
|
||||
let next_frame_callbacks = next_frame_callbacks.take();
|
||||
if !next_frame_callbacks.is_empty() {
|
||||
handle
|
||||
.update(&mut cx, |_, cx| {
|
||||
for callback in next_frame_callbacks {
|
||||
callback(cx);
|
||||
}
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
if dirty.get() {
|
||||
measure("frame duration", || {
|
||||
handle
|
||||
@ -428,6 +440,7 @@ impl Window {
|
||||
element_id_stack: GlobalElementId::default(),
|
||||
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame_callbacks,
|
||||
dirty_views: FxHashSet::default(),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
focus_listeners: SubscriberSet::new(),
|
||||
@ -670,57 +683,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Schedule the given closure to be run directly after the current frame is rendered.
|
||||
pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) {
|
||||
let handle = self.window.handle;
|
||||
let display_id = self.window.display_id;
|
||||
|
||||
let mut frame_consumers = std::mem::take(&mut self.app.frame_consumers);
|
||||
if let Entry::Vacant(e) = frame_consumers.entry(display_id) {
|
||||
let (tx, mut rx) = mpsc::unbounded::<()>();
|
||||
self.platform.set_display_link_output_callback(
|
||||
display_id,
|
||||
Box::new(move || _ = tx.unbounded_send(())),
|
||||
);
|
||||
|
||||
let consumer_task = self.app.spawn(|cx| async move {
|
||||
while rx.next().await.is_some() {
|
||||
cx.update(|cx| {
|
||||
for callback in cx
|
||||
.next_frame_callbacks
|
||||
.get_mut(&display_id)
|
||||
.unwrap()
|
||||
.drain(..)
|
||||
.collect::<SmallVec<[_; 32]>>()
|
||||
{
|
||||
callback(cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Flush effects, then stop the display link if no new next_frame_callbacks have been added.
|
||||
|
||||
cx.update(|cx| {
|
||||
if cx.next_frame_callbacks.is_empty() {
|
||||
cx.platform.stop_display_link(display_id);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
e.insert(consumer_task);
|
||||
}
|
||||
debug_assert!(self.app.frame_consumers.is_empty());
|
||||
self.app.frame_consumers = frame_consumers;
|
||||
|
||||
if self.next_frame_callbacks.is_empty() {
|
||||
self.platform.start_display_link(display_id);
|
||||
}
|
||||
|
||||
self.next_frame_callbacks
|
||||
.entry(display_id)
|
||||
.or_default()
|
||||
.push(Box::new(move |cx: &mut AppContext| {
|
||||
cx.update_window(handle, |_root_view, cx| callback(cx)).ok();
|
||||
}));
|
||||
RefCell::borrow_mut(&self.window.next_frame_callbacks).push(Box::new(callback));
|
||||
}
|
||||
|
||||
/// Spawn the future returned by the given closure on the application thread pool.
|
||||
|
Loading…
Reference in New Issue
Block a user