diff --git a/Cargo.lock b/Cargo.lock index f812abf..dba68ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1793,9 +1793,9 @@ dependencies = [ [[package]] name = "librashader-spirv-cross" -version = "0.24.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1842c733107d5f223c30a9d9c532d8f4bfd403d51e9ce22c53d6248a0ffa8318" +checksum = "c3576f2a17152fc9b7aa4d7ee9ad8a4c4f4d2117c301d9aea73d8635aa230214" dependencies = [ "build-target", "cc", diff --git a/librashader-capi/Cargo.toml b/librashader-capi/Cargo.toml index 0bcd3bc..a2c99e2 100644 --- a/librashader-capi/Cargo.toml +++ b/librashader-capi/Cargo.toml @@ -29,7 +29,7 @@ paste = "1.0.9" gl = { version = "0.14.0", optional = true } rustc-hash = "1.1.0" ash = { version = "0.37", optional = true } -spirv_cross = { package = "librashader-spirv-cross", version = "0.24" } +spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" } [target.'cfg(windows)'.dependencies.windows] version = "0.48.0" diff --git a/librashader-reflect/Cargo.toml b/librashader-reflect/Cargo.toml index 9159976..765c64e 100644 --- a/librashader-reflect/Cargo.toml +++ b/librashader-reflect/Cargo.toml @@ -23,7 +23,7 @@ librashader-common = { path = "../librashader-common", version = "0.2.0-beta.9" librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.9" } librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.9" } -spirv_cross = { package = "librashader-spirv-cross", version = "0.24", optional = true } +spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1", optional = true } naga = { version = "0.19.0", optional = true } rspirv = { version = "0.12.0", optional = true } diff --git a/librashader-reflect/src/reflect/naga/msl.rs b/librashader-reflect/src/reflect/naga/msl.rs index 2df010d..81a62fb 100644 --- a/librashader-reflect/src/reflect/naga/msl.rs +++ b/librashader-reflect/src/reflect/naga/msl.rs @@ -150,7 +150,7 @@ impl CompileShader for NagaReflect { translation_info: vertex.1, module: self.vertex, }, - next_free_binding: vertex_binding + next_free_binding: vertex_binding, }, }) } diff --git a/librashader-runtime-gl/Cargo.toml b/librashader-runtime-gl/Cargo.toml index 0ac851b..d6615f6 100644 --- a/librashader-runtime-gl/Cargo.toml +++ b/librashader-runtime-gl/Cargo.toml @@ -18,7 +18,7 @@ librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0- librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.9" } librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.9" } librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.9" } -spirv_cross = { package = "librashader-spirv-cross", version = "0.24" } +spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" } rustc-hash = "1.1.0" gl = "0.14.0" diff --git a/librashader-runtime-metal/Cargo.toml b/librashader-runtime-metal/Cargo.toml index f03dd7c..a86e2ac 100644 --- a/librashader-runtime-metal/Cargo.toml +++ b/librashader-runtime-metal/Cargo.toml @@ -30,6 +30,11 @@ rayon = "1.8.1" [dev-dependencies] +[[test]] +name = "triangle" +path = "tests/hello_triangle/main.rs" +harness = false + [package.metadata.docs.rs] features = ["librashader-cache/docsrs"] diff --git a/librashader-runtime-metal/src/draw_quad.rs b/librashader-runtime-metal/src/draw_quad.rs index 6f36cce..66c8be2 100644 --- a/librashader-runtime-metal/src/draw_quad.rs +++ b/librashader-runtime-metal/src/draw_quad.rs @@ -93,7 +93,12 @@ impl DrawQuad { }; unsafe { - cmd.setVertexBuffer_offset_atIndex(Some(self.buffer.as_ref()), 0, VERTEX_BUFFER_INDEX); + cmd.setVertexBuffer_offset_attributeStride_atIndex( + Some(self.buffer.as_ref()), + 0, + 4 * std::mem::size_of::(), + VERTEX_BUFFER_INDEX, + ); cmd.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangleStrip, offset, 4); } } diff --git a/librashader-runtime-metal/src/filter_chain.rs b/librashader-runtime-metal/src/filter_chain.rs index 250fa9f..08a1b8b 100644 --- a/librashader-runtime-metal/src/filter_chain.rs +++ b/librashader-runtime-metal/src/filter_chain.rs @@ -35,6 +35,7 @@ use objc2::runtime::ProtocolObject; use rayon::prelude::*; use rustc_hash::FxHashMap; use std::collections::VecDeque; +use std::fmt::{Debug, Formatter}; use std::path::Path; type ShaderPassMeta = @@ -60,6 +61,12 @@ pub struct FilterChainMetal { disable_mipmaps: bool, } +impl Debug for FilterChainMetal { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("FilterChainMetal")) + } +} + pub struct FilterMutable { pub passes_enabled: usize, pub(crate) parameters: FxHashMap, diff --git a/librashader-runtime-metal/src/graphics_pipeline.rs b/librashader-runtime-metal/src/graphics_pipeline.rs index d0aa289..cff8044 100644 --- a/librashader-runtime-metal/src/graphics_pipeline.rs +++ b/librashader-runtime-metal/src/graphics_pipeline.rs @@ -81,25 +81,25 @@ impl PipelineLayoutObjects { let binding = MTLVertexBufferLayoutDescriptor::new(); - let vertex_0 = MTLVertexAttributeDescriptor::new(); - let vertex_1 = MTLVertexAttributeDescriptor::new(); + let position = MTLVertexAttributeDescriptor::new(); + let texcoord = MTLVertexAttributeDescriptor::new(); // hopefully metal fills in vertices otherwise we'll need to use the vec4 stuff. - vertex_0.setFormat(MTLVertexFormatFloat2); - vertex_0.setBufferIndex(VERTEX_BUFFER_INDEX); - vertex_0.setOffset(0); + position.setFormat(MTLVertexFormatFloat2); + position.setBufferIndex(VERTEX_BUFFER_INDEX); + position.setOffset(0); - vertex_1.setFormat(MTLVertexFormatFloat2); - vertex_1.setBufferIndex(VERTEX_BUFFER_INDEX); - vertex_1.setOffset(2 * std::mem::size_of::()); + texcoord.setFormat(MTLVertexFormatFloat2); + texcoord.setBufferIndex(VERTEX_BUFFER_INDEX); + texcoord.setOffset(2 * std::mem::size_of::()); - attributes.setObject_atIndexedSubscript(Some(&vertex_0), 0); + attributes.setObject_atIndexedSubscript(Some(&position), 0); - attributes.setObject_atIndexedSubscript(Some(&vertex_1), 1); + attributes.setObject_atIndexedSubscript(Some(&texcoord), 1); binding.setStepFunction(MTLVertexStepFunctionPerVertex); binding.setStride(4 * std::mem::size_of::()); - layouts.setObject_atIndexedSubscript(Some(&binding), 0); + layouts.setObject_atIndexedSubscript(Some(&binding), VERTEX_BUFFER_INDEX); descriptor } diff --git a/librashader-runtime-metal/tests/hello_triangle/main.rs b/librashader-runtime-metal/tests/hello_triangle/main.rs new file mode 100644 index 0000000..5ef17d1 --- /dev/null +++ b/librashader-runtime-metal/tests/hello_triangle/main.rs @@ -0,0 +1,398 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use core::{cell::OnceCell, ptr::NonNull}; + +use icrate::Metal::MTLClearColor; +use icrate::{ + AppKit::{ + NSApplication, NSApplicationActivationPolicyRegular, NSApplicationDelegate, + NSBackingStoreBuffered, NSWindow, NSWindowStyleMaskClosable, NSWindowStyleMaskResizable, + NSWindowStyleMaskTitled, + }, + Foundation::{ + ns_string, MainThreadMarker, NSDate, NSNotification, NSObject, NSObjectProtocol, NSPoint, + NSRect, NSSize, + }, + Metal::{ + MTLCommandBuffer, MTLCommandEncoder, MTLCommandQueue, MTLCreateSystemDefaultDevice, + MTLDevice, MTLDrawable, MTLLibrary, MTLPrimitiveTypeTriangle, MTLRenderCommandEncoder, + MTLRenderPipelineDescriptor, MTLRenderPipelineState, + }, + MetalKit::{MTKView, MTKViewDelegate}, +}; +use librashader_presets::ShaderPreset; +use librashader_runtime_metal::FilterChainMetal; +use objc2::{ + declare_class, msg_send_id, mutability::MainThreadOnly, rc::Id, runtime::ProtocolObject, + ClassType, DeclaredClass, +}; + +#[rustfmt::skip] +const SHADERS: &str = r#" + #include + + struct SceneProperties { + float time; + }; + + struct VertexInput { + metal::packed_float3 position; + metal::packed_float3 color; + }; + + struct VertexOutput { + metal::float4 position [[position]]; + metal::float4 color; + }; + + vertex VertexOutput vertex_main( + device const SceneProperties& properties [[buffer(0)]], + device const VertexInput* vertices [[buffer(1)]], + uint vertex_idx [[vertex_id]] + ) { + VertexOutput out; + VertexInput in = vertices[vertex_idx]; + out.position = + metal::float4( + metal::float2x2( + metal::cos(properties.time), -metal::sin(properties.time), + metal::sin(properties.time), metal::cos(properties.time) + ) * in.position.xy, + in.position.z, + 1); + out.color = metal::float4(in.color, 1); + return out; + } + + fragment metal::float4 fragment_main(VertexOutput in [[stage_in]]) { + return in.color; + } +"#; + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct SceneProperties { + pub time: f32, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct VertexInput { + pub position: Position, + pub color: Color, +} + +#[derive(Copy, Clone)] +// NOTE: this has the same ABI as `MTLPackedFloat3` +#[repr(C)] +pub struct Position { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Copy, Clone)] +// NOTE: this has the same ABI as `MTLPackedFloat3` +#[repr(C)] +pub struct Color { + pub r: f32, + pub g: f32, + pub b: f32, +} + +macro_rules! idcell { + ($name:ident => $this:expr) => { + $this.ivars().$name.set($name).expect(&format!( + "ivar should not already be initialized: `{}`", + stringify!($name) + )); + }; + ($name:ident <= $this:expr) => { + #[rustfmt::skip] + let Some($name) = $this.ivars().$name.get() else { + unreachable!( + "ivar should be initialized: `{}`", + stringify!($name) + ) + }; + }; +} + +// declare the desired instance variables +struct Ivars { + start_date: Id, + command_queue: OnceCell>>, + pipeline_state: OnceCell>>, + filter_chain: OnceCell, + window: OnceCell>, +} + +// declare the Objective-C class machinery +declare_class!( + struct Delegate; + + // SAFETY: + // - The superclass NSObject does not have any subclassing requirements. + // - Main thread only mutability is correct, since this is an application delegate. + // - `Delegate` does not implement `Drop`. + unsafe impl ClassType for Delegate { + type Super = NSObject; + type Mutability = MainThreadOnly; + const NAME: &'static str = "Delegate"; + } + + impl DeclaredClass for Delegate { + type Ivars = Ivars; + } + + unsafe impl NSObjectProtocol for Delegate {} + + // define the delegate methods for the `NSApplicationDelegate` protocol + unsafe impl NSApplicationDelegate for Delegate { + #[method(applicationDidFinishLaunching:)] + #[allow(non_snake_case)] + unsafe fn applicationDidFinishLaunching(&self, _notification: &NSNotification) { + let mtm = MainThreadMarker::from(self); + // create the app window + let window = { + let content_rect = NSRect::new(NSPoint::new(0., 0.), NSSize::new(768., 768.)); + let style = NSWindowStyleMaskClosable + | NSWindowStyleMaskResizable + | NSWindowStyleMaskTitled; + let backing_store_type = NSBackingStoreBuffered; + let flag = false; + unsafe { + NSWindow::initWithContentRect_styleMask_backing_defer( + mtm.alloc(), + content_rect, + style, + backing_store_type, + flag, + ) + } + }; + + // get the default device + let device = { + let ptr = unsafe { MTLCreateSystemDefaultDevice() }; + unsafe { Id::retain(ptr) }.expect("Failed to get default system device.") + }; + + // create the command queue + let command_queue = device + .newCommandQueue() + .expect("Failed to create a command queue."); + + // create the metal view + let mtk_view = { + let frame_rect = window.frame(); + unsafe { MTKView::initWithFrame_device(mtm.alloc(), frame_rect, Some(&device)) } + }; + + // create the pipeline descriptor + let pipeline_descriptor = MTLRenderPipelineDescriptor::new(); + + unsafe { + pipeline_descriptor + .colorAttachments() + .objectAtIndexedSubscript(0) + .setPixelFormat(mtk_view.colorPixelFormat()); + } + + // compile the shaders + let library = device + .newLibraryWithSource_options_error(ns_string!(SHADERS), None) + .expect("Failed to create a library."); + + // configure the vertex shader + let vertex_function = library.newFunctionWithName(ns_string!("vertex_main")); + pipeline_descriptor.setVertexFunction(vertex_function.as_deref()); + + // configure the fragment shader + let fragment_function = library.newFunctionWithName(ns_string!("fragment_main")); + pipeline_descriptor.setFragmentFunction(fragment_function.as_deref()); + + // create the pipeline state + let pipeline_state = device + .newRenderPipelineStateWithDescriptor_error(&pipeline_descriptor) + .expect("Failed to create a pipeline state."); + + let preset = + ShaderPreset::try_parse("../test/shaders_slang/crt/crt-royale.slangp").unwrap(); + + let filter_chain = FilterChainMetal::load_from_preset( + preset, + &command_queue, + None, + ) + .unwrap(); + + // configure the metal view delegate + unsafe { + let object = ProtocolObject::from_ref(self); + mtk_view.setDelegate(Some(object)); + } + + // configure the window + window.setContentView(Some(&mtk_view)); + window.center(); + window.setTitle(ns_string!("metal example")); + window.makeKeyAndOrderFront(None); + + // initialize the delegate state + idcell!(command_queue => self); + idcell!(pipeline_state => self); + idcell!(filter_chain => self); + idcell!(window => self); + } + } + + // define the delegate methods for the `MTKViewDelegate` protocol + unsafe impl MTKViewDelegate for Delegate { + #[method(drawInMTKView:)] + #[allow(non_snake_case)] + unsafe fn drawInMTKView(&self, mtk_view: &MTKView) { + idcell!(command_queue <= self); + idcell!(pipeline_state <= self); + + unsafe { + mtk_view.setClearColor(MTLClearColor { + red: 0.3, + blue: 0.5, + green: 0.3, + alpha: 0.0, + }); + } + + // FIXME: icrate `MTKView` doesn't have a generated binding for `currentDrawable` yet + // (because it needs a definition of `CAMetalDrawable`, which we don't support yet) so + // we have to use a raw `msg_send_id` call here instead. + let current_drawable: Option>> = + msg_send_id![mtk_view, currentDrawable]; + + // prepare for drawing + let Some(current_drawable) = current_drawable else { + return; + }; + let Some(command_buffer) = command_queue.commandBuffer() else { + return; + }; + let Some(pass_descriptor) = (unsafe { mtk_view.currentRenderPassDescriptor() }) else { + return; + }; + let Some(encoder) = command_buffer.renderCommandEncoderWithDescriptor(&pass_descriptor) + else { + return; + }; + + // compute the scene properties + let scene_properties_data = &SceneProperties { + time: unsafe { self.ivars().start_date.timeIntervalSinceNow() } as f32, + }; + // write the scene properties to the vertex shader argument buffer at index 0 + let scene_properties_bytes = NonNull::from(scene_properties_data); + unsafe { + encoder.setVertexBytes_length_atIndex( + scene_properties_bytes.cast::(), + core::mem::size_of_val(scene_properties_data), + 0, + ) + }; + + // compute the triangle geometry + let vertex_input_data: &[VertexInput] = &[ + VertexInput { + position: Position { + x: -f32::sqrt(3.0) / 4.0, + y: -0.25, + z: 0., + }, + color: Color { + r: 1., + g: 0., + b: 0., + }, + }, + VertexInput { + position: Position { + x: f32::sqrt(3.0) / 4.0, + y: -0.25, + z: 0., + }, + color: Color { + r: 0., + g: 1., + b: 0., + }, + }, + VertexInput { + position: Position { + x: 0., + y: 0.5, + z: 0., + }, + color: Color { + r: 0., + g: 0., + b: 1., + }, + }, + ]; + // write the triangle geometry to the vertex shader argument buffer at index 1 + let vertex_input_bytes = NonNull::from(vertex_input_data); + unsafe { + encoder.setVertexBytes_length_atIndex( + vertex_input_bytes.cast::(), + core::mem::size_of_val(vertex_input_data), + 1, + ) + }; + + // configure the encoder with the pipeline and draw the triangle + encoder.setRenderPipelineState(pipeline_state); + unsafe { + encoder.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangle, 0, 3) + }; + encoder.endEncoding(); + + // schedule the command buffer for display and commit + command_buffer.presentDrawable(¤t_drawable); + command_buffer.commit(); + } + + #[method(mtkView:drawableSizeWillChange:)] + #[allow(non_snake_case)] + unsafe fn mtkView_drawableSizeWillChange(&self, _view: &MTKView, _size: NSSize) { + // println!("mtkView_drawableSizeWillChange"); + } + } +); + +impl Delegate { + pub fn new(mtm: MainThreadMarker) -> Id { + let this = mtm.alloc(); + let this = this.set_ivars(Ivars { + start_date: unsafe { NSDate::now() }, + command_queue: OnceCell::default(), + pipeline_state: OnceCell::default(), + filter_chain: OnceCell::default(), + window: OnceCell::default(), + }); + unsafe { msg_send_id![super(this), init] } + } +} + +fn main() { + let mtm = MainThreadMarker::new().unwrap(); + // configure the app + let app = NSApplication::sharedApplication(mtm); + app.setActivationPolicy(NSApplicationActivationPolicyRegular); + + // configure the application delegate + let delegate = Delegate::new(mtm); + let object = ProtocolObject::from_ref(&*delegate); + app.setDelegate(Some(object)); + + // run the app + unsafe { app.run() }; +}