rt(mtl): implement filter pass and filter chain logic

This commit is contained in:
chyyran 2024-02-11 20:38:55 -05:00 committed by Ronny Chan
parent ba3154b92d
commit a7b1682a37
12 changed files with 689 additions and 98 deletions

1
Cargo.lock generated
View file

@ -1737,6 +1737,7 @@ dependencies = [
"librashader-reflect", "librashader-reflect",
"librashader-runtime", "librashader-runtime",
"objc2 0.5.0", "objc2 0.5.0",
"rayon",
"rustc-hash", "rustc-hash",
"thiserror", "thiserror",
] ]

View file

@ -50,6 +50,7 @@ pub struct NagaMslModule {
pub struct NagaMslContext { pub struct NagaMslContext {
pub vertex: NagaMslModule, pub vertex: NagaMslModule,
pub fragment: NagaMslModule, pub fragment: NagaMslModule,
pub next_free_binding: u32,
} }
impl FromCompilation<SpirvCompilation, Naga> for MSL { impl FromCompilation<SpirvCompilation, Naga> for MSL {

View file

@ -136,6 +136,8 @@ impl CompileShader<MSL> for NagaReflect {
let fragment = write_msl(&self.fragment, frag_options)?; let fragment = write_msl(&self.fragment, frag_options)?;
let vertex = write_msl(&self.vertex, vert_options)?; let vertex = write_msl(&self.vertex, vert_options)?;
let vertex_binding = self.get_next_binding(0);
Ok(ShaderCompilerOutput { Ok(ShaderCompilerOutput {
vertex: vertex.0, vertex: vertex.0,
fragment: fragment.0, fragment: fragment.0,
@ -148,6 +150,7 @@ impl CompileShader<MSL> for NagaReflect {
translation_info: vertex.1, translation_info: vertex.1,
module: self.vertex, module: self.vertex,
}, },
next_free_binding: vertex_binding
}, },
}) })
} }

View file

@ -25,6 +25,7 @@ rustc-hash = "1.1.0"
thiserror = "1.0" thiserror = "1.0"
array-concat = "0.5.2" array-concat = "0.5.2"
bytemuck = { version = "1.12.3", features = ["derive"] } bytemuck = { version = "1.12.3", features = ["derive"] }
rayon = "1.8.1"
[dev-dependencies] [dev-dependencies]

View file

@ -1,3 +1,4 @@
use crate::error;
use crate::error::FilterChainError; use crate::error::FilterChainError;
use icrate::Foundation::NSRange; use icrate::Foundation::NSRange;
use icrate::Metal::{ use icrate::Metal::{
@ -12,8 +13,14 @@ pub struct MetalBuffer {
size: usize, size: usize,
} }
impl AsRef<ProtocolObject<dyn MTLBuffer>> for MetalBuffer {
fn as_ref(&self) -> &ProtocolObject<dyn MTLBuffer> {
self.buffer.as_ref()
}
}
impl MetalBuffer { impl MetalBuffer {
pub fn new(device: &ProtocolObject<dyn MTLDevice>, size: usize) -> Result<Self> { pub fn new(device: &ProtocolObject<dyn MTLDevice>, size: usize) -> error::Result<Self> {
let resource_mode = if cfg!(target_os = "ios") { let resource_mode = if cfg!(target_os = "ios") {
MTLResourceStorageModeShared MTLResourceStorageModeShared
} else { } else {

View file

@ -11,6 +11,7 @@ use std::ffi::c_void;
use std::ptr::NonNull; use std::ptr::NonNull;
use crate::error::{FilterChainError, Result}; use crate::error::{FilterChainError, Result};
use crate::graphics_pipeline::VERTEX_BUFFER_INDEX;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone, Default, Zeroable, Pod)] #[derive(Debug, Copy, Clone, Default, Zeroable, Pod)]
@ -92,7 +93,7 @@ impl DrawQuad {
}; };
unsafe { 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); cmd.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangleStrip, offset, 4);
} }
} }

