From 43da6e60c6cad8376abe6bdfc0fa882f5b8ff1d2 Mon Sep 17 00:00:00 2001 From: chyyran Date: Mon, 12 Feb 2024 00:43:51 -0500 Subject: [PATCH] rt(mtl): do intermediate passes offscreen --- librashader-runtime-metal/Cargo.toml | 11 +- librashader-runtime-metal/src/buffer.rs | 10 +- librashader-runtime-metal/src/draw_quad.rs | 61 ++- librashader-runtime-metal/src/filter_chain.rs | 21 +- librashader-runtime-metal/src/filter_pass.rs | 11 +- .../src/graphics_pipeline.rs | 23 +- librashader-runtime-metal/src/luts.rs | 14 +- librashader-runtime-metal/src/main.rs | 460 ++++++++++++++++++ librashader-runtime-metal/src/texture.rs | 26 +- .../tests/hello_triangle/main.rs | 62 ++- 10 files changed, 633 insertions(+), 66 deletions(-) create mode 100644 librashader-runtime-metal/src/main.rs diff --git a/librashader-runtime-metal/Cargo.toml b/librashader-runtime-metal/Cargo.toml index d3c1b00..7704991 100644 --- a/librashader-runtime-metal/Cargo.toml +++ b/librashader-runtime-metal/Cargo.toml @@ -27,9 +27,9 @@ array-concat = "0.5.2" bytemuck = { version = "1.12.3", features = ["derive"] } rayon = "1.8.1" -[dev-dependencies.icrate] +[dependencies.icrate] version = "0.1.0" -features = ["AppKit", "AppKit_all", "Foundation", "Foundation_all", "MetalKit", "MetalKit_all"] +features = ["AppKit", "AppKit_all", "Foundation", "Foundation_all", "MetalKit", "MetalKit_all", "Metal", "Metal_all"] [[test]] name = "triangle" @@ -40,5 +40,8 @@ harness = false features = ["librashader-cache/docsrs"] [target.'cfg(target_vendor="apple")'.dependencies] -icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all" ]} -objc2 = { version = "0.5.0", features = ["apple"] } \ No newline at end of file +#icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all" ]} +objc2 = { version = "0.5.0", features = ["apple"] } +# +#[lib] +#crate-type = ["lib", "staticlib"] \ No newline at end of file diff --git a/librashader-runtime-metal/src/buffer.rs b/librashader-runtime-metal/src/buffer.rs index c2b2ee0..36f9590 100644 --- a/librashader-runtime-metal/src/buffer.rs +++ b/librashader-runtime-metal/src/buffer.rs @@ -24,7 +24,7 @@ impl MetalBuffer { let resource_mode = if cfg!(target_os = "ios") { MTLResourceStorageModeShared } else { - MTLResourceStorageModeManaged + MTLResourceStorageModeShared }; let buffer = device @@ -35,10 +35,10 @@ impl MetalBuffer { pub fn flush(&self) { // We don't know what was actually written to so... - self.buffer.didModifyRange(NSRange { - location: 0, - length: self.size, - }) + // self.buffer.didModifyRange(NSRange { + // location: 0, + // length: self.size, + // }) } } diff --git a/librashader-runtime-metal/src/draw_quad.rs b/librashader-runtime-metal/src/draw_quad.rs index 66c8be2..872d103 100644 --- a/librashader-runtime-metal/src/draw_quad.rs +++ b/librashader-runtime-metal/src/draw_quad.rs @@ -15,26 +15,46 @@ use crate::graphics_pipeline::VERTEX_BUFFER_INDEX; #[repr(C)] #[derive(Debug, Copy, Clone, Default, Zeroable, Pod)] -struct MetalVertex { - position: [f32; 4], - texcoord: [f32; 2], +pub(crate) struct MetalVertex { + pub position: [f32; 4], + pub texcoord: [f32; 2], } -const FINAL_VBO_DATA: [MetalVertex; 4] = [ +const OFFSCREEN_VBO_DATA: [MetalVertex; 4] = [ MetalVertex { - position: [0.0, 1.0, 0.0, 1.0], + position: [-1.0, -1.0, 0.0, 1.0], texcoord: [0.0, 1.0], }, MetalVertex { - position: [1.0, 1.0, 0.0, 1.0], + position: [-1.0, 1.0, 0.0, 1.0], + texcoord: [0.0, 0.0], + }, + MetalVertex { + position: [1.0, -1.0, 0.0, 1.0], texcoord: [1.0, 1.0], }, + MetalVertex { + position: [1.0, 1.0, 0.0, 1.0], + texcoord: [1.0, 0.0], + }, +]; + + +const FINAL_VBO_DATA: [MetalVertex; 4] = [ MetalVertex { position: [0.0, 0.0, 0.0, 1.0], + texcoord: [0.0, 1.0], + }, + MetalVertex { + position: [0.0, 1.0, 0.0, 1.0], texcoord: [0.0, 0.0], }, MetalVertex { position: [1.0, 0.0, 0.0, 1.0], + texcoord: [1.0, 1.0], + }, + MetalVertex { + position: [1.0, 1.0, 0.0, 1.0], texcoord: [1.0, 0.0], }, ]; @@ -57,7 +77,7 @@ const VBO_DEFAULT_FINAL: [f32; 16] = [ 1.0, 1.0, 1.0, 1.0, ]; -const VBO_DATA: [f32; 32] = concat_arrays!(VBO_OFFSCREEN, VBO_DEFAULT_FINAL); +const VBO_DATA: [MetalVertex; 8] = concat_arrays!(OFFSCREEN_VBO_DATA, FINAL_VBO_DATA); pub struct DrawQuad { buffer: Id>, @@ -66,6 +86,22 @@ pub struct DrawQuad { impl DrawQuad { pub fn new(device: &ProtocolObject) -> Result { let vbo_data: &'static [u8] = bytemuck::cast_slice(&VBO_DATA); + // let buffer = unsafe { + // device + // .newBufferWithBytes_length_options( + // // SAFETY: this pointer is const. + // // https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes + // NonNull::new_unchecked(vbo_data.as_ptr() as *mut c_void), + // vbo_data.len(), + // if cfg!(target_os = "ios") { + // MTLResourceStorageModeShared + // } else { + // MTLResourceStorageModeManaged + // }, + // ) + // .ok_or(FilterChainError::BufferError)? + // }; + let buffer = unsafe { device .newBufferWithBytes_length_options( @@ -93,13 +129,10 @@ impl DrawQuad { }; unsafe { - 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); + cmd.setVertexBuffer_offset_atIndex(Some(&self.buffer), 0, 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 61c80d9..a912e46 100644 --- a/librashader-runtime-metal/src/filter_chain.rs +++ b/librashader-runtime-metal/src/filter_chain.rs @@ -202,8 +202,11 @@ impl FilterChainMetal { fn push_history( &mut self, input: &ProtocolObject, - cmd: &ProtocolObject, + cmd: &ProtocolObject, ) -> error::Result<()> { + let mipmapper = cmd + .blitCommandEncoder() + .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; if let Some(mut back) = self.history_framebuffers.pop_back() { if back.texture.height() != input.height() || back.texture.width() != input.width() @@ -220,10 +223,11 @@ impl FilterChainMetal { ); } - back.copy_from(cmd, input)?; + back.copy_from(&mipmapper, input)?; self.history_framebuffers.push_front(back); } + mipmapper.endEncoding(); Ok(()) } @@ -317,7 +321,7 @@ impl FilterChainMetal { /// Records shader rendering commands to the provided command encoder. pub fn frame( &mut self, - input: Id>, + input: &ProtocolObject, viewport: &Viewport<&ProtocolObject>, cmd_buffer: &ProtocolObject, frame_count: usize, @@ -394,17 +398,14 @@ impl FilterChainMetal { let (pass, last) = passes.split_at_mut(passes_len - 1); let options = options.unwrap_or(&self.default_options); - let mipmapper = cmd_buffer - .blitCommandEncoder() - .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; - for (index, pass) in pass.iter_mut().enumerate() { let target = &self.output_framebuffers[index]; source.filter_mode = pass.config.filter; source.wrap_mode = pass.config.wrap_mode; source.mip_filter = pass.config.filter; - let out = RenderTarget::identity(target.texture.as_ref()); + let out = + RenderTarget::identity(target.texture.as_ref()); pass.draw( &cmd_buffer, index, @@ -419,7 +420,7 @@ impl FilterChainMetal { )?; if target.max_miplevels > 1 && !self.disable_mipmaps { - target.generate_mipmaps(&mipmapper); + target.generate_mipmaps(&cmd_buffer)?; } source = self.common.output_textures[index] @@ -457,7 +458,7 @@ impl FilterChainMetal { )?; } - self.push_history(&input, &mipmapper)?; + self.push_history(&input, &cmd_buffer)?; self.common.internal_frame_count = self.common.internal_frame_count.wrapping_add(1); Ok(()) } diff --git a/librashader-runtime-metal/src/filter_pass.rs b/librashader-runtime-metal/src/filter_pass.rs index fcaccbb..3dcaef1 100644 --- a/librashader-runtime-metal/src/filter_pass.rs +++ b/librashader-runtime-metal/src/filter_pass.rs @@ -43,7 +43,6 @@ impl BindSemantics, MetalBuffer, MetalBuffer> for Fi let sampler = samplers.get(texture.wrap_mode, texture.filter_mode, texture.mip_filter); unsafe { - renderpass.setFragmentTexture_atIndex(Some(&texture.texture), binding.binding as usize); renderpass.setFragmentTexture_atIndex(Some(&texture.texture), binding.binding as usize); renderpass.setFragmentSamplerState_atIndex(Some(sampler), binding.binding as usize); } @@ -95,6 +94,11 @@ impl FilterPass { Some(self.uniform_storage.inner_ubo().as_ref()), 0, ubo.binding as usize, + ); + cmd.setFragmentBuffer_offset_atIndex( + Some(self.uniform_storage.inner_ubo().as_ref()), + 0, + ubo.binding as usize, ) } } @@ -105,6 +109,11 @@ impl FilterPass { Some(self.uniform_storage.inner_ubo().as_ref()), 0, pcb.binding.unwrap_or(1) as usize, + ); + cmd.setFragmentBuffer_offset_atIndex( + Some(self.uniform_storage.inner_ubo().as_ref()), + 0, + pcb.binding.unwrap_or(1) as usize, ) } } diff --git a/librashader-runtime-metal/src/graphics_pipeline.rs b/librashader-runtime-metal/src/graphics_pipeline.rs index cff8044..92b4abb 100644 --- a/librashader-runtime-metal/src/graphics_pipeline.rs +++ b/librashader-runtime-metal/src/graphics_pipeline.rs @@ -1,19 +1,13 @@ +use std::mem::offset_of; use crate::error::{FilterChainError, Result}; use icrate::Foundation::NSString; -use icrate::Metal::{ - MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, MTLDevice, - MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat, - MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor, - MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor, - MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture, - MTLVertexAttributeDescriptor, MTLVertexBufferLayoutDescriptor, MTLVertexDescriptor, - MTLVertexFormatFloat2, MTLVertexStepFunctionPerVertex, MTLViewport, -}; +use icrate::Metal::{MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLClearColor, MTLCommandBuffer, MTLDevice, MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat, MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor, MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor, MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture, MTLVertexAttributeDescriptor, MTLVertexBufferLayoutDescriptor, MTLVertexDescriptor, MTLVertexFormatFloat2, MTLVertexFormatFloat4, MTLVertexStepFunctionPerVertex, MTLViewport}; use librashader_reflect::back::msl::{CrossMslContext, NagaMslContext}; use librashader_reflect::back::ShaderCompilerOutput; use librashader_runtime::render_target::RenderTarget; use objc2::rc::Id; use objc2::runtime::ProtocolObject; +use crate::draw_quad::MetalVertex; /// This is only really plausible for SPIRV-Cross, for Naga we need to supply the next plausible binding. pub const VERTEX_BUFFER_INDEX: usize = 4; @@ -85,20 +79,20 @@ impl PipelineLayoutObjects { let texcoord = MTLVertexAttributeDescriptor::new(); // hopefully metal fills in vertices otherwise we'll need to use the vec4 stuff. - position.setFormat(MTLVertexFormatFloat2); + position.setFormat(MTLVertexFormatFloat4); position.setBufferIndex(VERTEX_BUFFER_INDEX); - position.setOffset(0); + position.setOffset(offset_of!(MetalVertex, position)); texcoord.setFormat(MTLVertexFormatFloat2); texcoord.setBufferIndex(VERTEX_BUFFER_INDEX); - texcoord.setOffset(2 * std::mem::size_of::()); + texcoord.setOffset(offset_of!(MetalVertex, texcoord)); attributes.setObject_atIndexedSubscript(Some(&position), 0); attributes.setObject_atIndexedSubscript(Some(&texcoord), 1); binding.setStepFunction(MTLVertexStepFunctionPerVertex); - binding.setStride(4 * std::mem::size_of::()); + binding.setStride(std::mem::size_of::()); layouts.setObject_atIndexedSubscript(Some(&binding), VERTEX_BUFFER_INDEX); descriptor @@ -178,7 +172,8 @@ impl MetalGraphicsPipeline { ) -> Result>> { unsafe { let descriptor = MTLRenderPassDescriptor::new(); - let ca = descriptor.colorAttachments().objectAtIndexedSubscript(0); + let ca = descriptor.colorAttachments() + .objectAtIndexedSubscript(0); ca.setLoadAction(MTLLoadActionDontCare); ca.setStoreAction(MTLStoreActionStore); ca.setTexture(Some(output.output)); diff --git a/librashader-runtime-metal/src/luts.rs b/librashader-runtime-metal/src/luts.rs index 73fe6af..507d04a 100644 --- a/librashader-runtime-metal/src/luts.rs +++ b/librashader-runtime-metal/src/luts.rs @@ -36,11 +36,13 @@ impl LutTexture { ); descriptor.setSampleCount(1); - descriptor.setMipmapLevelCount(if config.mipmap { - image.size.calculate_miplevels() as usize - } else { - 1 - }); + // descriptor.setMipmapLevelCount(if config.mipmap { + // image.size.calculate_miplevels() as usize + // } else { + // 1 + // }); + + descriptor.setMipmapLevelCount(1); descriptor.setUsage(MTLTextureUsageShaderRead); @@ -71,7 +73,7 @@ impl LutTexture { } if config.mipmap { - mipmapper.generateMipmapsForTexture(&texture); + // mipmapper.generateMipmapsForTexture(&texture); } Ok(LutTexture(InputTexture { diff --git a/librashader-runtime-metal/src/main.rs b/librashader-runtime-metal/src/main.rs new file mode 100644 index 0000000..c91ed34 --- /dev/null +++ b/librashader-runtime-metal/src/main.rs @@ -0,0 +1,460 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use core::{cell::OnceCell, ptr::NonNull}; +use std::sync::RwLock; + +use icrate::Metal::{MTLBlitCommandEncoder, MTLClearColor, MTLPixelFormatRGBA8Unorm, 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-lottes.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(); + + let backbuffer = command_queue + .device() + .newTextureWithDescriptor(&tex_desc) + .unwrap(); + + 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() }; +} diff --git a/librashader-runtime-metal/src/texture.rs b/librashader-runtime-metal/src/texture.rs index 65c596a..1eb238f 100644 --- a/librashader-runtime-metal/src/texture.rs +++ b/librashader-runtime-metal/src/texture.rs @@ -1,7 +1,7 @@ use crate::error::{FilterChainError, Result}; use icrate::Metal::{ - MTLBlitCommandEncoder, MTLCommandBuffer, MTLDevice, MTLPixelFormat, MTLTexture, - MTLTextureDescriptor, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead, + MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLPixelFormat, + MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead, MTLTextureUsageShaderWrite, }; use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; @@ -61,11 +61,13 @@ impl OwnedTexture { ); descriptor.setSampleCount(1); - descriptor.setMipmapLevelCount(if max_miplevels <= 1 { - size.calculate_miplevels() as usize - } else { - 1 - }); + // descriptor.setMipmapLevelCount(if max_miplevels <= 1 { + // size.calculate_miplevels() as usize + // } else { + // 1 + // }); + + descriptor.setMipmapLevelCount(1); descriptor.setUsage( MTLTextureUsageShaderRead @@ -140,9 +142,13 @@ impl OwnedTexture { // cmd.clear_texture(&self.image, &wgpu::ImageSubresourceRange::default()); } - /// caller must end the blit encoder after. - pub fn generate_mipmaps(&self, mipmapper: &ProtocolObject) { - mipmapper.generateMipmapsForTexture(&self.texture); + pub fn generate_mipmaps(&self, cmd: &ProtocolObject) -> Result<()> { + let mipmapper = cmd + .blitCommandEncoder() + .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; + // mipmapper.generateMipmapsForTexture(&self.texture); + mipmapper.endEncoding(); + Ok(()) } } diff --git a/librashader-runtime-metal/tests/hello_triangle/main.rs b/librashader-runtime-metal/tests/hello_triangle/main.rs index 5ef17d1..2ad62fb 100644 --- a/librashader-runtime-metal/tests/hello_triangle/main.rs +++ b/librashader-runtime-metal/tests/hello_triangle/main.rs @@ -1,8 +1,9 @@ #![deny(unsafe_op_in_unsafe_fn)] use core::{cell::OnceCell, ptr::NonNull}; +use std::sync::RwLock; -use icrate::Metal::MTLClearColor; +use icrate::Metal::{MTLBlitCommandEncoder, MTLClearColor, MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget}; use icrate::{ AppKit::{ NSApplication, NSApplicationActivationPolicyRegular, NSApplicationDelegate, @@ -20,6 +21,7 @@ use icrate::{ }, MetalKit::{MTKView, MTKViewDelegate}, }; +use librashader_common::Viewport; use librashader_presets::ShaderPreset; use librashader_runtime_metal::FilterChainMetal; use objc2::{ @@ -123,7 +125,7 @@ struct Ivars { start_date: Id, command_queue: OnceCell>>, pipeline_state: OnceCell>>, - filter_chain: OnceCell, + filter_chain: OnceCell>, window: OnceCell>, } @@ -227,6 +229,8 @@ declare_class!( ) .unwrap(); + let filter_chain = RwLock::new(filter_chain); + // configure the metal view delegate unsafe { let object = ProtocolObject::from_ref(self); @@ -254,8 +258,10 @@ declare_class!( 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, @@ -280,6 +286,7 @@ declare_class!( let Some(pass_descriptor) = (unsafe { mtk_view.currentRenderPassDescriptor() }) else { return; }; + let Some(encoder) = command_buffer.renderCommandEncoderWithDescriptor(&pass_descriptor) else { return; @@ -348,6 +355,7 @@ declare_class!( ) }; + // configure the encoder with the pipeline and draw the triangle encoder.setRenderPipelineState(pipeline_state); unsafe { @@ -355,6 +363,56 @@ declare_class!( }; 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); + // let frontbuffer = command_queue + // .device() + // .newTextureWithDescriptor(&tex_desc) + // .unwrap(); + + let backbuffer = command_queue + .device() + .newTextureWithDescriptor(&tex_desc) + .unwrap(); + + // let blit = command_buffer + // .blitCommandEncoder() + // .unwrap(); + // blit.copyFromTexture_toTexture(&texture, &frontbuffer); + // blit.endEncoding(); + + filter_chain.frame(&texture, + &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();