Start on metal rendering infrastructure

This commit is contained in:
Nathan Sobo 2021-03-20 09:38:36 -06:00
parent d14c943150
commit 292b41ad57
8 changed files with 320 additions and 76 deletions

43
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

View File

@ -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");
}

View File

@ -2,6 +2,7 @@ mod app;
mod dispatcher;
mod event;
mod geometry;
mod renderer;
mod runner;
mod window;

View File

@ -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<Self> {
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<metal::RenderPipelineState> {
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))
}

View File

@ -0,0 +1,17 @@
#include <simd/simd.h>
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;

View File

@ -0,0 +1,30 @@
#include <metal_stdlib>
#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;
}

View File

@ -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<Option<Box<dyn FnMut(Vector2F, f32)>>>,
synthetic_drag_counter: Cell<usize>,
executor: Rc<executor::Foreground>,
scene_to_render: RefCell<Option<Scene>>,
renderer: RefCell<Renderer>,
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<executor::Foreground>,
) -> Result<Self> {
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<WindowState>, position: Vector2F) {