From 292b41ad5798305b97bd9e999b1551c82f8cdb8c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 20 Mar 2021 09:38:36 -0600 Subject: [PATCH] Start on metal rendering infrastructure --- Cargo.lock | 43 +---- gpui/Cargo.toml | 4 +- gpui/build.rs | 67 +++++++- gpui/src/platform/mac/mod.rs | 1 + gpui/src/platform/mac/renderer.rs | 63 ++++++++ gpui/src/platform/mac/shaders/shaders.h | 17 ++ gpui/src/platform/mac/shaders/shaders.metal | 30 ++++ gpui/src/platform/mac/window.rs | 171 +++++++++++++++----- 8 files changed, 320 insertions(+), 76 deletions(-) create mode 100644 gpui/src/platform/mac/renderer.rs create mode 100644 gpui/src/platform/mac/shaders/shaders.h create mode 100644 gpui/src/platform/mac/shaders/shaders.metal diff --git a/Cargo.lock b/Cargo.lock index f60dd21a41..d05d3535e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,7 +336,7 @@ dependencies = [ "cocoa-foundation", "core-foundation", "core-graphics", - "foreign-types 0.3.2", + "foreign-types", "libc", "objc", ] @@ -351,7 +351,7 @@ dependencies = [ "block", "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "libc", "objc", ] @@ -396,7 +396,7 @@ dependencies = [ "bitflags", "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "libc", ] @@ -408,7 +408,7 @@ checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ "bitflags", "core-foundation", - "foreign-types 0.3.2", + "foreign-types", "libc", ] @@ -420,7 +420,7 @@ checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" dependencies = [ "core-foundation", "core-graphics", - "foreign-types 0.3.2", + "foreign-types", "libc", ] @@ -616,28 +616,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.0", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63f713f8b2aa9e24fec85b0e290c56caee12e3b6ae0aeeda238a75b28251afd6" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "foreign-types-shared", ] [[package]] @@ -646,12 +625,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "foreign-types-shared" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" - [[package]] name = "freetype" version = "0.7.0" @@ -773,7 +746,7 @@ dependencies = [ "core-text", "ctor", "font-kit", - "foreign-types 0.5.0", + "foreign-types", "log", "metal", "num_cpus", @@ -924,7 +897,7 @@ dependencies = [ "bitflags", "block", "cocoa-foundation", - "foreign-types 0.3.2", + "foreign-types", "log", "objc", ] diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index edeea5be5a..aa8b5650b8 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -29,7 +29,7 @@ core-foundation = "0.9" core-graphics = "0.22.2" core-text = "19.2" font-kit = {git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1"} -foreign-types = "0.5" +foreign-types = "0.3" log = "0.4" -metal = "0.21" +metal = "0.21.0" objc = "0.2" diff --git a/gpui/build.rs b/gpui/build.rs index 935571f40c..2fa8822538 100644 --- a/gpui/build.rs +++ b/gpui/build.rs @@ -1,8 +1,14 @@ -use std::{env, path::PathBuf}; +use std::{ + env, + path::PathBuf, + process::{self, Command}, +}; fn main() { generate_dispatch_bindings(); compile_context_predicate_parser(); + compile_metal_shaders(); + generate_shader_bindings(); } fn generate_dispatch_bindings() { @@ -20,7 +26,7 @@ fn generate_dispatch_bindings() { let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings .write_to_file(out_path.join("dispatch_sys.rs")) - .expect("couldn't write bindings"); + .expect("couldn't write dispatch bindings"); } fn compile_context_predicate_parser() { @@ -33,3 +39,60 @@ fn compile_context_predicate_parser() { .file(parser_c) .compile("tree_sitter_context_predicate"); } + +const SHADER_HEADER_PATH: &'static str = "./src/platform/mac/shaders/shaders.h"; + +fn compile_metal_shaders() { + let shader_path = "./src/platform/mac/shaders/shaders.metal"; + let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air"); + let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib"); + + println!("cargo:rerun-if-changed={}", SHADER_HEADER_PATH); + println!("cargo:rerun-if-changed={}", shader_path); + + let output = Command::new("xcrun") + .args(&["-sdk", "macosx", "metal", "-c", shader_path, "-o"]) + .arg(&air_output_path) + .output() + .unwrap(); + + if !output.status.success() { + eprintln!( + "metal shader compilation failed:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + process::exit(1); + } + + let output = Command::new("xcrun") + .args(&["-sdk", "macosx", "metallib"]) + .arg(air_output_path) + .arg("-o") + .arg(metallib_output_path) + .output() + .unwrap(); + + if !output.status.success() { + eprintln!( + "metallib compilation failed:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + process::exit(1); + } +} + +fn generate_shader_bindings() { + let bindings = bindgen::Builder::default() + .header(SHADER_HEADER_PATH) + .whitelist_type("GPUIQuadInputIndex") + .whitelist_type("GPUIQuad") + .whitelist_type("GPUIQuadUniforms") + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .generate() + .expect("unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("shaders.rs")) + .expect("couldn't write shader bindings"); +} diff --git a/gpui/src/platform/mac/mod.rs b/gpui/src/platform/mac/mod.rs index 47c3a13ae6..148355d34c 100644 --- a/gpui/src/platform/mac/mod.rs +++ b/gpui/src/platform/mac/mod.rs @@ -2,6 +2,7 @@ mod app; mod dispatcher; mod event; mod geometry; +mod renderer; mod runner; mod window; diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs new file mode 100644 index 0000000000..72948f3ee0 --- /dev/null +++ b/gpui/src/platform/mac/renderer.rs @@ -0,0 +1,63 @@ +use anyhow::{anyhow, Result}; + +use crate::Scene; + +use super::window::RenderContext; + +const SHADERS_METALLIB: &'static [u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); + +pub struct Renderer { + quad_pipeline_state: metal::RenderPipelineState, +} + +impl Renderer { + pub fn new(device: &metal::DeviceRef, pixel_format: metal::MTLPixelFormat) -> Result { + let library = device + .new_library_with_data(SHADERS_METALLIB) + .map_err(|message| anyhow!("error building metal library: {}", message))?; + + Ok(Self { + quad_pipeline_state: build_pipeline_state( + device, + &library, + "quad", + "quad_vertex", + "quad_fragment", + pixel_format, + )?, + }) + } + + pub fn render(&mut self, scene: &Scene, ctx: RenderContext) {} +} + +fn build_pipeline_state( + device: &metal::DeviceRef, + library: &metal::LibraryRef, + label: &str, + vertex_fn_name: &str, + fragment_fn_name: &str, + pixel_format: metal::MTLPixelFormat, +) -> Result { + let vertex_fn = library + .get_function(vertex_fn_name, None) + .map_err(|message| anyhow!("error locating vertex function: {}", message))?; + let fragment_fn = library + .get_function(fragment_fn_name, None) + .map_err(|message| anyhow!("error locating fragment function: {}", message))?; + + let mut descriptor = metal::RenderPipelineDescriptor::new(); + descriptor.set_label(label); + descriptor.set_vertex_function(Some(vertex_fn.as_ref())); + descriptor.set_fragment_function(Some(fragment_fn.as_ref())); + descriptor + .color_attachments() + .object_at(0) + .unwrap() + .set_pixel_format(pixel_format); + + device + .new_render_pipeline_state(&descriptor) + .map_err(|message| anyhow!("could not create render pipeline state: {}", message)) +} diff --git a/gpui/src/platform/mac/shaders/shaders.h b/gpui/src/platform/mac/shaders/shaders.h new file mode 100644 index 0000000000..374ecebc5a --- /dev/null +++ b/gpui/src/platform/mac/shaders/shaders.h @@ -0,0 +1,17 @@ +#include + +typedef enum { + GPUIQuadInputIndexVertices = 0, + GPUIQuadInputIndexQuads = 1, + GPUIQuadInputIndexUniforms = 2, +} GPUIQuadInputIndex; + +typedef struct { + vector_float2 origin; + vector_float2 size; + vector_float4 background_color; +} GPUIQuad; + +typedef struct { + vector_float2 viewport_size; +} GPUIQuadUniforms; diff --git a/gpui/src/platform/mac/shaders/shaders.metal b/gpui/src/platform/mac/shaders/shaders.metal new file mode 100644 index 0000000000..e508c7ac66 --- /dev/null +++ b/gpui/src/platform/mac/shaders/shaders.metal @@ -0,0 +1,30 @@ +#include +#include "shaders.h" + +using namespace metal; + +struct QuadFragmentInput { + float4 position [[position]]; + GPUIQuad quad; +}; + +vertex QuadFragmentInput quad_vertex( + uint unit_vertex_id [[vertex_id]], + uint quad_id [[instance_id]], + constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]], + constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]], + constant GPUIQuadUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]] +) { + float2 unit_vertex = unit_vertices[unit_vertex_id]; + GPUIQuad quad = quads[quad_id]; + float4 position = float4((unit_vertex * quad.size + quad.origin) / (uniforms->viewport_size / 2.0), 0.0, 1.0); + + return QuadFragmentInput { + position, + quad, + }; +} + +fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]]) { + return input.quad.background_color; +} \ No newline at end of file diff --git a/gpui/src/platform/mac/window.rs b/gpui/src/platform/mac/window.rs index fdedf545cf..b5af664408 100644 --- a/gpui/src/platform/mac/window.rs +++ b/gpui/src/platform/mac/window.rs @@ -12,13 +12,16 @@ use cocoa::{ }, base::{id, nil}, foundation::{NSAutoreleasePool, NSSize, NSString}, + quartzcore::AutoresizingMask, }; use ctor::ctor; +use foreign_types::ForeignType as _; +use metal::{MTLClearColor, MTLLoadAction, MTLStoreAction}; use objc::{ class, declare::ClassDecl, msg_send, - runtime::{Class, Object, Sel, BOOL, NO, YES}, + runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES}, sel, sel_impl, }; use pathfinder_geometry::vector::vec2f; @@ -31,13 +34,12 @@ use std::{ time::{Duration, Instant}, }; -use super::geometry::RectFExt; +use super::{geometry::RectFExt, renderer::Renderer}; const WINDOW_STATE_IVAR: &'static str = "windowState"; static mut WINDOW_CLASS: *const Class = ptr::null(); static mut VIEW_CLASS: *const Class = ptr::null(); -static mut DELEGATE_CLASS: *const Class = ptr::null(); #[ctor] unsafe fn build_classes() { @@ -63,7 +65,9 @@ unsafe fn build_classes() { VIEW_CLASS = { let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap(); decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR); + decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel)); + decl.add_method( sel!(keyDown:), handle_view_event as extern "C" fn(&Object, Sel, id), @@ -84,20 +88,25 @@ unsafe fn build_classes() { sel!(scrollWheel:), handle_view_event as extern "C" fn(&Object, Sel, id), ); - decl.register() - }; - DELEGATE_CLASS = { - let mut decl = ClassDecl::new("GPUIWindowDelegate", class!(NSObject)).unwrap(); + decl.add_protocol(Protocol::get("CALayerDelegate").unwrap()); decl.add_method( - sel!(dealloc), - dealloc_delegate as extern "C" fn(&Object, Sel), + sel!(makeBackingLayer), + make_backing_layer as extern "C" fn(&Object, Sel) -> id, ); - decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR); decl.add_method( - sel!(windowDidResize:), - window_did_resize as extern "C" fn(&Object, Sel, id), + sel!(viewDidChangeBackingProperties), + view_did_change_backing_properties as extern "C" fn(&Object, Sel), ); + decl.add_method( + sel!(setFrameSize:), + set_frame_size as extern "C" fn(&Object, Sel, NSSize), + ); + decl.add_method( + sel!(displayLayer:), + display_layer as extern "C" fn(&Object, Sel, id), + ); + decl.register() }; } @@ -110,6 +119,17 @@ struct WindowState { resize_callback: RefCell>>, synthetic_drag_counter: Cell, executor: Rc, + scene_to_render: RefCell>, + renderer: RefCell, + command_queue: metal::CommandQueue, + device: metal::Device, + layer: id, +} + +pub struct RenderContext<'a> { + pub drawable_size: Vector2F, + pub device: &'a metal::Device, + pub command_encoder: &'a metal::RenderCommandEncoderRef, } impl Window { @@ -117,6 +137,8 @@ impl Window { options: platform::WindowOptions, executor: Rc, ) -> Result { + const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm; + unsafe { let pool = NSAutoreleasePool::new(nil); @@ -138,12 +160,20 @@ impl Window { return Err(anyhow!("window returned nil from initializer")); } - let delegate: id = msg_send![DELEGATE_CLASS, alloc]; - let delegate = delegate.init(); - if native_window == nil { - return Err(anyhow!("delegate returned nil from initializer")); - } - native_window.setDelegate_(delegate); + let device = metal::Device::system_default() + .ok_or_else(|| anyhow!("could not find default metal device"))?; + + let layer: id = msg_send![class!(CAMetalLayer), layer]; + let _: () = msg_send![layer, setDevice: device.as_ptr()]; + let _: () = msg_send![layer, setPixelFormat: PIXEL_FORMAT]; + let _: () = msg_send![layer, setAllowsNextDrawableTimeout: NO]; + let _: () = msg_send![layer, setNeedsDisplayOnBoundsChange: YES]; + let _: () = msg_send![layer, setPresentsWithTransaction: YES]; + let _: () = msg_send![ + layer, + setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE + | AutoresizingMask::HEIGHT_SIZABLE + ]; let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view = NSView::init(native_view); @@ -157,6 +187,11 @@ impl Window { resize_callback: RefCell::new(None), synthetic_drag_counter: Cell::new(0), executor, + scene_to_render: Default::default(), + renderer: RefCell::new(Renderer::new(&device, PIXEL_FORMAT)?), + command_queue: device.new_command_queue(), + device, + layer, })); (*native_window).set_ivar( @@ -167,10 +202,6 @@ impl Window { WINDOW_STATE_IVAR, Rc::into_raw(window.0.clone()) as *const c_void, ); - (*delegate).set_ivar( - WINDOW_STATE_IVAR, - Rc::into_raw(window.0.clone()) as *const c_void, - ); if let Some(title) = options.title.as_ref() { native_window.setTitle_(NSString::alloc(nil).init_str(title)); @@ -237,7 +268,10 @@ impl platform::Window for Window { } fn render_scene(&self, scene: Scene) { - log::info!("render scene"); + *self.0.scene_to_render.borrow_mut() = Some(scene); + unsafe { + let _: () = msg_send![self.0.native_window.contentView(), setNeedsDisplay: YES]; + } } } @@ -293,14 +327,6 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) { } } -extern "C" fn dealloc_delegate(this: &Object, _: Sel) { - unsafe { - let raw: *mut c_void = *this.get_ivar(WINDOW_STATE_IVAR); - Rc::from_raw(raw as *mut WindowState); - let () = msg_send![super(this, class!(NSObject)), dealloc]; - } -} - extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let window = unsafe { window_state(this) }; @@ -329,14 +355,85 @@ extern "C" fn send_event(this: &Object, _: Sel, native_event: id) { } } -extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { +extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id { let window = unsafe { window_state(this) }; - let size = window.size(); - let scale_factor = window.scale_factor(); - if let Some(callback) = window.resize_callback.borrow_mut().as_mut() { - callback(size, scale_factor); + window.layer +} + +extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) { + let window; + unsafe { + window = window_state(this); + let _: () = msg_send![window.layer, setContentsScale: window.scale_factor() as f64]; + } + + if let Some(callback) = window.resize_callback.borrow_mut().as_mut() { + let size = window.size(); + let scale_factor = window.scale_factor(); + callback(size, scale_factor); + }; +} + +extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) { + let window; + unsafe { + window = window_state(this); + if window.size() == vec2f(size.width as f32, size.height as f32) { + return; + } + + let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size]; + + let scale_factor = window.scale_factor() as f64; + let drawable_size: NSSize = NSSize { + width: size.width * scale_factor, + height: size.height * scale_factor, + }; + let _: () = msg_send![window.layer, setDrawableSize: drawable_size]; + } + + if let Some(callback) = window.resize_callback.borrow_mut().as_mut() { + let size = window.size(); + let scale_factor = window.scale_factor(); + callback(size, scale_factor); + }; +} + +extern "C" fn display_layer(this: &Object, _: Sel, _: id) { + unsafe { + let window = window_state(this); + + if let Some(scene) = window.scene_to_render.borrow_mut().take() { + let drawable: &metal::MetalDrawableRef = msg_send![window.layer, nextDrawable]; + + let render_pass_descriptor = metal::RenderPassDescriptor::new(); + let color_attachment = render_pass_descriptor + .color_attachments() + .object_at(0) + .unwrap(); + color_attachment.set_texture(Some(drawable.texture())); + color_attachment.set_load_action(MTLLoadAction::Clear); + color_attachment.set_store_action(MTLStoreAction::Store); + color_attachment.set_clear_color(MTLClearColor::new(0., 0., 0., 1.)); + + let command_buffer = window.command_queue.new_command_buffer(); + let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); + + window.renderer.borrow_mut().render( + &scene, + RenderContext { + drawable_size: window.size() * window.scale_factor(), + device: &window.device, + command_encoder, + }, + ); + + command_encoder.end_encoding(); + command_buffer.commit(); + command_buffer.wait_until_completed(); + drawable.present(); + }; } - drop(window); } fn schedule_synthetic_drag(window_state: &Rc, position: Vector2F) {