From a7b1682a37a0c93dde25c1adca8e6a4926c832d3 Mon Sep 17 00:00:00 2001 From: chyyran Date: Sun, 11 Feb 2024 20:38:55 -0500 Subject: [PATCH] rt(mtl): implement filter pass and filter chain logic --- Cargo.lock | 1 + librashader-reflect/src/back/msl.rs | 1 + librashader-reflect/src/reflect/naga/msl.rs | 3 + librashader-runtime-metal/Cargo.toml | 1 + librashader-runtime-metal/src/buffer.rs | 9 +- librashader-runtime-metal/src/draw_quad.rs | 3 +- librashader-runtime-metal/src/filter_chain.rs | 381 +++++++++++++++++- librashader-runtime-metal/src/filter_pass.rs | 169 +++++++- .../src/graphics_pipeline.rs | 49 ++- librashader-runtime-metal/src/lib.rs | 11 +- librashader-runtime-metal/src/luts.rs | 31 +- librashader-runtime-metal/src/texture.rs | 128 ++++-- 12 files changed, 689 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffb079f..f812abf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1737,6 +1737,7 @@ dependencies = [ "librashader-reflect", "librashader-runtime", "objc2 0.5.0", + "rayon", "rustc-hash", "thiserror", ] diff --git a/librashader-reflect/src/back/msl.rs b/librashader-reflect/src/back/msl.rs index c8e71be..3a2f857 100644 --- a/librashader-reflect/src/back/msl.rs +++ b/librashader-reflect/src/back/msl.rs @@ -50,6 +50,7 @@ pub struct NagaMslModule { pub struct NagaMslContext { pub vertex: NagaMslModule, pub fragment: NagaMslModule, + pub next_free_binding: u32, } impl FromCompilation for MSL { diff --git a/librashader-reflect/src/reflect/naga/msl.rs b/librashader-reflect/src/reflect/naga/msl.rs index 06a69b4..2df010d 100644 --- a/librashader-reflect/src/reflect/naga/msl.rs +++ b/librashader-reflect/src/reflect/naga/msl.rs @@ -136,6 +136,8 @@ impl CompileShader for NagaReflect { let fragment = write_msl(&self.fragment, frag_options)?; let vertex = write_msl(&self.vertex, vert_options)?; + + let vertex_binding = self.get_next_binding(0); Ok(ShaderCompilerOutput { vertex: vertex.0, fragment: fragment.0, @@ -148,6 +150,7 @@ impl CompileShader for NagaReflect { translation_info: vertex.1, module: self.vertex, }, + next_free_binding: vertex_binding }, }) } diff --git a/librashader-runtime-metal/Cargo.toml b/librashader-runtime-metal/Cargo.toml index f8d20f7..f03dd7c 100644 --- a/librashader-runtime-metal/Cargo.toml +++ b/librashader-runtime-metal/Cargo.toml @@ -25,6 +25,7 @@ rustc-hash = "1.1.0" thiserror = "1.0" array-concat = "0.5.2" bytemuck = { version = "1.12.3", features = ["derive"] } +rayon = "1.8.1" [dev-dependencies] diff --git a/librashader-runtime-metal/src/buffer.rs b/librashader-runtime-metal/src/buffer.rs index b1bb5f5..c2b2ee0 100644 --- a/librashader-runtime-metal/src/buffer.rs +++ b/librashader-runtime-metal/src/buffer.rs @@ -1,3 +1,4 @@ +use crate::error; use crate::error::FilterChainError; use icrate::Foundation::NSRange; use icrate::Metal::{ @@ -12,8 +13,14 @@ pub struct MetalBuffer { size: usize, } +impl AsRef> for MetalBuffer { + fn as_ref(&self) -> &ProtocolObject { + self.buffer.as_ref() + } +} + impl MetalBuffer { - pub fn new(device: &ProtocolObject, size: usize) -> Result { + pub fn new(device: &ProtocolObject, size: usize) -> error::Result { let resource_mode = if cfg!(target_os = "ios") { MTLResourceStorageModeShared } else { diff --git a/librashader-runtime-metal/src/draw_quad.rs b/librashader-runtime-metal/src/draw_quad.rs index 463d347..6f36cce 100644 --- a/librashader-runtime-metal/src/draw_quad.rs +++ b/librashader-runtime-metal/src/draw_quad.rs @@ -11,6 +11,7 @@ use std::ffi::c_void; use std::ptr::NonNull; use crate::error::{FilterChainError, Result}; +use crate::graphics_pipeline::VERTEX_BUFFER_INDEX; #[repr(C)] #[derive(Debug, Copy, Clone, Default, Zeroable, Pod)] @@ -92,7 +93,7 @@ impl DrawQuad { }; unsafe { - cmd.setVertexBuffer_offset_atIndex(Some(self.buffer.as_ref()), 0, 0); + cmd.setVertexBuffer_offset_atIndex(Some(self.buffer.as_ref()), 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 33269c3..250fa9f 100644 --- a/librashader-runtime-metal/src/filter_chain.rs +++ b/librashader-runtime-metal/src/filter_chain.rs @@ -1,27 +1,41 @@ +use crate::buffer::MetalBuffer; use crate::draw_quad::DrawQuad; use crate::error; use crate::error::FilterChainError; use crate::filter_pass::FilterPass; +use crate::graphics_pipeline::MetalGraphicsPipeline; use crate::luts::LutTexture; -use crate::options::FilterChainOptionsMetal; +use crate::options::{FilterChainOptionsMetal, FrameOptionsMetal}; use crate::samplers::SamplerSet; -use crate::texture::{MetalTexture, OwnedImage}; -use icrate::Metal::{MTLCommandBuffer, MTLCommandQueue, MTLDevice}; +use crate::texture::{get_texture_size, InputTexture, OwnedTexture}; +use icrate::Metal::{ + MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLCommandQueue, MTLDevice, + MTLPixelFormat, MTLPixelFormatRGBA8Unorm, MTLTexture, +}; +use librashader_common::{ImageFormat, Size, Viewport}; use librashader_presets::context::VideoDriver; use librashader_presets::{ShaderPassConfig, ShaderPreset, TextureConfig}; -use librashader_reflect::back::targets::{MSL, WGSL}; -use librashader_reflect::back::CompileReflectShader; +use librashader_reflect::back::msl::MslVersion; +use librashader_reflect::back::targets::MSL; +use librashader_reflect::back::{CompileReflectShader, CompileShader}; use librashader_reflect::front::{Glslang, SpirvCompilation}; use librashader_reflect::reflect::cross::SpirvCross; -use librashader_reflect::reflect::naga::Naga; use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact}; use librashader_reflect::reflect::semantics::ShaderSemantics; +use librashader_reflect::reflect::ReflectShader; +use librashader_runtime::binding::BindingUtil; +use librashader_runtime::framebuffer::FramebufferInit; +use librashader_runtime::image::{Image, ImageError, UVDirection, BGRA8}; +use librashader_runtime::quad::QuadType; +use librashader_runtime::render_target::RenderTarget; +use librashader_runtime::scaling::ScaleFramebuffer; +use librashader_runtime::uniforms::UniformStorage; use objc2::rc::Id; use objc2::runtime::ProtocolObject; +use rayon::prelude::*; use rustc_hash::FxHashMap; use std::collections::VecDeque; use std::path::Path; -use std::sync::Arc; type ShaderPassMeta = ShaderPassArtifact + Send>; @@ -36,13 +50,13 @@ fn compile_passes( Ok((passes, semantics)) } -/// A wgpu filter chain. +/// A Metal filter chain. pub struct FilterChainMetal { pub(crate) common: FilterCommon, passes: Box<[FilterPass]>, - output_framebuffers: Box<[OwnedImage]>, - feedback_framebuffers: Box<[OwnedImage]>, - history_framebuffers: VecDeque, + output_framebuffers: Box<[OwnedTexture]>, + feedback_framebuffers: Box<[OwnedTexture]>, + history_framebuffers: VecDeque, disable_mipmaps: bool, } @@ -52,23 +66,22 @@ pub struct FilterMutable { } pub(crate) struct FilterCommon { - pub output_textures: Box<[Option]>, - pub feedback_textures: Box<[Option]>, - pub history_textures: Box<[Option]>, + pub output_textures: Box<[Option]>, + pub feedback_textures: Box<[Option]>, + pub history_textures: Box<[Option]>, pub luts: FxHashMap, pub samplers: SamplerSet, pub config: FilterMutable, pub internal_frame_count: i32, pub(crate) draw_quad: DrawQuad, device: Id>, - queue: Id>, } impl FilterChainMetal { /// Load the shader preset at the given path into a filter chain. pub fn load_from_path( path: impl AsRef, - queue: Id>, + queue: &ProtocolObject, options: Option<&FilterChainOptionsMetal>, ) -> error::Result { // load passes from preset @@ -79,14 +92,15 @@ impl FilterChainMetal { /// Load a filter chain from a pre-parsed `ShaderPreset`. pub fn load_from_preset( preset: ShaderPreset, - queue: Id>, + queue: &ProtocolObject, options: Option<&FilterChainOptionsMetal>, ) -> error::Result { let cmd = queue .commandBuffer() .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; - let filter_chain = Self::load_from_preset_deferred(preset, queue, cmd, options)?; + let filter_chain = + Self::load_from_preset_deferred_internal(preset, queue.device(), &cmd, options)?; cmd.commit(); unsafe { cmd.waitUntilCompleted() }; @@ -94,6 +108,117 @@ impl FilterChainMetal { Ok(filter_chain) } + fn load_luts( + device: &ProtocolObject, + cmd: &ProtocolObject, + textures: &[TextureConfig], + ) -> error::Result> { + let mut luts = FxHashMap::default(); + + let mipmapper = cmd + .blitCommandEncoder() + .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; + + let images = textures + .par_iter() + .map(|texture| Image::::load(&texture.path, UVDirection::TopLeft)) + .collect::>, ImageError>>()?; + for (index, (texture, image)) in textures.iter().zip(images).enumerate() { + let texture = LutTexture::new(device, &mipmapper, image, texture)?; + luts.insert(index, texture); + } + + mipmapper.endEncoding(); + Ok(luts) + } + + fn init_passes( + device: &Id>, + passes: Vec, + semantics: &ShaderSemantics, + ) -> error::Result> { + // todo: fix this to allow send + let filters: Vec> = passes + .into_iter() + .enumerate() + .map(|(index, (config, source, mut reflect))| { + let reflection = reflect.reflect(index, semantics)?; + let msl = reflect.compile(Some(MslVersion::V2_0))?; + + let ubo_size = reflection.ubo.as_ref().map_or(0, |ubo| ubo.size as usize); + let push_size = reflection + .push_constant + .as_ref() + .map_or(0, |push| push.size); + + let uniform_storage = UniformStorage::new_with_storage( + MetalBuffer::new(&device, ubo_size)?, + MetalBuffer::new(&device, push_size as usize)?, + ); + + let uniform_bindings = reflection.meta.create_binding_map(|param| param.offset()); + + let render_pass_format: MTLPixelFormat = + if let Some(format) = config.get_format_override() { + format.into() + } else { + source.format.into() + }; + + let graphics_pipeline = MetalGraphicsPipeline::new( + &device, + &msl, + if render_pass_format == 0 { + MTLPixelFormatRGBA8Unorm + } else { + render_pass_format + }, + )?; + + Ok(FilterPass { + reflection, + uniform_storage, + uniform_bindings, + source, + config, + graphics_pipeline, + }) + }) + .collect(); + // + let filters: error::Result> = filters.into_iter().collect(); + let filters = filters?; + Ok(filters.into_boxed_slice()) + } + + fn push_history( + &mut self, + input: &ProtocolObject, + cmd: &ProtocolObject, + ) -> error::Result<()> { + if let Some(mut back) = self.history_framebuffers.pop_back() { + if back.texture.height() != input.height() + || back.texture.width() != input.width() + || input.pixelFormat() != back.texture.pixelFormat() + { + let size = Size { + width: input.width() as u32, + height: input.height() as u32, + }; + + let _old_back = std::mem::replace( + &mut back, + OwnedTexture::new(&self.common.device, size, 1, input.pixelFormat())?, + ); + } + + back.copy_from(cmd, input)?; + + self.history_framebuffers.push_front(back); + } + Ok(()) + } + /// Load a filter chain from a pre-parsed `ShaderPreset`, deferring and GPU-side initialization /// to the caller. This function therefore requires no external synchronization of the device queue. /// @@ -103,13 +228,227 @@ impl FilterChainMetal { /// graphics queue. The command buffer must be completely executed before calling [`frame`](Self::frame). pub fn load_from_preset_deferred( preset: ShaderPreset, - queue: Id>, - cmd: Id>, + queue: &ProtocolObject, + cmd: &ProtocolObject, + options: Option<&FilterChainOptionsMetal>, + ) -> error::Result { + Self::load_from_preset_deferred_internal(preset, queue.device(), &cmd, options) + } + + /// Load a filter chain from a pre-parsed `ShaderPreset`, deferring and GPU-side initialization + /// to the caller. This function therefore requires no external synchronization of the device queue. + /// + /// ## Safety + /// The provided command buffer must be ready for recording. + /// The caller is responsible for ending the command buffer and immediately submitting it to a + /// graphics queue. The command buffer must be completely executed before calling [`frame`](Self::frame). + fn load_from_preset_deferred_internal( + preset: ShaderPreset, + device: Id>, + cmd: &ProtocolObject, options: Option<&FilterChainOptionsMetal>, ) -> error::Result { - let device = queue.device(); let (passes, semantics) = compile_passes(preset.shaders, &preset.textures)?; + let filters = Self::init_passes(&device, passes, &semantics)?; + let samplers = SamplerSet::new(&device)?; + let luts = FilterChainMetal::load_luts(&device, &cmd, &preset.textures)?; + let framebuffer_gen = || { + Ok::<_, error::FilterChainError>(OwnedTexture::new( + &device, + Size::new(1, 1), + 1, + ImageFormat::R8G8B8A8Unorm.into(), + )?) + }; + let input_gen = || None; + let framebuffer_init = FramebufferInit::new( + filters.iter().map(|f| &f.reflection.meta), + &framebuffer_gen, + &input_gen, + ); + let (output_framebuffers, output_textures) = framebuffer_init.init_output_framebuffers()?; + // + // initialize feedback framebuffers + let (feedback_framebuffers, feedback_textures) = + framebuffer_init.init_output_framebuffers()?; + // + // initialize history + let (history_framebuffers, history_textures) = framebuffer_init.init_history()?; + + let draw_quad = DrawQuad::new(&device)?; + Ok(FilterChainMetal { + common: FilterCommon { + luts, + samplers, + config: FilterMutable { + passes_enabled: preset.shader_count as usize, + parameters: preset + .parameters + .into_iter() + .map(|param| (param.name, param.value)) + .collect(), + }, + draw_quad, + device, + output_textures, + feedback_textures, + history_textures, + internal_frame_count: 0, + }, + passes: filters, + output_framebuffers, + feedback_framebuffers, + history_framebuffers, + disable_mipmaps: options.map(|f| f.force_no_mipmaps).unwrap_or(false), + }) + } + + /// Records shader rendering commands to the provided command encoder. + pub fn frame( + &mut self, + input: Id>, + viewport: &Viewport<&ProtocolObject>, + cmd_buffer: &ProtocolObject, + frame_count: usize, + options: Option<&FrameOptionsMetal>, + ) -> error::Result<()> { + let max = std::cmp::min(self.passes.len(), self.common.config.passes_enabled); + let passes = &mut self.passes[0..max]; + if let Some(options) = &options { + if options.clear_history { + for history in &mut self.history_framebuffers { + history.clear(cmd_buffer); + } + } + } + if passes.is_empty() { + return Ok(()); + } + + // let original_image_view = input.create_view(&wgpu::TextureViewDescriptor::default()); + + let filter = passes[0].config.filter; + let wrap_mode = passes[0].config.wrap_mode; + + // update history + for (texture, image) in self + .common + .history_textures + .iter_mut() + .zip(self.history_framebuffers.iter()) + { + *texture = Some(image.as_input(filter, wrap_mode)?); + } + + let original = InputTexture { + texture: input + .newTextureViewWithPixelFormat(input.pixelFormat()) + .ok_or(FilterChainError::FailedToCreateTexture)?, + wrap_mode, + filter_mode: filter, + mip_filter: filter, + }; + + let mut source = original.try_clone()?; + + // swap output and feedback **before** recording command buffers + std::mem::swap( + &mut self.output_framebuffers, + &mut self.feedback_framebuffers, + ); + + // rescale render buffers to ensure all bindings are valid. + OwnedTexture::scale_framebuffers_with_context( + get_texture_size(&source.texture).into(), + get_texture_size(viewport.output), + &mut self.output_framebuffers, + &mut self.feedback_framebuffers, + passes, + &self.common.device, + Some(&mut |index: usize, + pass: &FilterPass, + output: &OwnedTexture, + feedback: &OwnedTexture| { + // refresh inputs + self.common.feedback_textures[index] = + Some(feedback.as_input(pass.config.filter, pass.config.wrap_mode)?); + self.common.output_textures[index] = + Some(output.as_input(pass.config.filter, pass.config.wrap_mode)?); + Ok(()) + }), + )?; + + let passes_len = passes.len(); + let (pass, last) = passes.split_at_mut(passes_len - 1); + let frame_direction = options.map_or(1, |f| f.frame_direction); + + 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()); + pass.draw( + &cmd_buffer, + index, + &self.common, + pass.config.get_frame_count(frame_count), + frame_direction, + viewport, + &original, + &source, + &out, + QuadType::Offscreen, + )?; + + if target.max_miplevels > 1 && !self.disable_mipmaps { + target.generate_mipmaps(&mipmapper); + } + + source = self.common.output_textures[index] + .as_ref() + .map(InputTexture::try_clone) + .unwrap()?; + } + + // try to hint the optimizer + assert_eq!(last.len(), 1); + + if let Some(pass) = last.iter_mut().next() { + if pass.graphics_pipeline.render_pass_format != viewport.output.pixelFormat() { + // need to recompile + pass.graphics_pipeline + .recompile(&self.common.device, viewport.output.pixelFormat())?; + } + + source.filter_mode = pass.config.filter; + source.wrap_mode = pass.config.wrap_mode; + source.mip_filter = pass.config.filter; + let output_image = viewport.output; + let out = RenderTarget::viewport_with_output(output_image, viewport); + pass.draw( + &cmd_buffer, + passes_len - 1, + &self.common, + pass.config.get_frame_count(frame_count), + frame_direction, + viewport, + &original, + &source, + &out, + QuadType::Final, + )?; + } + + self.push_history(&input, &mipmapper)?; + 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 8bc1738..683f268 100644 --- a/librashader-runtime-metal/src/filter_pass.rs +++ b/librashader-runtime-metal/src/filter_pass.rs @@ -1 +1,168 @@ -pub struct FilterPass {} +use crate::buffer::MetalBuffer; +use crate::error; +use crate::filter_chain::FilterCommon; +use crate::graphics_pipeline::MetalGraphicsPipeline; +use crate::samplers::SamplerSet; +use crate::texture::{get_texture_size, InputTexture}; +use icrate::Metal::{MTLCommandBuffer, MTLCommandEncoder, MTLRenderCommandEncoder, MTLTexture}; +use librashader_common::{ImageFormat, Size, Viewport}; +use librashader_preprocess::ShaderSource; +use librashader_presets::ShaderPassConfig; +use librashader_reflect::reflect::semantics::{MemberOffset, TextureBinding, UniformBinding}; +use librashader_reflect::reflect::ShaderReflection; +use librashader_runtime::binding::{BindSemantics, TextureInput}; +use librashader_runtime::filter_pass::FilterPassMeta; +use librashader_runtime::quad::QuadType; +use librashader_runtime::render_target::RenderTarget; +use librashader_runtime::uniforms::{NoUniformBinder, UniformStorage}; +use objc2::runtime::ProtocolObject; +use rustc_hash::FxHashMap; + +impl TextureInput for InputTexture { + fn size(&self) -> Size { + get_texture_size(&self.texture) + } +} + +impl BindSemantics, MetalBuffer, MetalBuffer> for FilterPass { + type InputTexture = InputTexture; + type SamplerSet = SamplerSet; + type DescriptorSet<'a> = &'a ProtocolObject; + type DeviceContext = (); + type UniformOffset = MemberOffset; + + #[inline(always)] + fn bind_texture<'a>( + renderpass: &mut Self::DescriptorSet<'a>, + samplers: &Self::SamplerSet, + binding: &TextureBinding, + texture: &Self::InputTexture, + _device: &Self::DeviceContext, + ) { + 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); + } + } +} + +pub struct FilterPass { + pub reflection: ShaderReflection, + pub(crate) uniform_storage: + UniformStorage, MetalBuffer, MetalBuffer>, + pub uniform_bindings: FxHashMap, + pub source: ShaderSource, + pub config: ShaderPassConfig, + pub graphics_pipeline: MetalGraphicsPipeline, +} + +impl FilterPass { + pub(crate) fn draw( + &mut self, + cmd: &ProtocolObject, + pass_index: usize, + parent: &FilterCommon, + frame_count: u32, + frame_direction: i32, + viewport: &Viewport<&ProtocolObject>, + original: &InputTexture, + source: &InputTexture, + output: &RenderTarget>, + vbo_type: QuadType, + ) -> error::Result<()> { + let cmd = self.graphics_pipeline.begin_rendering(output, &cmd)?; + + self.build_semantics( + pass_index, + parent, + output.mvp, + frame_count, + frame_direction, + get_texture_size(output.output), + get_texture_size(viewport.output), + original, + source, + &cmd, + ); + + if let Some(ubo) = &self.reflection.ubo { + unsafe { + cmd.setVertexBuffer_offset_atIndex( + Some(self.uniform_storage.inner_ubo().as_ref()), + 0, + ubo.binding as usize, + ) + } + } + if let Some(pcb) = &self.reflection.push_constant { + unsafe { + // SPIRV-Cross always has PCB bound to 1. Naga is arbitrary but their compilation provides the next free binding for drawquad. + cmd.setVertexBuffer_offset_atIndex( + Some(self.uniform_storage.inner_ubo().as_ref()), + 0, + pcb.binding.unwrap_or(1) as usize, + ) + } + } + + parent.draw_quad.draw_quad(&cmd, vbo_type); + cmd.endEncoding(); + + Ok(()) + } + + fn build_semantics<'a>( + &mut self, + pass_index: usize, + parent: &FilterCommon, + mvp: &[f32; 16], + frame_count: u32, + frame_direction: i32, + fb_size: Size, + viewport_size: Size, + original: &InputTexture, + source: &InputTexture, + mut renderpass: &ProtocolObject, + ) { + Self::bind_semantics( + &(), + &parent.samplers, + &mut self.uniform_storage, + &mut renderpass, + mvp, + frame_count, + frame_direction, + fb_size, + viewport_size, + original, + source, + &self.uniform_bindings, + &self.reflection.meta.texture_meta, + parent.output_textures[0..pass_index] + .iter() + .map(|o| o.as_ref()), + parent.feedback_textures.iter().map(|o| o.as_ref()), + parent.history_textures.iter().map(|o| o.as_ref()), + parent.luts.iter().map(|(u, i)| (*u, i.as_ref())), + &self.source.parameters, + &parent.config.parameters, + ); + + // flush to buffers + self.uniform_storage.inner_ubo().flush(); + self.uniform_storage.inner_push().flush(); + } +} + +impl FilterPassMeta for FilterPass { + fn framebuffer_format(&self) -> ImageFormat { + self.source.format + } + + fn config(&self) -> &ShaderPassConfig { + &self.config + } +} diff --git a/librashader-runtime-metal/src/graphics_pipeline.rs b/librashader-runtime-metal/src/graphics_pipeline.rs index a594225..d0aa289 100644 --- a/librashader-runtime-metal/src/graphics_pipeline.rs +++ b/librashader-runtime-metal/src/graphics_pipeline.rs @@ -1,8 +1,8 @@ use crate::error::{FilterChainError, Result}; use icrate::Foundation::NSString; use icrate::Metal::{ - MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, - MTLCommandEncoder, MTLDevice, MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat, + MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, MTLDevice, + MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat, MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor, MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor, MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture, @@ -11,25 +11,27 @@ use icrate::Metal::{ }; use librashader_reflect::back::msl::{CrossMslContext, NagaMslContext}; use librashader_reflect::back::ShaderCompilerOutput; -use librashader_reflect::reflect::ShaderReflection; use librashader_runtime::render_target::RenderTarget; use objc2::rc::Id; use objc2::runtime::ProtocolObject; +/// 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; + pub struct MetalGraphicsPipeline { pub layout: PipelineLayoutObjects, render_pipeline: Id>, + pub render_pass_format: MTLPixelFormat, } pub struct PipelineLayoutObjects { - vertex_lib: Id>, - fragment_lib: Id>, + _vertex_lib: Id>, + _fragment_lib: Id>, vertex_entry: Id>, fragment_entry: Id>, - device: Id>, } -trait MslEntryPoint { +pub(crate) trait MslEntryPoint { fn entry_point() -> Id; } @@ -48,7 +50,7 @@ impl MslEntryPoint for NagaMslContext { impl PipelineLayoutObjects { pub fn new( shader_assembly: &ShaderCompilerOutput, - device: Id>, + device: &ProtocolObject, ) -> Result { let entry = T::entry_point(); @@ -65,11 +67,10 @@ impl PipelineLayoutObjects { .ok_or(FilterChainError::ShaderWrongEntryName)?; Ok(Self { - vertex_lib: vertex, - fragment_lib: fragment, + _vertex_lib: vertex, + _fragment_lib: fragment, vertex_entry, fragment_entry, - device, }) } @@ -85,11 +86,11 @@ impl PipelineLayoutObjects { // hopefully metal fills in vertices otherwise we'll need to use the vec4 stuff. vertex_0.setFormat(MTLVertexFormatFloat2); - vertex_0.setBufferIndex(4); + vertex_0.setBufferIndex(VERTEX_BUFFER_INDEX); vertex_0.setOffset(0); vertex_1.setFormat(MTLVertexFormatFloat2); - vertex_1.setBufferIndex(4); + vertex_1.setBufferIndex(VERTEX_BUFFER_INDEX); vertex_1.setOffset(2 * std::mem::size_of::()); attributes.setObject_atIndexedSubscript(Some(&vertex_0), 0); @@ -119,6 +120,7 @@ impl PipelineLayoutObjects { pub fn create_pipeline( &self, + device: &ProtocolObject, format: MTLPixelFormat, ) -> Result>> { let descriptor = MTLRenderPipelineDescriptor::new(); @@ -140,36 +142,39 @@ impl PipelineLayoutObjects { descriptor.setFragmentFunction(Some(&self.fragment_entry)); } - Ok(self - .device - .newRenderPipelineStateWithDescriptor_error(descriptor.as_ref())?) + Ok(device.newRenderPipelineStateWithDescriptor_error(descriptor.as_ref())?) } } impl MetalGraphicsPipeline { pub fn new( - device: Id>, + device: &ProtocolObject, shader_assembly: &ShaderCompilerOutput, render_pass_format: MTLPixelFormat, ) -> Result { let layout = PipelineLayoutObjects::new(shader_assembly, device)?; - let pipeline = layout.create_pipeline(render_pass_format)?; + let pipeline = layout.create_pipeline(device, render_pass_format)?; Ok(Self { layout, render_pipeline: pipeline, + render_pass_format, }) } - pub fn recompile(&mut self, format: MTLPixelFormat) -> Result<()> { - let render_pipeline = self.layout.create_pipeline(format)?; + pub fn recompile( + &mut self, + device: &ProtocolObject, + format: MTLPixelFormat, + ) -> Result<()> { + let render_pipeline = self.layout.create_pipeline(device, format)?; self.render_pipeline = render_pipeline; Ok(()) } pub fn begin_rendering<'pass>( &self, - output: RenderTarget<&'pass ProtocolObject>, - buffer: Id>, + output: &RenderTarget>, + buffer: &ProtocolObject, ) -> Result>> { unsafe { let descriptor = MTLRenderPassDescriptor::new(); diff --git a/librashader-runtime-metal/src/lib.rs b/librashader-runtime-metal/src/lib.rs index d02c2c8..5932176 100644 --- a/librashader-runtime-metal/src/lib.rs +++ b/librashader-runtime-metal/src/lib.rs @@ -1,11 +1,18 @@ #![cfg(target_vendor = "apple")] +#![feature(type_alias_impl_trait)] + mod buffer; mod draw_quad; -mod error; mod filter_chain; mod filter_pass; mod graphics_pipeline; mod luts; -mod options; mod samplers; mod texture; + +pub use filter_chain::FilterChainMetal; + +pub mod error; +pub mod options; +use librashader_runtime::impl_filter_chain_parameters; +impl_filter_chain_parameters!(FilterChainMetal); diff --git a/librashader-runtime-metal/src/luts.rs b/librashader-runtime-metal/src/luts.rs index 77d1d41..73fe6af 100644 --- a/librashader-runtime-metal/src/luts.rs +++ b/librashader-runtime-metal/src/luts.rs @@ -1,26 +1,29 @@ use crate::error::{FilterChainError, Result}; -use crate::samplers::SamplerSet; -use crate::texture::MetalTexture; +use crate::texture::InputTexture; use icrate::Metal::{ - MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLOrigin, - MTLPixelFormatBGRA8Unorm, MTLRegion, MTLSize, MTLTexture, MTLTextureDescriptor, - MTLTextureUsageShaderRead, + MTLBlitCommandEncoder, MTLDevice, MTLOrigin, MTLPixelFormatBGRA8Unorm, MTLRegion, MTLSize, + MTLTexture, MTLTextureDescriptor, MTLTextureUsageShaderRead, }; use librashader_presets::TextureConfig; use librashader_runtime::image::{Image, BGRA8}; use librashader_runtime::scaling::MipmapSize; -use objc2::rc::Id; use objc2::runtime::ProtocolObject; use std::ffi::c_void; use std::ptr::NonNull; -pub(crate) struct LutTexture(MetalTexture); +pub(crate) struct LutTexture(InputTexture); + +impl AsRef for LutTexture { + fn as_ref(&self) -> &InputTexture { + self.0.as_ref() + } +} impl LutTexture { pub fn new( device: &ProtocolObject, + mipmapper: &ProtocolObject, image: Image, - cmd: &ProtocolObject, config: &TextureConfig, ) -> Result { let descriptor = unsafe { @@ -68,12 +71,14 @@ impl LutTexture { } if config.mipmap { - if let Some(encoder) = cmd.blitCommandEncoder() { - encoder.generateMipmapsForTexture(&texture); - encoder.endEncoding(); - } + mipmapper.generateMipmapsForTexture(&texture); } - Ok(LutTexture(texture)) + Ok(LutTexture(InputTexture { + texture, + wrap_mode: config.wrap_mode, + filter_mode: config.filter_mode, + mip_filter: config.filter_mode, + })) } } diff --git a/librashader-runtime-metal/src/texture.rs b/librashader-runtime-metal/src/texture.rs index 04e196a..54bb9cc 100644 --- a/librashader-runtime-metal/src/texture.rs +++ b/librashader-runtime-metal/src/texture.rs @@ -1,33 +1,56 @@ use crate::error::{FilterChainError, Result}; use icrate::Metal::{ - MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLPixelFormat, - MTLPixelFormatBGRA8Unorm, MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget, - MTLTextureUsageShaderRead, MTLTextureUsageShaderWrite, + MTLBlitCommandEncoder, MTLCommandBuffer, MTLDevice, MTLPixelFormat, MTLTexture, + MTLTextureDescriptor, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead, + MTLTextureUsageShaderWrite, }; use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; use librashader_presets::Scale2D; -use librashader_runtime::scaling::{MipmapSize, ViewportSize}; +use librashader_runtime::scaling::{MipmapSize, ScaleFramebuffer, ViewportSize}; use objc2::rc::Id; use objc2::runtime::ProtocolObject; -use std::sync::Arc; pub type MetalTexture = Id>; -pub struct OwnedImage { - image: MetalTexture, - max_miplevels: u32, +pub struct OwnedTexture { + pub(crate) texture: MetalTexture, + pub(crate) max_miplevels: u32, size: Size, } -impl OwnedImage { +pub struct InputTexture { + pub texture: MetalTexture, + pub wrap_mode: WrapMode, + pub filter_mode: FilterMode, + pub mip_filter: FilterMode, +} + +impl InputTexture { + pub fn try_clone(&self) -> Result { + Ok(Self { + texture: self + .texture + .newTextureViewWithPixelFormat(self.texture.pixelFormat()) + .ok_or(FilterChainError::FailedToCreateTexture)?, + wrap_mode: self.wrap_mode, + filter_mode: self.filter_mode, + mip_filter: self.mip_filter, + }) + } +} +impl AsRef for InputTexture { + fn as_ref(&self) -> &InputTexture { + &self + } +} + +impl OwnedTexture { pub fn new( device: &ProtocolObject, size: Size, max_miplevels: u32, - format: ImageFormat, + format: MTLPixelFormat, ) -> Result { - let format: MTLPixelFormat = format.into(); - let descriptor = unsafe { let descriptor = MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped( @@ -54,7 +77,7 @@ impl OwnedImage { }; Ok(Self { - image: device + texture: device .newTextureWithDescriptor(&descriptor) .ok_or(FilterChainError::FailedToCreateTexture)?, max_miplevels, @@ -66,52 +89,50 @@ impl OwnedImage { &mut self, device: &ProtocolObject, scaling: Scale2D, - format: ImageFormat, + format: MTLPixelFormat, viewport_size: &Size, source_size: &Size, mipmap: bool, - ) -> Size { + ) -> Result> { let size = source_size.scale_viewport(scaling, *viewport_size); - let format: MTLPixelFormat = format.into(); if self.size != size || (mipmap && self.max_miplevels == 1) || (!mipmap && self.max_miplevels != 1) - || format != self.image.pixelFormat() + || format != self.texture.pixelFormat() { - let mut new = OwnedImage::new(device, size, self.max_miplevels, format.into())?; + let mut new = OwnedTexture::new(device, size, self.max_miplevels, format)?; std::mem::swap(self, &mut new); } - size + Ok(size) } - // pub(crate) fn as_input(&self, filter: FilterMode, wrap_mode: WrapMode) -> InputImage { - // InputImage { - // image: Arc::clone(&self.image), - // view: Arc::clone(&self.view), - // wrap_mode, - // filter_mode: filter, - // mip_filter: filter, - // } - // } + pub(crate) fn as_input(&self, filter: FilterMode, wrap_mode: WrapMode) -> Result { + Ok(InputTexture { + texture: self + .texture + .newTextureViewWithPixelFormat(self.texture.pixelFormat()) + .ok_or(FilterChainError::FailedToCreateTexture)?, + wrap_mode, + filter_mode: filter, + mip_filter: filter, + }) + } pub fn copy_from( &self, + encoder: &ProtocolObject, other: &ProtocolObject, - cmd: Id>, ) -> Result<()> { - let encoder = cmd - .blitCommandEncoder() - .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; unsafe { - encoder.copyFromTexture_toTexture(other, &self.image); + encoder.copyFromTexture_toTexture(other, &self.texture); } - encoder.generateMipmapsForTexture(&self.image); - encoder.endEncoding(); + encoder.generateMipmapsForTexture(&self.texture); + Ok(()) } - pub fn clear(&self, cmd: Id>) { + pub fn clear(&self, cmd: &ProtocolObject) { // let render = cmd.renderCommandEncoder() // .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; // render. @@ -120,6 +141,39 @@ impl OwnedImage { /// caller must end the blit encoder after. pub fn generate_mipmaps(&self, mipmapper: &ProtocolObject) { - mipmapper.generateMipmapsForTexture(&self.image); + mipmapper.generateMipmapsForTexture(&self.texture); + } +} + +impl ScaleFramebuffer for OwnedTexture { + type Error = FilterChainError; + type Context = ProtocolObject; + + fn scale( + &mut self, + scaling: Scale2D, + format: ImageFormat, + viewport_size: &Size, + source_size: &Size, + should_mipmap: bool, + context: &Self::Context, + ) -> std::result::Result, Self::Error> { + Ok(self.scale( + &context, + scaling, + format.into(), + viewport_size, + source_size, + should_mipmap, + )?) + } +} + +pub(crate) fn get_texture_size(texture: &ProtocolObject) -> Size { + let height = texture.height(); + let width = texture.width(); + Size { + height: height as u32, + width: width as u32, } }