Enable Blade on MacOS via "macos-blade" feature (#7669)
Depends on https://github.com/zed-industries/font-kit/pull/2 and https://github.com/kvark/blade/pull/77 This change enables Blade to be also used on MacOS. It will also make it easier to use it on Windows. What works: most of the things. Zed loads as fast and appears equally responsive to the current renderer. <img width="306" alt="Screenshot 2024-02-11 at 12 09 15 AM" src="https://github.com/zed-industries/zed/assets/107301/66d82f45-5ea2-4e2b-86c6-5b3ed333c827"> Things missing: - [x] video streaming. ~~Requires a bit of plumbing on both Blade and Zed sides, but all fairly straightforward.~~ - verified with a local setup - [x] resize. ~~Not sure where exactly to hook up the reaction on the window size change. Once we know where, the fix is one line.~~ - [ ] fine-tune CA Layer - this isn't a blocker for merging the PR, but it would be a blocker if we wanted to switch to the new path by default - [ ] rebase on latest, get the dependency merged (need review/merge of https://github.com/zed-industries/font-kit/pull/2!) Update: I implemented resize support as well as "surface" rendering on the Blade path (which will be useful on Linux/Windows later on). I haven't tested the latter though - not sure how to get something streaming. Would appreciate some help! I don't think this should be a blocker to this PR, anyway. The only little piece that's missing for the Blade on MacOS path to be full-featured is fine-tuning the CALayer configuration. Zed does a lot of careful logic in configuring the layer, such as switching the "present with transaction" on/off intermittently, which Blade path doesn't have yet. Release Notes: - N/A --------- Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
co-authored by
Mikayla
parent
1c361ac579
commit
9ad1862f2f
@@ -111,11 +111,11 @@ mod sys {
|
||||
pub enum CVDisplayLink {}
|
||||
|
||||
foreign_type! {
|
||||
type CType = CVDisplayLink;
|
||||
fn drop = CVDisplayLinkRelease;
|
||||
fn clone = CVDisplayLinkRetain;
|
||||
pub struct DisplayLink;
|
||||
pub struct DisplayLinkRef;
|
||||
pub unsafe type DisplayLink {
|
||||
type CType = CVDisplayLink;
|
||||
fn drop = CVDisplayLinkRelease;
|
||||
fn clone = CVDisplayLinkRetain;
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DisplayLink {
|
||||
|
||||
@@ -13,7 +13,7 @@ use core_graphics::{
|
||||
event_source::{CGEventSource, CGEventSourceStateID},
|
||||
};
|
||||
use ctor::ctor;
|
||||
use foreign_types::ForeignType;
|
||||
use metal::foreign_types::ForeignType as _;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
use super::metal_atlas::MetalAtlas;
|
||||
use crate::{
|
||||
platform::mac::ns_string, point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,
|
||||
ContentMask, DevicePixels, Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex,
|
||||
PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
|
||||
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
||||
Hsla, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
|
||||
ScaledPixels, Scene, Shadow, Size, Surface, Underline,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
base::{nil, NO, YES},
|
||||
foundation::{NSDictionary, NSUInteger},
|
||||
base::{NO, YES},
|
||||
foundation::NSUInteger,
|
||||
quartzcore::AutoresizingMask,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use core_foundation::base::TCFType;
|
||||
use foreign_types::ForeignType;
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
@@ -29,6 +30,18 @@ const SHADERS_SOURCE_FILE: &'static str =
|
||||
include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
|
||||
const INSTANCE_BUFFER_SIZE: usize = 2 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...)
|
||||
|
||||
pub type Context = Arc<Mutex<Vec<metal::Buffer>>>;
|
||||
pub type Renderer = MetalRenderer;
|
||||
|
||||
pub unsafe fn new_renderer(
|
||||
context: self::Context,
|
||||
_native_window: *mut c_void,
|
||||
_native_view: *mut c_void,
|
||||
_bounds: crate::Size<f32>,
|
||||
) -> Renderer {
|
||||
MetalRenderer::new(context)
|
||||
}
|
||||
|
||||
pub(crate) struct MetalRenderer {
|
||||
device: metal::Device,
|
||||
layer: metal::MetalLayer,
|
||||
@@ -196,25 +209,12 @@ impl MetalRenderer {
|
||||
&self.layer
|
||||
}
|
||||
|
||||
pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
|
||||
&self.sprite_atlas
|
||||
pub fn layer_ptr(&self) -> *mut CAMetalLayer {
|
||||
self.layer.as_ptr()
|
||||
}
|
||||
|
||||
/// Enables or disables the Metal HUD for debugging purposes. Note that this only works
|
||||
/// when the app is bundled and it has the `MetalHudEnabled` key set to true in Info.plist.
|
||||
pub fn set_hud_enabled(&mut self, enabled: bool) {
|
||||
unsafe {
|
||||
if enabled {
|
||||
let hud_properties = NSDictionary::dictionaryWithObject_forKey_(
|
||||
nil,
|
||||
ns_string("default"),
|
||||
ns_string("mode"),
|
||||
);
|
||||
let _: () = msg_send![&*self.layer, setDeveloperHUDProperties: hud_properties];
|
||||
} else {
|
||||
let _: () = msg_send![&*self.layer, setDeveloperHUDProperties: NSDictionary::dictionary(nil)];
|
||||
}
|
||||
}
|
||||
pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
|
||||
&self.sprite_atlas
|
||||
}
|
||||
|
||||
pub fn set_presents_with_transaction(&mut self, presents_with_transaction: bool) {
|
||||
@@ -223,6 +223,19 @@ impl MetalRenderer {
|
||||
.set_presents_with_transaction(presents_with_transaction);
|
||||
}
|
||||
|
||||
pub fn update_drawable_size(&mut self, size: Size<f64>) {
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
self.layer(),
|
||||
setDrawableSize: size
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn destroy(&mut self) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
let layer = self.layer.clone();
|
||||
let viewport_size = layer.drawable_size();
|
||||
|
||||
@@ -52,6 +52,8 @@ use std::{
|
||||
};
|
||||
use time::UtcOffset;
|
||||
|
||||
use super::renderer;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSUTF8StringEncoding: NSUInteger = 4;
|
||||
|
||||
@@ -145,7 +147,7 @@ pub(crate) struct MacPlatformState {
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
text_system: Arc<MacTextSystem>,
|
||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
renderer_context: renderer::Context,
|
||||
pasteboard: id,
|
||||
text_hash_pasteboard_type: id,
|
||||
metadata_pasteboard_type: id,
|
||||
@@ -175,7 +177,7 @@ impl MacPlatform {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher),
|
||||
text_system: Arc::new(MacTextSystem::new()),
|
||||
instance_buffer_pool: Arc::default(),
|
||||
renderer_context: renderer::Context::default(),
|
||||
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
|
||||
text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
|
||||
metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
|
||||
@@ -494,12 +496,11 @@ impl Platform for MacPlatform {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
let instance_buffer_pool = self.0.lock().instance_buffer_pool.clone();
|
||||
Box::new(MacWindow::open(
|
||||
handle,
|
||||
options,
|
||||
self.foreground_executor(),
|
||||
instance_buffer_pool,
|
||||
self.0.lock().renderer_context.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{global_bounds_from_ns_rect, ns_string, MacDisplay, MetalRenderer, NSRange};
|
||||
use super::{global_bounds_from_ns_rect, ns_string, renderer, MacDisplay, NSRange};
|
||||
use crate::{
|
||||
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
||||
Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
|
||||
@@ -23,7 +23,6 @@ use cocoa::{
|
||||
};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGRect};
|
||||
use ctor::ctor;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use futures::channel::oneshot;
|
||||
use objc::{
|
||||
class,
|
||||
@@ -322,7 +321,7 @@ struct MacWindowState {
|
||||
native_window: id,
|
||||
native_view: NonNull<Object>,
|
||||
display_link: Option<DisplayLink>,
|
||||
renderer: MetalRenderer,
|
||||
renderer: renderer::Renderer,
|
||||
kind: WindowKind,
|
||||
request_frame_callback: Option<Box<dyn FnMut()>>,
|
||||
event_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
||||
@@ -450,6 +449,13 @@ impl MacWindowState {
|
||||
get_scale_factor(self.native_window)
|
||||
}
|
||||
|
||||
fn update_drawable_size(&mut self, drawable_size: NSSize) {
|
||||
self.renderer.update_drawable_size(Size {
|
||||
width: drawable_size.width,
|
||||
height: drawable_size.height,
|
||||
})
|
||||
}
|
||||
|
||||
fn titlebar_height(&self) -> Pixels {
|
||||
unsafe {
|
||||
let frame = NSWindow::frame(self.native_window);
|
||||
@@ -478,7 +484,7 @@ impl MacWindow {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
executor: ForegroundExecutor,
|
||||
instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
|
||||
renderer_context: renderer::Context,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
@@ -541,13 +547,32 @@ impl MacWindow {
|
||||
let native_view = NSView::init(native_view);
|
||||
assert!(!native_view.is_null());
|
||||
|
||||
let window_size = {
|
||||
let bounds = match options.bounds {
|
||||
WindowBounds::Fullscreen | WindowBounds::Maximized => {
|
||||
native_window.screen().visibleFrame()
|
||||
}
|
||||
WindowBounds::Fixed(bounds) => global_bounds_to_ns_rect(bounds),
|
||||
};
|
||||
let scale = get_scale_factor(native_window);
|
||||
size(
|
||||
bounds.size.width as f32 * scale,
|
||||
bounds.size.height as f32 * scale,
|
||||
)
|
||||
};
|
||||
|
||||
let window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||
handle,
|
||||
executor,
|
||||
native_window,
|
||||
native_view: NonNull::new_unchecked(native_view),
|
||||
display_link: None,
|
||||
renderer: MetalRenderer::new(instance_buffer_pool),
|
||||
renderer: renderer::new_renderer(
|
||||
renderer_context,
|
||||
native_window as *mut _,
|
||||
native_view as *mut _,
|
||||
window_size,
|
||||
),
|
||||
kind: options.kind,
|
||||
request_frame_callback: None,
|
||||
event_callback: None,
|
||||
@@ -704,6 +729,7 @@ impl MacWindow {
|
||||
impl Drop for MacWindow {
|
||||
fn drop(&mut self) {
|
||||
let mut this = self.0.lock();
|
||||
this.renderer.destroy();
|
||||
let window = this.native_window;
|
||||
this.display_link.take();
|
||||
this.executor
|
||||
@@ -1031,7 +1057,22 @@ impl PlatformWindow for MacWindow {
|
||||
/// Enables or disables the Metal HUD for debugging purposes. Note that this only works
|
||||
/// when the app is bundled and it has the `MetalHudEnabled` key set to true in Info.plist.
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool) {
|
||||
self.0.lock().renderer.set_hud_enabled(enabled);
|
||||
let this_lock = self.0.lock();
|
||||
let layer = this_lock.renderer.layer();
|
||||
|
||||
unsafe {
|
||||
if enabled {
|
||||
let hud_properties = NSDictionary::dictionaryWithObject_forKey_(
|
||||
nil,
|
||||
ns_string("default"),
|
||||
ns_string("mode"),
|
||||
);
|
||||
let _: () = msg_send![layer, setDeveloperHUDProperties: hud_properties];
|
||||
} else {
|
||||
let _: () =
|
||||
msg_send![layer, setDeveloperHUDProperties: NSDictionary::dictionary(nil)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1487,31 +1528,28 @@ extern "C" fn close_window(this: &Object, _: Sel) {
|
||||
extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let window_state = window_state.as_ref().lock();
|
||||
window_state.renderer.layer().as_ptr() as id
|
||||
window_state.renderer.layer_ptr() as id
|
||||
}
|
||||
|
||||
extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
|
||||
let scale_factor = lock.scale_factor() as f64;
|
||||
let size = lock.content_size();
|
||||
let drawable_size: NSSize = NSSize {
|
||||
width: f64::from(size.width) * scale_factor,
|
||||
height: f64::from(size.height) * scale_factor,
|
||||
};
|
||||
unsafe {
|
||||
let scale_factor = lock.scale_factor() as f64;
|
||||
let size = lock.content_size();
|
||||
let drawable_size: NSSize = NSSize {
|
||||
width: f64::from(size.width) * scale_factor,
|
||||
height: f64::from(size.height) * scale_factor,
|
||||
};
|
||||
|
||||
let _: () = msg_send![
|
||||
lock.renderer.layer(),
|
||||
setContentsScale: scale_factor
|
||||
];
|
||||
let _: () = msg_send![
|
||||
lock.renderer.layer(),
|
||||
setDrawableSize: drawable_size
|
||||
];
|
||||
}
|
||||
|
||||
lock.update_drawable_size(drawable_size);
|
||||
|
||||
if let Some(mut callback) = lock.resize_callback.take() {
|
||||
let content_size = lock.content_size();
|
||||
let scale_factor = lock.scale_factor();
|
||||
@@ -1523,7 +1561,7 @@ extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
|
||||
|
||||
extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
|
||||
let window_state = unsafe { get_window_state(this) };
|
||||
let lock = window_state.as_ref().lock();
|
||||
let mut lock = window_state.as_ref().lock();
|
||||
|
||||
if lock.content_size() == size.into() {
|
||||
return;
|
||||
@@ -1539,12 +1577,7 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
|
||||
height: size.height * scale_factor,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
lock.renderer.layer(),
|
||||
setDrawableSize: drawable_size
|
||||
];
|
||||
}
|
||||
lock.update_drawable_size(drawable_size);
|
||||
|
||||
drop(lock);
|
||||
let mut lock = window_state.lock();
|
||||
@@ -1561,6 +1594,7 @@ 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() {
|
||||
#[cfg(not(feature = "macos-blade"))]
|
||||
lock.renderer.set_presents_with_transaction(true);
|
||||
lock.stop_display_link();
|
||||
drop(lock);
|
||||
@@ -1568,6 +1602,7 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||
|
||||
let mut lock = window_state.lock();
|
||||
lock.request_frame_callback = Some(callback);
|
||||
#[cfg(not(feature = "macos-blade"))]
|
||||
lock.renderer.set_presents_with_transaction(false);
|
||||
lock.start_display_link();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user