diff --git a/Cargo.lock b/Cargo.lock index 564c4a1..50aad78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1859,8 +1859,11 @@ dependencies = [ "librashader-reflect", "librashader-runtime", "objc2 0.5.2", + "objc2-app-kit", "objc2-foundation", "objc2-metal", + "objc2-metal-kit", + "objc2-quartz-core", "rayon", "thiserror", ] @@ -2291,6 +2294,46 @@ dependencies = [ "objc2-encode 4.0.3", ] +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "objc2-encode" version = "3.0.0" @@ -2327,6 +2370,33 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-metal-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9ec2524854019d1ff69d0a3222ce930a1bdf31add9b58ef8cffb6a37eead6c" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", + "objc2-metal", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "once_cell" version = "1.19.0" diff --git a/librashader-reflect/src/lib.rs b/librashader-reflect/src/lib.rs index 1311614..bbae1c7 100644 --- a/librashader-reflect/src/lib.rs +++ b/librashader-reflect/src/lib.rs @@ -45,7 +45,6 @@ //! is supported. #![feature(impl_trait_in_assoc_type)] #![feature(let_chains)] - #![allow(stable_features)] #![feature(c_str_literals)] /// Shader codegen backends. diff --git a/librashader-runtime-mtl/Cargo.toml b/librashader-runtime-mtl/Cargo.toml index 3755653..190b54e 100644 --- a/librashader-runtime-mtl/Cargo.toml +++ b/librashader-runtime-mtl/Cargo.toml @@ -39,8 +39,12 @@ objc2-metal = { workspace = true, features = ["all"] } objc2 = { workspace = true, features = ["apple"] } [features] - # run_test = ["icrate/AppKit", "i "icrate/Foundation_all", "icrate/MetalKit", "icrate/MetalKit_all"] +[dev-dependencies] +objc2-metal-kit = { version = "0.2", features = ["all"]} +objc2-foundation = { version = "0.2", features = ["all"] } +objc2-app-kit = { version = "0.2", features = ["all"] } +objc2-quartz-core = { version = "0.2", features = ["CAMetalLayer", "objc2-metal"]} #[lib] #crate-type = ["lib", "staticlib"] \ No newline at end of file diff --git a/librashader-runtime-mtl/tests/hello_triangle/main.rs b/librashader-runtime-mtl/tests/hello_triangle/main.rs index 183fd7a..319326c 100644 --- a/librashader-runtime-mtl/tests/hello_triangle/main.rs +++ b/librashader-runtime-mtl/tests/hello_triangle/main.rs @@ -1,32 +1,29 @@ -#![deny(unsafe_op_in_unsafe_fn)] - use core::{cell::OnceCell, ptr::NonNull}; use std::sync::RwLock; -use icrate::Metal::{ - MTLBlitCommandEncoder, MTLClearColor, MTLTexture, MTLTextureDescriptor, - MTLTextureUsageRenderTarget, +use objc2_app_kit::{ + NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSBackingStoreType, + NSWindow, NSWindowStyleMask, }; -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 objc2_metal::{ + MTLBlitCommandEncoder, MTLClearColor, MTLTexture, MTLTextureDescriptor, MTLTextureUsage, }; + +use objc2_foundation::{ + ns_string, MainThreadMarker, NSDate, NSNotification, NSObject, NSObjectProtocol, NSPoint, + NSRect, NSSize, +}; + +use objc2_metal::{ + MTLCommandBuffer, MTLCommandEncoder, MTLCommandQueue, MTLCreateSystemDefaultDevice, MTLDevice, + MTLDrawable, MTLLibrary, MTLPrimitiveType, MTLRenderCommandEncoder, + MTLRenderPipelineDescriptor, MTLRenderPipelineState, +}; +use objc2_metal_kit::{MTKView, MTKViewDelegate}; + use librashader_common::Viewport; use librashader_presets::ShaderPreset; -use librashader_runtime_metal::FilterChainMetal; +use librashader_runtime_mtl::FilterChainMetal; use objc2::{ declare_class, msg_send_id, mutability::MainThreadOnly, rc::Id, runtime::ProtocolObject, ClassType, DeclaredClass, @@ -161,10 +158,10 @@ declare_class!( // 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 style = NSWindowStyleMask::Closable + | NSWindowStyleMask::Resizable + | NSWindowStyleMask::Titled; + let backing_store_type = NSBackingStoreType::NSBackingStoreBuffered; let flag = false; unsafe { NSWindow::initWithContentRect_styleMask_backing_defer( @@ -362,7 +359,7 @@ declare_class!( // configure the encoder with the pipeline and draw the triangle encoder.setRenderPipelineState(pipeline_state); unsafe { - encoder.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangle, 0, 3) + encoder.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 3) }; encoder.endEncoding(); @@ -382,7 +379,7 @@ declare_class!( false ); - tex_desc.setUsage(MTLTextureUsageRenderTarget); + tex_desc.setUsage(MTLTextureUsage::RenderTarget); // let frontbuffer = command_queue // .device() // .newTextureWithDescriptor(&tex_desc) @@ -447,7 +444,7 @@ fn main() { let mtm = MainThreadMarker::new().unwrap(); // configure the app let app = NSApplication::sharedApplication(mtm); - app.setActivationPolicy(NSApplicationActivationPolicyRegular); + app.setActivationPolicy(NSApplicationActivationPolicy::Regular); // configure the application delegate let delegate = Delegate::new(mtm); diff --git a/librashader-runtime-mtl/tests/hello_triangle/mod.rs b/librashader-runtime-mtl/tests/hello_triangle/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/librashader-runtime-mtl/tests/main.rs b/librashader-runtime-mtl/tests/main.rs index 91cafd9..5b49ac1 100644 --- a/librashader-runtime-mtl/tests/main.rs +++ b/librashader-runtime-mtl/tests/main.rs @@ -1,488 +1,6 @@ -#![cfg(feature = "run_test")] -#![deny(unsafe_op_in_unsafe_fn)] +include!("./hello_triangle/main.rs"); -use core::{cell::OnceCell, ptr::NonNull}; -use std::sync::RwLock; - -use icrate::Foundation::NSString; -use icrate::Metal::{ - MTLBlitCommandEncoder, MTLClearColor, MTLResource, MTLTexture, MTLTextureDescriptor, - MTLTextureUsagePixelFormatView, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead, -}; -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_common::Viewport; -use librashader_presets::ShaderPreset; -use librashader_runtime_mtl::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-lottes.slangp").unwrap(); - - let preset = ShaderPreset::try_parse("./test/shaders_slang/crt/crt-royale.slangp").unwrap(); - // let preset = ShaderPreset::try_parse("./test/basic.slangp").unwrap(); - - let filter_chain = FilterChainMetal::load_from_preset( - preset, - &command_queue, - None, - ) - .unwrap(); - - let filter_chain = RwLock::new(filter_chain); - - // 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); - idcell!(filter_chain <= self); - - unsafe { - mtk_view.setFramebufferOnly(false); - 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, - time: 0.0 - }; - // 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(); - - - unsafe { - let mut filter_chain = filter_chain.write().unwrap(); - let texture = pass_descriptor - .colorAttachments() - .objectAtIndexedSubscript(0) - .texture() - .unwrap(); - - let tex_desc = MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped( - texture.pixelFormat(), - texture.width(), - texture.height(), - false - ); - - tex_desc.setUsage(MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsagePixelFormatView); - // tex_desc.setPixelFormat(MTLPixelFormatRGBA8Unorm); - let frontbuffer = command_queue - .device() - .newTextureWithDescriptor(&tex_desc) - .unwrap(); - - frontbuffer - .setLabel(Some(&*NSString::from_str("librashader frontbuffer"))); - - let backbuffer = command_queue - .device() - .newTextureWithDescriptor(&tex_desc) - .unwrap(); - - backbuffer - .setLabel(Some(&*NSString::from_str("librashader backbuffer"))); - let blit = command_buffer - .blitCommandEncoder() - .unwrap(); - blit.copyFromTexture_toTexture(&texture, &frontbuffer); - blit.endEncoding(); - - filter_chain.frame(&frontbuffer, - &Viewport { - x: 0.0, - y: 0.0, - mvp: None, - output: &backbuffer - }, &command_buffer, 1, None) - .expect("frame"); - - // let rpass = MTLRenderPassDescriptor::new(); - // rpass.colorAttachments() - // .objectAtIndexedSubscript(0) - // .setTexture(Some(&backbuffer)); - // rpass.colorAttachments() - // .objectAtIndexedSubscript(0) - // .setLoadAction(MTLLoadActionClear); - // rpass.colorAttachments() - // .objectAtIndexedSubscript(0) - // .setStoreAction(MTLStoreActionStore); - // - - // let clear = command_buffer - // .renderCommandEncoderWithDescriptor(&*rpass) - // .unwrap(); - // - // - // clear.endEncoding(); - - let blit = command_buffer - .blitCommandEncoder() - .unwrap(); - blit.copyFromTexture_toTexture(&backbuffer, &texture); - blit.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() }; -} +#[test] +fn test_mtl() { + main() +} \ No newline at end of file diff --git a/librashader-runtime-mtl/tests/triangle.rs b/librashader-runtime-mtl/tests/triangle.rs index 2e4967a..e69de29 100644 --- a/librashader-runtime-mtl/tests/triangle.rs +++ b/librashader-runtime-mtl/tests/triangle.rs @@ -1,469 +0,0 @@ -#![deny(unsafe_op_in_unsafe_fn)] - -use core::{cell::OnceCell, ptr::NonNull}; -use std::sync::RwLock; - -use icrate::Foundation::NSString; -use icrate::Metal::{ - MTLBlitCommandEncoder, MTLClearColor, MTLPixelFormatRGBA8Unorm, MTLResource, MTLTexture, - MTLTextureDescriptor, MTLTextureUsagePixelFormatView, MTLTextureUsageRenderTarget, - MTLTextureUsageShaderRead, -}; -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_common::Viewport; -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-lottes.slangp").unwrap(); - - let preset = ShaderPreset::try_parse("./test/shaders_slang/crt/crt-royale.slangp").unwrap(); - // let preset = ShaderPreset::try_parse("./test/basic.slangp").unwrap(); - - let filter_chain = FilterChainMetal::load_from_preset( - preset, - &command_queue, - None, - ) - .unwrap(); - - let filter_chain = RwLock::new(filter_chain); - - // 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); - idcell!(filter_chain <= self); - - unsafe { - mtk_view.setFramebufferOnly(false); - 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, - time: 0.0 - }; - // 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(); - - - unsafe { - let mut filter_chain = filter_chain.write().unwrap(); - let texture = pass_descriptor - .colorAttachments() - .objectAtIndexedSubscript(0) - .texture() - .unwrap(); - - let tex_desc = MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped( - texture.pixelFormat(), - texture.width(), - texture.height(), - false - ); - - tex_desc.setUsage(MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsagePixelFormatView); - // tex_desc.setPixelFormat(MTLPixelFormatRGBA8Unorm); - let frontbuffer = command_queue - .device() - .newTextureWithDescriptor(&tex_desc) - .unwrap(); - - frontbuffer - .setLabel(Some(&*NSString::from_str("librashader frontbuffer"))); - - let backbuffer = command_queue - .device() - .newTextureWithDescriptor(&tex_desc) - .unwrap(); - - backbuffer - .setLabel(Some(&*NSString::from_str("librashader backbuffer"))); - let blit = command_buffer - .blitCommandEncoder() - .unwrap(); - blit.copyFromTexture_toTexture(&texture, &frontbuffer); - blit.endEncoding(); - - filter_chain.frame(&frontbuffer, - &Viewport { - x: 0.0, - y: 0.0, - mvp: None, - output: &backbuffer - }, &command_buffer, 1, None) - .expect("frame"); - - let blit = command_buffer - .blitCommandEncoder() - .unwrap(); - blit.copyFromTexture_toTexture(&backbuffer, &texture); - blit.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() }; -}