From ba3154b92df753eebe87085f3f704ca715e4f8cd Mon Sep 17 00:00:00 2001 From: chyyran Date: Sun, 11 Feb 2024 17:54:13 -0500 Subject: [PATCH] rt(mtl): implement texture and buffer abstractions --- librashader-common/src/lib.rs | 1 - librashader-common/src/metal.rs | 5 +- librashader-runtime-metal/Cargo.toml | 2 +- librashader-runtime-metal/src/buffer.rs | 54 ++++++++ librashader-runtime-metal/src/draw_quad.rs | 28 ++-- librashader-runtime-metal/src/error.rs | 6 +- librashader-runtime-metal/src/filter_chain.rs | 115 ++++++++++++++++ librashader-runtime-metal/src/filter_pass.rs | 1 + .../src/graphics_pipeline.rs | 8 +- librashader-runtime-metal/src/lib.rs | 6 + librashader-runtime-metal/src/luts.rs | 79 +++++++++++ librashader-runtime-metal/src/options.rs | 20 +++ librashader-runtime-metal/src/texture.rs | 125 ++++++++++++++++++ librashader-runtime/src/parameters.rs | 1 - 14 files changed, 425 insertions(+), 26 deletions(-) create mode 100644 librashader-runtime-metal/src/buffer.rs create mode 100644 librashader-runtime-metal/src/filter_chain.rs create mode 100644 librashader-runtime-metal/src/filter_pass.rs create mode 100644 librashader-runtime-metal/src/luts.rs create mode 100644 librashader-runtime-metal/src/options.rs create mode 100644 librashader-runtime-metal/src/texture.rs diff --git a/librashader-common/src/lib.rs b/librashader-common/src/lib.rs index bf32ff0..653d8c7 100644 --- a/librashader-common/src/lib.rs +++ b/librashader-common/src/lib.rs @@ -29,7 +29,6 @@ pub mod metal; mod viewport; - pub use viewport::Viewport; use num_traits::AsPrimitive; diff --git a/librashader-common/src/metal.rs b/librashader-common/src/metal.rs index 408bde6..2e7d353 100644 --- a/librashader-common/src/metal.rs +++ b/librashader-common/src/metal.rs @@ -1,5 +1,5 @@ +use crate::{FilterMode, ImageFormat, Size, WrapMode}; use icrate::Metal; -use crate::{Size, ImageFormat, FilterMode, WrapMode}; impl From for Metal::MTLPixelFormat { fn from(format: ImageFormat) -> Self { @@ -61,7 +61,6 @@ impl From> for Metal::MTLViewport { } } - impl From for Metal::MTLSamplerAddressMode { fn from(value: WrapMode) -> Self { match value { @@ -91,5 +90,3 @@ impl FilterMode { } } } - - diff --git a/librashader-runtime-metal/Cargo.toml b/librashader-runtime-metal/Cargo.toml index 8ddee05..f8d20f7 100644 --- a/librashader-runtime-metal/Cargo.toml +++ b/librashader-runtime-metal/Cargo.toml @@ -24,7 +24,7 @@ librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta. rustc-hash = "1.1.0" thiserror = "1.0" array-concat = "0.5.2" -bytemuck = "1.14.3" +bytemuck = { version = "1.12.3", features = ["derive"] } [dev-dependencies] diff --git a/librashader-runtime-metal/src/buffer.rs b/librashader-runtime-metal/src/buffer.rs new file mode 100644 index 0000000..b1bb5f5 --- /dev/null +++ b/librashader-runtime-metal/src/buffer.rs @@ -0,0 +1,54 @@ +use crate::error::FilterChainError; +use icrate::Foundation::NSRange; +use icrate::Metal::{ + MTLBuffer, MTLDevice, MTLResourceStorageModeManaged, MTLResourceStorageModeShared, +}; +use objc2::rc::Id; +use objc2::runtime::ProtocolObject; +use std::ops::{Deref, DerefMut}; + +pub struct MetalBuffer { + buffer: Id>, + size: usize, +} + +impl MetalBuffer { + pub fn new(device: &ProtocolObject, size: usize) -> Result { + let resource_mode = if cfg!(target_os = "ios") { + MTLResourceStorageModeShared + } else { + MTLResourceStorageModeManaged + }; + + let buffer = device + .newBufferWithLength_options(size, resource_mode) + .ok_or(FilterChainError::BufferError)?; + Ok(Self { buffer, size }) + } + + pub fn flush(&self) { + // We don't know what was actually written to so... + self.buffer.didModifyRange(NSRange { + location: 0, + length: self.size, + }) + } +} + +impl Deref for MetalBuffer { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + // SAFETY: the lifetime of this reference must be longer than of the MetalBuffer. + // Additionally, `MetalBuffer.buffer` is never lent out directly + unsafe { std::slice::from_raw_parts(self.buffer.contents().as_ptr().cast(), self.size) } + } +} + +impl DerefMut for MetalBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: the lifetime of this reference must be longer than of the MetalBuffer. + // Additionally, `MetalBuffer.buffer` is never lent out directly + unsafe { std::slice::from_raw_parts_mut(self.buffer.contents().as_ptr().cast(), self.size) } + } +} diff --git a/librashader-runtime-metal/src/draw_quad.rs b/librashader-runtime-metal/src/draw_quad.rs index 5ad9efa..463d347 100644 --- a/librashader-runtime-metal/src/draw_quad.rs +++ b/librashader-runtime-metal/src/draw_quad.rs @@ -65,20 +65,20 @@ pub struct DrawQuad { impl DrawQuad { pub fn new(device: &ProtocolObject) -> Result { let vbo_data: &'static [u8] = bytemuck::cast_slice(&VBO_DATA); - let Some(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 - }, - ) - }) else { - return Err(FilterChainError::BufferError); + 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)? }; Ok(DrawQuad { buffer }) diff --git a/librashader-runtime-metal/src/error.rs b/librashader-runtime-metal/src/error.rs index 1781a73..4d9c7b3 100644 --- a/librashader-runtime-metal/src/error.rs +++ b/librashader-runtime-metal/src/error.rs @@ -26,11 +26,15 @@ pub enum FilterChainError { #[error("buffer creation error")] BufferError, #[error("metal error")] - MetalError(Id), + MetalError(#[from] Id), #[error("couldn't find entry for shader")] ShaderWrongEntryName, #[error("couldn't create render pass")] FailedToCreateRenderPass, + #[error("couldn't create texture")] + FailedToCreateTexture, + #[error("couldn't create command buffer")] + FailedToCreateCommandBuffer, } /// Result type for Metal filter chains. diff --git a/librashader-runtime-metal/src/filter_chain.rs b/librashader-runtime-metal/src/filter_chain.rs new file mode 100644 index 0000000..33269c3 --- /dev/null +++ b/librashader-runtime-metal/src/filter_chain.rs @@ -0,0 +1,115 @@ +use crate::draw_quad::DrawQuad; +use crate::error; +use crate::error::FilterChainError; +use crate::filter_pass::FilterPass; +use crate::luts::LutTexture; +use crate::options::FilterChainOptionsMetal; +use crate::samplers::SamplerSet; +use crate::texture::{MetalTexture, OwnedImage}; +use icrate::Metal::{MTLCommandBuffer, MTLCommandQueue, MTLDevice}; +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::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 objc2::rc::Id; +use objc2::runtime::ProtocolObject; +use rustc_hash::FxHashMap; +use std::collections::VecDeque; +use std::path::Path; +use std::sync::Arc; + +type ShaderPassMeta = + ShaderPassArtifact + Send>; +fn compile_passes( + shaders: Vec, + textures: &[TextureConfig], +) -> Result<(Vec, ShaderSemantics), FilterChainError> { + let (passes, semantics) = + MSL::compile_preset_passes::( + shaders, &textures, + )?; + Ok((passes, semantics)) +} + +/// A wgpu filter chain. +pub struct FilterChainMetal { + pub(crate) common: FilterCommon, + passes: Box<[FilterPass]>, + output_framebuffers: Box<[OwnedImage]>, + feedback_framebuffers: Box<[OwnedImage]>, + history_framebuffers: VecDeque, + disable_mipmaps: bool, +} + +pub struct FilterMutable { + pub passes_enabled: usize, + pub(crate) parameters: FxHashMap, +} + +pub(crate) struct FilterCommon { + 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>, + options: Option<&FilterChainOptionsMetal>, + ) -> error::Result { + // load passes from preset + let preset = ShaderPreset::try_parse_with_driver_context(path, VideoDriver::Metal)?; + Self::load_from_preset(preset, queue, options) + } + + /// Load a filter chain from a pre-parsed `ShaderPreset`. + pub fn load_from_preset( + preset: ShaderPreset, + queue: Id>, + 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)?; + + cmd.commit(); + unsafe { cmd.waitUntilCompleted() }; + + Ok(filter_chain) + } + + /// 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). + pub fn load_from_preset_deferred( + preset: ShaderPreset, + queue: Id>, + cmd: Id>, + options: Option<&FilterChainOptionsMetal>, + ) -> error::Result { + let device = queue.device(); + let (passes, semantics) = compile_passes(preset.shaders, &preset.textures)?; + + let samplers = SamplerSet::new(&device)?; + } +} diff --git a/librashader-runtime-metal/src/filter_pass.rs b/librashader-runtime-metal/src/filter_pass.rs new file mode 100644 index 0000000..8bc1738 --- /dev/null +++ b/librashader-runtime-metal/src/filter_pass.rs @@ -0,0 +1 @@ +pub struct FilterPass {} diff --git a/librashader-runtime-metal/src/graphics_pipeline.rs b/librashader-runtime-metal/src/graphics_pipeline.rs index 529467d..a594225 100644 --- a/librashader-runtime-metal/src/graphics_pipeline.rs +++ b/librashader-runtime-metal/src/graphics_pipeline.rs @@ -30,7 +30,7 @@ pub struct PipelineLayoutObjects { } trait MslEntryPoint { - fn entry_point() -> NSString; + fn entry_point() -> Id; } impl MslEntryPoint for CrossMslContext { @@ -112,7 +112,7 @@ impl PipelineLayoutObjects { ca.setSourceAlphaBlendFactor(MTLBlendFactorSourceAlpha); ca.setSourceRGBBlendFactor(MTLBlendFactorSourceAlpha); ca.setDestinationAlphaBlendFactor(MTLBlendFactorOneMinusSourceAlpha); - ca.setDetinationRGBBlendFactor(MTLBlendFactorOneMinusSourceAlpha); + ca.setDestinationRGBBlendFactor(MTLBlendFactorOneMinusSourceAlpha); ca } @@ -153,10 +153,10 @@ impl MetalGraphicsPipeline { render_pass_format: MTLPixelFormat, ) -> Result { let layout = PipelineLayoutObjects::new(shader_assembly, device)?; - + let pipeline = layout.create_pipeline(render_pass_format)?; Ok(Self { layout, - render_pipeline: layout.create_pipeline(render_pass_format)?, + render_pipeline: pipeline, }) } diff --git a/librashader-runtime-metal/src/lib.rs b/librashader-runtime-metal/src/lib.rs index debc419..d02c2c8 100644 --- a/librashader-runtime-metal/src/lib.rs +++ b/librashader-runtime-metal/src/lib.rs @@ -1,5 +1,11 @@ #![cfg(target_vendor = "apple")] +mod buffer; mod draw_quad; mod error; +mod filter_chain; +mod filter_pass; mod graphics_pipeline; +mod luts; +mod options; mod samplers; +mod texture; diff --git a/librashader-runtime-metal/src/luts.rs b/librashader-runtime-metal/src/luts.rs new file mode 100644 index 0000000..77d1d41 --- /dev/null +++ b/librashader-runtime-metal/src/luts.rs @@ -0,0 +1,79 @@ +use crate::error::{FilterChainError, Result}; +use crate::samplers::SamplerSet; +use crate::texture::MetalTexture; +use icrate::Metal::{ + MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, 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); + +impl LutTexture { + pub fn new( + device: &ProtocolObject, + image: Image, + cmd: &ProtocolObject, + config: &TextureConfig, + ) -> Result { + let descriptor = unsafe { + let descriptor = + MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped( + MTLPixelFormatBGRA8Unorm, + image.size.width as usize, + image.size.height as usize, + config.mipmap, + ); + + descriptor.setSampleCount(1); + descriptor.setMipmapLevelCount(if config.mipmap { + image.size.calculate_miplevels() as usize + } else { + 1 + }); + + descriptor.setUsage(MTLTextureUsageShaderRead); + + descriptor + }; + + let texture = device + .newTextureWithDescriptor(&descriptor) + .ok_or(FilterChainError::FailedToCreateTexture)?; + + unsafe { + let region = MTLRegion { + origin: MTLOrigin { x: 0, y: 0, z: 0 }, + size: MTLSize { + width: image.size.width as usize, + height: image.size.height as usize, + depth: 1, + }, + }; + + texture.replaceRegion_mipmapLevel_withBytes_bytesPerRow( + region, + 0, + // SAFETY: replaceRegion withBytes is const. + NonNull::new_unchecked(image.bytes.as_slice().as_ptr() as *mut c_void), + 4 * image.size.width as usize, + ) + } + + if config.mipmap { + if let Some(encoder) = cmd.blitCommandEncoder() { + encoder.generateMipmapsForTexture(&texture); + encoder.endEncoding(); + } + } + + Ok(LutTexture(texture)) + } +} diff --git a/librashader-runtime-metal/src/options.rs b/librashader-runtime-metal/src/options.rs new file mode 100644 index 0000000..0f585db --- /dev/null +++ b/librashader-runtime-metal/src/options.rs @@ -0,0 +1,20 @@ +//! Metal shader runtime options. + +/// Options for each Vulkan shader frame. +#[repr(C)] +#[derive(Default, Debug, Clone)] +pub struct FrameOptionsMetal { + /// Whether or not to clear the history buffers. + pub clear_history: bool, + /// The direction of rendering. + /// -1 indicates that the frames are played in reverse order. + pub frame_direction: i32, +} + +/// Options for filter chain creation. +#[repr(C)] +#[derive(Default, Debug, Clone)] +pub struct FilterChainOptionsMetal { + /// Whether or not to explicitly disable mipmap generation regardless of shader preset settings. + pub force_no_mipmaps: bool, +} diff --git a/librashader-runtime-metal/src/texture.rs b/librashader-runtime-metal/src/texture.rs new file mode 100644 index 0000000..04e196a --- /dev/null +++ b/librashader-runtime-metal/src/texture.rs @@ -0,0 +1,125 @@ +use crate::error::{FilterChainError, Result}; +use icrate::Metal::{ + MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLPixelFormat, + MTLPixelFormatBGRA8Unorm, MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget, + MTLTextureUsageShaderRead, MTLTextureUsageShaderWrite, +}; +use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; +use librashader_presets::Scale2D; +use librashader_runtime::scaling::{MipmapSize, 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, + size: Size, +} + +impl OwnedImage { + pub fn new( + device: &ProtocolObject, + size: Size, + max_miplevels: u32, + format: ImageFormat, + ) -> Result { + let format: MTLPixelFormat = format.into(); + + let descriptor = unsafe { + let descriptor = + MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped( + format, + size.width as usize, + size.height as usize, + max_miplevels <= 1, + ); + + descriptor.setSampleCount(1); + descriptor.setMipmapLevelCount(if max_miplevels <= 1 { + size.calculate_miplevels() as usize + } else { + 1 + }); + + descriptor.setUsage( + MTLTextureUsageShaderRead + | MTLTextureUsageShaderWrite + | MTLTextureUsageRenderTarget, + ); + + descriptor + }; + + Ok(Self { + image: device + .newTextureWithDescriptor(&descriptor) + .ok_or(FilterChainError::FailedToCreateTexture)?, + max_miplevels, + size, + }) + } + + pub fn scale( + &mut self, + device: &ProtocolObject, + scaling: Scale2D, + format: ImageFormat, + viewport_size: &Size, + source_size: &Size, + mipmap: bool, + ) -> Size { + 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() + { + let mut new = OwnedImage::new(device, size, self.max_miplevels, format.into())?; + std::mem::swap(self, &mut new); + } + 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 fn copy_from( + &self, + other: &ProtocolObject, + cmd: Id>, + ) -> Result<()> { + let encoder = cmd + .blitCommandEncoder() + .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; + unsafe { + encoder.copyFromTexture_toTexture(other, &self.image); + } + encoder.generateMipmapsForTexture(&self.image); + encoder.endEncoding(); + Ok(()) + } + + pub fn clear(&self, cmd: Id>) { + // let render = cmd.renderCommandEncoder() + // .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; + // render. + // 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.image); + } +} diff --git a/librashader-runtime/src/parameters.rs b/librashader-runtime/src/parameters.rs index b815ef2..801c7d4 100644 --- a/librashader-runtime/src/parameters.rs +++ b/librashader-runtime/src/parameters.rs @@ -59,4 +59,3 @@ macro_rules! impl_filter_chain_parameters { } }; } -