View file

@ -1,27 +1,41 @@
use crate::buffer::MetalBuffer;
use crate::draw_quad::DrawQuad; use crate::draw_quad::DrawQuad;
use crate::error; use crate::error;
use crate::error::FilterChainError; use crate::error::FilterChainError;
use crate::filter_pass::FilterPass; use crate::filter_pass::FilterPass;
use crate::graphics_pipeline::MetalGraphicsPipeline;
use crate::luts::LutTexture; use crate::luts::LutTexture;
use crate::options::FilterChainOptionsMetal; use crate::options::{FilterChainOptionsMetal, FrameOptionsMetal};
use crate::samplers::SamplerSet; use crate::samplers::SamplerSet;
use crate::texture::{MetalTexture, OwnedImage}; use crate::texture::{get_texture_size, InputTexture, OwnedTexture};
use icrate::Metal::{MTLCommandBuffer, MTLCommandQueue, MTLDevice}; 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::context::VideoDriver;
use librashader_presets::{ShaderPassConfig, ShaderPreset, TextureConfig}; use librashader_presets::{ShaderPassConfig, ShaderPreset, TextureConfig};
use librashader_reflect::back::targets::{MSL, WGSL}; use librashader_reflect::back::msl::MslVersion;
use librashader_reflect::back::CompileReflectShader; use librashader_reflect::back::targets::MSL;
use librashader_reflect::back::{CompileReflectShader, CompileShader};
use librashader_reflect::front::{Glslang, SpirvCompilation}; use librashader_reflect::front::{Glslang, SpirvCompilation};
use librashader_reflect::reflect::cross::SpirvCross; use librashader_reflect::reflect::cross::SpirvCross;
use librashader_reflect::reflect::naga::Naga;
use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact}; use librashader_reflect::reflect::presets::{CompilePresetTarget, ShaderPassArtifact};
use librashader_reflect::reflect::semantics::ShaderSemantics; 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::rc::Id;
use objc2::runtime::ProtocolObject; use objc2::runtime::ProtocolObject;
use rayon::prelude::*;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
type ShaderPassMeta = type ShaderPassMeta =
ShaderPassArtifact<impl CompileReflectShader<MSL, SpirvCompilation, SpirvCross> + Send>; ShaderPassArtifact<impl CompileReflectShader<MSL, SpirvCompilation, SpirvCross> + Send>;
@ -36,13 +50,13 @@ fn compile_passes(
Ok((passes, semantics)) Ok((passes, semantics))
} }
/// A wgpu filter chain. /// A Metal filter chain.
pub struct FilterChainMetal { pub struct FilterChainMetal {
pub(crate) common: FilterCommon, pub(crate) common: FilterCommon,
passes: Box<[FilterPass]>, passes: Box<[FilterPass]>,
output_framebuffers: Box<[OwnedImage]>, output_framebuffers: Box<[OwnedTexture]>,
feedback_framebuffers: Box<[OwnedImage]>, feedback_framebuffers: Box<[OwnedTexture]>,
history_framebuffers: VecDeque<OwnedImage>, history_framebuffers: VecDeque<OwnedTexture>,
disable_mipmaps: bool, disable_mipmaps: bool,
} }
@ -52,23 +66,22 @@ pub struct FilterMutable {
} }
pub(crate) struct FilterCommon { pub(crate) struct FilterCommon {
pub output_textures: Box<[Option<MetalTexture>]>, pub output_textures: Box<[Option<InputTexture>]>,
pub feedback_textures: Box<[Option<MetalTexture>]>, pub feedback_textures: Box<[Option<InputTexture>]>,
pub history_textures: Box<[Option<MetalTexture>]>, pub history_textures: Box<[Option<InputTexture>]>,
pub luts: FxHashMap<usize, LutTexture>, pub luts: FxHashMap<usize, LutTexture>,
pub samplers: SamplerSet, pub samplers: SamplerSet,
pub config: FilterMutable, pub config: FilterMutable,
pub internal_frame_count: i32, pub internal_frame_count: i32,
pub(crate) draw_quad: DrawQuad, pub(crate) draw_quad: DrawQuad,
device: Id<ProtocolObject<dyn MTLDevice>>, device: Id<ProtocolObject<dyn MTLDevice>>,
queue: Id<ProtocolObject<dyn MTLCommandQueue>>,
} }
impl FilterChainMetal { impl FilterChainMetal {
/// Load the shader preset at the given path into a filter chain. /// Load the shader preset at the given path into a filter chain.
pub fn load_from_path( pub fn load_from_path(
path: impl AsRef<Path>, path: impl AsRef<Path>,
queue: Id<ProtocolObject<dyn MTLCommandQueue>>, queue: &ProtocolObject<dyn MTLCommandQueue>,
options: Option<&FilterChainOptionsMetal>, options: Option<&FilterChainOptionsMetal>,
) -> error::Result<FilterChainMetal> { ) -> error::Result<FilterChainMetal> {
// load passes from preset // load passes from preset
@ -79,14 +92,15 @@ impl FilterChainMetal {
/// Load a filter chain from a pre-parsed `ShaderPreset`. /// Load a filter chain from a pre-parsed `ShaderPreset`.
pub fn load_from_preset( pub fn load_from_preset(
preset: ShaderPreset, preset: ShaderPreset,
queue: Id<ProtocolObject<dyn MTLCommandQueue>>, queue: &ProtocolObject<dyn MTLCommandQueue>,
options: Option<&FilterChainOptionsMetal>, options: Option<&FilterChainOptionsMetal>,
) -> error::Result<FilterChainMetal> { ) -> error::Result<FilterChainMetal> {
let cmd = queue let cmd = queue
.commandBuffer() .commandBuffer()
.ok_or(FilterChainError::FailedToCreateCommandBuffer)?; .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(); cmd.commit();
unsafe { cmd.waitUntilCompleted() }; unsafe { cmd.waitUntilCompleted() };
@ -94,6 +108,117 @@ impl FilterChainMetal {
Ok(filter_chain) Ok(filter_chain)
} }
fn load_luts(
device: &ProtocolObject<dyn MTLDevice>,
cmd: &ProtocolObject<dyn MTLCommandBuffer>,
textures: &[TextureConfig],
) -> error::Result<FxHashMap<usize, LutTexture>> {
let mut luts = FxHashMap::default();
let mipmapper = cmd
.blitCommandEncoder()
.ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
let images = textures
.par_iter()
.map(|texture| Image::<BGRA8>::load(&texture.path, UVDirection::TopLeft))
.collect::<Result<Vec<Image<BGRA8>>, 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<ProtocolObject<dyn MTLDevice>>,
passes: Vec<ShaderPassMeta>,
semantics: &ShaderSemantics,
) -> error::Result<Box<[FilterPass]>> {
// todo: fix this to allow send
let filters: Vec<error::Result<FilterPass>> = 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<Vec<FilterPass>> = filters.into_iter().collect();
let filters = filters?;
Ok(filters.into_boxed_slice())
}
fn push_history(
&mut self,
input: &ProtocolObject<dyn MTLTexture>,
cmd: &ProtocolObject<dyn MTLBlitCommandEncoder>,
) -> 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 /// 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. /// 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). /// graphics queue. The command buffer must be completely executed before calling [`frame`](Self::frame).
pub fn load_from_preset_deferred( pub fn load_from_preset_deferred(
preset: ShaderPreset, preset: ShaderPreset,
queue: Id<ProtocolObject<dyn MTLCommandQueue>>, queue: &ProtocolObject<dyn MTLCommandQueue>,
cmd: Id<ProtocolObject<dyn MTLCommandBuffer>>, cmd: &ProtocolObject<dyn MTLCommandBuffer>,
options: Option<&FilterChainOptionsMetal>,
) -> error::Result<FilterChainMetal> {
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<ProtocolObject<dyn MTLDevice>>,
cmd: &ProtocolObject<dyn MTLCommandBuffer>,
options: Option<&FilterChainOptionsMetal>, options: Option<&FilterChainOptionsMetal>,
) -> error::Result<FilterChainMetal> { ) -> error::Result<FilterChainMetal> {
let device = queue.device();
let (passes, semantics) = compile_passes(preset.shaders, &preset.textures)?; let (passes, semantics) = compile_passes(preset.shaders, &preset.textures)?;
let filters = Self::init_passes(&device, passes, &semantics)?;
let samplers = SamplerSet::new(&device)?; 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<ProtocolObject<dyn MTLTexture>>,
viewport: &Viewport<&ProtocolObject<dyn MTLTexture>>,
cmd_buffer: &ProtocolObject<dyn MTLCommandBuffer>,
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(())
} }
} }

View file

@ -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<u32> {
get_texture_size(&self.texture)
}
}
impl BindSemantics<NoUniformBinder, Option<()>, MetalBuffer, MetalBuffer> for FilterPass {
type InputTexture = InputTexture;
type SamplerSet = SamplerSet;
type DescriptorSet<'a> = &'a ProtocolObject<dyn MTLRenderCommandEncoder>;
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<NoUniformBinder, Option<()>, MetalBuffer, MetalBuffer>,
pub uniform_bindings: FxHashMap<UniformBinding, MemberOffset>,
pub source: ShaderSource,
pub config: ShaderPassConfig,
pub graphics_pipeline: MetalGraphicsPipeline,
}
impl FilterPass {
pub(crate) fn draw(
&mut self,
cmd: &ProtocolObject<dyn MTLCommandBuffer>,
pass_index: usize,
parent: &FilterCommon,
frame_count: u32,
frame_direction: i32,
viewport: &Viewport<&ProtocolObject<dyn MTLTexture>>,
original: &InputTexture,
source: &InputTexture,
output: &RenderTarget<ProtocolObject<dyn MTLTexture>>,
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<u32>,
viewport_size: Size<u32>,
original: &InputTexture,
source: &InputTexture,
mut renderpass: &ProtocolObject<dyn MTLRenderCommandEncoder>,
) {
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
}
}

View file

@ -1,8 +1,8 @@
use crate::error::{FilterChainError, Result}; use crate::error::{FilterChainError, Result};
use icrate::Foundation::NSString; use icrate::Foundation::NSString;
use icrate::Metal::{ use icrate::Metal::{
MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, MTLDevice,
MTLCommandEncoder, MTLDevice, MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat, MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat,
MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor, MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor,
MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor, MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor,
MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture, MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture,
@ -11,25 +11,27 @@ use icrate::Metal::{
}; };
use librashader_reflect::back::msl::{CrossMslContext, NagaMslContext}; use librashader_reflect::back::msl::{CrossMslContext, NagaMslContext};
use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::back::ShaderCompilerOutput;
use librashader_reflect::reflect::ShaderReflection;
use librashader_runtime::render_target::RenderTarget; use librashader_runtime::render_target::RenderTarget;
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::ProtocolObject; 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 struct MetalGraphicsPipeline {
pub layout: PipelineLayoutObjects, pub layout: PipelineLayoutObjects,
render_pipeline: Id<ProtocolObject<dyn MTLRenderPipelineState>>, render_pipeline: Id<ProtocolObject<dyn MTLRenderPipelineState>>,
pub render_pass_format: MTLPixelFormat,
} }
pub struct PipelineLayoutObjects { pub struct PipelineLayoutObjects {
vertex_lib: Id<ProtocolObject<dyn MTLLibrary>>, _vertex_lib: Id<ProtocolObject<dyn MTLLibrary>>,
fragment_lib: Id<ProtocolObject<dyn MTLLibrary>>, _fragment_lib: Id<ProtocolObject<dyn MTLLibrary>>,
vertex_entry: Id<ProtocolObject<dyn MTLFunction>>, vertex_entry: Id<ProtocolObject<dyn MTLFunction>>,
fragment_entry: Id<ProtocolObject<dyn MTLFunction>>, fragment_entry: Id<ProtocolObject<dyn MTLFunction>>,
device: Id<ProtocolObject<dyn MTLDevice>>,
} }
trait MslEntryPoint { pub(crate) trait MslEntryPoint {
fn entry_point() -> Id<NSString>; fn entry_point() -> Id<NSString>;
} }
@ -48,7 +50,7 @@ impl MslEntryPoint for NagaMslContext {
impl PipelineLayoutObjects { impl PipelineLayoutObjects {
pub fn new<T: MslEntryPoint>( pub fn new<T: MslEntryPoint>(
shader_assembly: &ShaderCompilerOutput<String, T>, shader_assembly: &ShaderCompilerOutput<String, T>,
device: Id<ProtocolObject<dyn MTLDevice>>, device: &ProtocolObject<dyn MTLDevice>,
) -> Result<Self> { ) -> Result<Self> {
let entry = T::entry_point(); let entry = T::entry_point();
@ -65,11 +67,10 @@ impl PipelineLayoutObjects {
.ok_or(FilterChainError::ShaderWrongEntryName)?; .ok_or(FilterChainError::ShaderWrongEntryName)?;
Ok(Self { Ok(Self {
vertex_lib: vertex, _vertex_lib: vertex,
fragment_lib: fragment, _fragment_lib: fragment,
vertex_entry, vertex_entry,
fragment_entry, fragment_entry,
device,
}) })
} }
@ -85,11 +86,11 @@ impl PipelineLayoutObjects {
// hopefully metal fills in vertices otherwise we'll need to use the vec4 stuff. // hopefully metal fills in vertices otherwise we'll need to use the vec4 stuff.
vertex_0.setFormat(MTLVertexFormatFloat2); vertex_0.setFormat(MTLVertexFormatFloat2);
vertex_0.setBufferIndex(4); vertex_0.setBufferIndex(VERTEX_BUFFER_INDEX);
vertex_0.setOffset(0); vertex_0.setOffset(0);
vertex_1.setFormat(MTLVertexFormatFloat2); vertex_1.setFormat(MTLVertexFormatFloat2);
vertex_1.setBufferIndex(4); vertex_1.setBufferIndex(VERTEX_BUFFER_INDEX);
vertex_1.setOffset(2 * std::mem::size_of::<f32>()); vertex_1.setOffset(2 * std::mem::size_of::<f32>());
attributes.setObject_atIndexedSubscript(Some(&vertex_0), 0); attributes.setObject_atIndexedSubscript(Some(&vertex_0), 0);
@ -119,6 +120,7 @@ impl PipelineLayoutObjects {
pub fn create_pipeline( pub fn create_pipeline(
&self, &self,
device: &ProtocolObject<dyn MTLDevice>,
format: MTLPixelFormat, format: MTLPixelFormat,
) -> Result<Id<ProtocolObject<dyn MTLRenderPipelineState>>> { ) -> Result<Id<ProtocolObject<dyn MTLRenderPipelineState>>> {
let descriptor = MTLRenderPipelineDescriptor::new(); let descriptor = MTLRenderPipelineDescriptor::new();
@ -140,36 +142,39 @@ impl PipelineLayoutObjects {
descriptor.setFragmentFunction(Some(&self.fragment_entry)); descriptor.setFragmentFunction(Some(&self.fragment_entry));
} }
Ok(self Ok(device.newRenderPipelineStateWithDescriptor_error(descriptor.as_ref())?)
.device
.newRenderPipelineStateWithDescriptor_error(descriptor.as_ref())?)
} }
} }
impl MetalGraphicsPipeline { impl MetalGraphicsPipeline {
pub fn new<T: MslEntryPoint>( pub fn new<T: MslEntryPoint>(
device: Id<ProtocolObject<dyn MTLDevice>>, device: &ProtocolObject<dyn MTLDevice>,
shader_assembly: &ShaderCompilerOutput<String, T>, shader_assembly: &ShaderCompilerOutput<String, T>,
render_pass_format: MTLPixelFormat, render_pass_format: MTLPixelFormat,
) -> Result<Self> { ) -> Result<Self> {
let layout = PipelineLayoutObjects::new(shader_assembly, device)?; 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 { Ok(Self {
layout, layout,
render_pipeline: pipeline, render_pipeline: pipeline,
render_pass_format,
}) })
} }
pub fn recompile(&mut self, format: MTLPixelFormat) -> Result<()> { pub fn recompile(
let render_pipeline = self.layout.create_pipeline(format)?; &mut self,
device: &ProtocolObject<dyn MTLDevice>,
format: MTLPixelFormat,
) -> Result<()> {
let render_pipeline = self.layout.create_pipeline(device, format)?;
self.render_pipeline = render_pipeline; self.render_pipeline = render_pipeline;
Ok(()) Ok(())
} }
pub fn begin_rendering<'pass>( pub fn begin_rendering<'pass>(
&self, &self,
output: RenderTarget<&'pass ProtocolObject<dyn MTLTexture>>, output: &RenderTarget<ProtocolObject<dyn MTLTexture>>,
buffer: Id<ProtocolObject<dyn MTLCommandBuffer>>, buffer: &ProtocolObject<dyn MTLCommandBuffer>,
) -> Result<Id<ProtocolObject<dyn MTLRenderCommandEncoder>>> { ) -> Result<Id<ProtocolObject<dyn MTLRenderCommandEncoder>>> {
unsafe { unsafe {
let descriptor = MTLRenderPassDescriptor::new(); let descriptor = MTLRenderPassDescriptor::new();

View file

@ -1,11 +1,18 @@
#![cfg(target_vendor = "apple")] #![cfg(target_vendor = "apple")]
#![feature(type_alias_impl_trait)]
mod buffer; mod buffer;
mod draw_quad; mod draw_quad;
mod error;
mod filter_chain; mod filter_chain;
mod filter_pass; mod filter_pass;
mod graphics_pipeline; mod graphics_pipeline;
mod luts; mod luts;
mod options;
mod samplers; mod samplers;
mod texture; 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);

View file

@ -1,26 +1,29 @@
use crate::error::{FilterChainError, Result}; use crate::error::{FilterChainError, Result};
use crate::samplers::SamplerSet; use crate::texture::InputTexture;
use crate::texture::MetalTexture;
use icrate::Metal::{ use icrate::Metal::{
MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLOrigin, MTLBlitCommandEncoder, MTLDevice, MTLOrigin, MTLPixelFormatBGRA8Unorm, MTLRegion, MTLSize,
MTLPixelFormatBGRA8Unorm, MTLRegion, MTLSize, MTLTexture, MTLTextureDescriptor, MTLTexture, MTLTextureDescriptor, MTLTextureUsageShaderRead,
MTLTextureUsageShaderRead,
}; };
use librashader_presets::TextureConfig; use librashader_presets::TextureConfig;
use librashader_runtime::image::{Image, BGRA8}; use librashader_runtime::image::{Image, BGRA8};
use librashader_runtime::scaling::MipmapSize; use librashader_runtime::scaling::MipmapSize;
use objc2::rc::Id;
use objc2::runtime::ProtocolObject; use objc2::runtime::ProtocolObject;
use std::ffi::c_void; use std::ffi::c_void;
use std::ptr::NonNull; use std::ptr::NonNull;
pub(crate) struct LutTexture(MetalTexture); pub(crate) struct LutTexture(InputTexture);
impl AsRef<InputTexture> for LutTexture {
fn as_ref(&self) -> &InputTexture {
self.0.as_ref()
}
}
impl LutTexture { impl LutTexture {
pub fn new( pub fn new(
device: &ProtocolObject<dyn MTLDevice>, device: &ProtocolObject<dyn MTLDevice>,
mipmapper: &ProtocolObject<dyn MTLBlitCommandEncoder>,
image: Image<BGRA8>, image: Image<BGRA8>,
cmd: &ProtocolObject<dyn MTLCommandBuffer>,
config: &TextureConfig, config: &TextureConfig,
) -> Result<Self> { ) -> Result<Self> {
let descriptor = unsafe { let descriptor = unsafe {
@ -68,12 +71,14 @@ impl LutTexture {
} }
if config.mipmap { if config.mipmap {
if let Some(encoder) = cmd.blitCommandEncoder() { mipmapper.generateMipmapsForTexture(&texture);
encoder.generateMipmapsForTexture(&texture);
encoder.endEncoding();
}
} }
Ok(LutTexture(texture)) Ok(LutTexture(InputTexture {
texture,
wrap_mode: config.wrap_mode,
filter_mode: config.filter_mode,
mip_filter: config.filter_mode,
}))
} }
} }

View file

@ -1,33 +1,56 @@
use crate::error::{FilterChainError, Result}; use crate::error::{FilterChainError, Result};
use icrate::Metal::{ use icrate::Metal::{
MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLPixelFormat, MTLBlitCommandEncoder, MTLCommandBuffer, MTLDevice, MTLPixelFormat, MTLTexture,
MTLPixelFormatBGRA8Unorm, MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget, MTLTextureDescriptor, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead,
MTLTextureUsageShaderRead, MTLTextureUsageShaderWrite, MTLTextureUsageShaderWrite,
}; };
use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; use librashader_common::{FilterMode, ImageFormat, Size, WrapMode};
use librashader_presets::Scale2D; use librashader_presets::Scale2D;
use librashader_runtime::scaling::{MipmapSize, ViewportSize}; use librashader_runtime::scaling::{MipmapSize, ScaleFramebuffer, ViewportSize};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::ProtocolObject; use objc2::runtime::ProtocolObject;
use std::sync::Arc;
pub type MetalTexture = Id<ProtocolObject<dyn MTLTexture>>; pub type MetalTexture = Id<ProtocolObject<dyn MTLTexture>>;
pub struct OwnedImage { pub struct OwnedTexture {
image: MetalTexture, pub(crate) texture: MetalTexture,
max_miplevels: u32, pub(crate) max_miplevels: u32,
size: Size<u32>, size: Size<u32>,
} }
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<Self> {
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<InputTexture> for InputTexture {
fn as_ref(&self) -> &InputTexture {
&self
}
}
impl OwnedTexture {
pub fn new( pub fn new(
device: &ProtocolObject<dyn MTLDevice>, device: &ProtocolObject<dyn MTLDevice>,
size: Size<u32>, size: Size<u32>,
max_miplevels: u32, max_miplevels: u32,
format: ImageFormat, format: MTLPixelFormat,
) -> Result<Self> { ) -> Result<Self> {
let format: MTLPixelFormat = format.into();
let descriptor = unsafe { let descriptor = unsafe {
let descriptor = let descriptor =
MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped( MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
@ -54,7 +77,7 @@ impl OwnedImage {
}; };
Ok(Self { Ok(Self {
image: device texture: device
.newTextureWithDescriptor(&descriptor) .newTextureWithDescriptor(&descriptor)
.ok_or(FilterChainError::FailedToCreateTexture)?, .ok_or(FilterChainError::FailedToCreateTexture)?,
max_miplevels, max_miplevels,
@ -66,52 +89,50 @@ impl OwnedImage {
&mut self, &mut self,
device: &ProtocolObject<dyn MTLDevice>, device: &ProtocolObject<dyn MTLDevice>,
scaling: Scale2D, scaling: Scale2D,
format: ImageFormat, format: MTLPixelFormat,
viewport_size: &Size<u32>, viewport_size: &Size<u32>,
source_size: &Size<u32>, source_size: &Size<u32>,
mipmap: bool, mipmap: bool,
) -> Size<u32> { ) -> Result<Size<u32>> {
let size = source_size.scale_viewport(scaling, *viewport_size); let size = source_size.scale_viewport(scaling, *viewport_size);
let format: MTLPixelFormat = format.into();
if self.size != size if self.size != size
|| (mipmap && self.max_miplevels == 1) || (mipmap && self.max_miplevels == 1)
|| (!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); std::mem::swap(self, &mut new);
} }
size Ok(size)
} }
// pub(crate) fn as_input(&self, filter: FilterMode, wrap_mode: WrapMode) -> InputImage { pub(crate) fn as_input(&self, filter: FilterMode, wrap_mode: WrapMode) -> Result<InputTexture> {
// InputImage { Ok(InputTexture {
// image: Arc::clone(&self.image), texture: self
// view: Arc::clone(&self.view), .texture
// wrap_mode, .newTextureViewWithPixelFormat(self.texture.pixelFormat())
// filter_mode: filter, .ok_or(FilterChainError::FailedToCreateTexture)?,
// mip_filter: filter, wrap_mode,
// } filter_mode: filter,
// } mip_filter: filter,
})
}
pub fn copy_from( pub fn copy_from(
&self, &self,
encoder: &ProtocolObject<dyn MTLBlitCommandEncoder>,
other: &ProtocolObject<dyn MTLTexture>, other: &ProtocolObject<dyn MTLTexture>,
cmd: Id<ProtocolObject<dyn MTLCommandBuffer>>,
) -> Result<()> { ) -> Result<()> {
let encoder = cmd
.blitCommandEncoder()
.ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
unsafe { unsafe {
encoder.copyFromTexture_toTexture(other, &self.image); encoder.copyFromTexture_toTexture(other, &self.texture);
} }
encoder.generateMipmapsForTexture(&self.image); encoder.generateMipmapsForTexture(&self.texture);
encoder.endEncoding();
Ok(()) Ok(())
} }
pub fn clear(&self, cmd: Id<ProtocolObject<dyn MTLCommandBuffer>>) { pub fn clear(&self, cmd: &ProtocolObject<dyn MTLCommandBuffer>) {
// let render = cmd.renderCommandEncoder() // let render = cmd.renderCommandEncoder()
// .ok_or(FilterChainError::FailedToCreateCommandBuffer)?; // .ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
// render. // render.
@ -120,6 +141,39 @@ impl OwnedImage {
/// caller must end the blit encoder after. /// caller must end the blit encoder after.
pub fn generate_mipmaps(&self, mipmapper: &ProtocolObject<dyn MTLBlitCommandEncoder>) { pub fn generate_mipmaps(&self, mipmapper: &ProtocolObject<dyn MTLBlitCommandEncoder>) {
mipmapper.generateMipmapsForTexture(&self.image); mipmapper.generateMipmapsForTexture(&self.texture);
}
}
impl ScaleFramebuffer for OwnedTexture {
type Error = FilterChainError;
type Context = ProtocolObject<dyn MTLDevice>;
fn scale(
&mut self,
scaling: Scale2D,
format: ImageFormat,
viewport_size: &Size<u32>,
source_size: &Size<u32>,
should_mipmap: bool,
context: &Self::Context,
) -> std::result::Result<Size<u32>, Self::Error> {
Ok(self.scale(
&context,
scaling,
format.into(),
viewport_size,
source_size,
should_mipmap,
)?)
}
}
pub(crate) fn get_texture_size(texture: &ProtocolObject<dyn MTLTexture>) -> Size<u32> {
let height = texture.height();
let width = texture.width();
Size {
height: height as u32,
width: width as u32,
} }
} }