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-runtime",
"objc2 0.5.0",
"rayon",
"rustc-hash",
"thiserror",
]

View file

@ -50,6 +50,7 @@ pub struct NagaMslModule {
pub struct NagaMslContext {
pub vertex: NagaMslModule,
pub fragment: NagaMslModule,
pub next_free_binding: u32,
}
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 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<MSL> for NagaReflect {
translation_info: vertex.1,
module: self.vertex,
},
next_free_binding: vertex_binding
},
})
}

View file

@ -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]

View file

@ -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<ProtocolObject<dyn MTLBuffer>> for MetalBuffer {
fn as_ref(&self) -> &ProtocolObject<dyn MTLBuffer> {
self.buffer.as_ref()
}
}
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") {
MTLResourceStorageModeShared
} else {

View file

@ -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);
}
}

View file

@ -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<impl CompileReflectShader<MSL, SpirvCompilation, SpirvCross> + 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<OwnedImage>,
output_framebuffers: Box<[OwnedTexture]>,
feedback_framebuffers: Box<[OwnedTexture]>,
history_framebuffers: VecDeque<OwnedTexture>,
disable_mipmaps: bool,
}
@ -52,23 +66,22 @@ pub struct FilterMutable {
}
pub(crate) struct FilterCommon {
pub output_textures: Box<[Option<MetalTexture>]>,
pub feedback_textures: Box<[Option<MetalTexture>]>,
pub history_textures: Box<[Option<MetalTexture>]>,
pub output_textures: Box<[Option<InputTexture>]>,
pub feedback_textures: Box<[Option<InputTexture>]>,
pub history_textures: Box<[Option<InputTexture>]>,
pub luts: FxHashMap<usize, LutTexture>,
pub samplers: SamplerSet,
pub config: FilterMutable,
pub internal_frame_count: i32,
pub(crate) draw_quad: DrawQuad,
device: Id<ProtocolObject<dyn MTLDevice>>,
queue: Id<ProtocolObject<dyn MTLCommandQueue>>,
}
impl FilterChainMetal {
/// Load the shader preset at the given path into a filter chain.
pub fn load_from_path(
path: impl AsRef<Path>,
queue: Id<ProtocolObject<dyn MTLCommandQueue>>,
queue: &ProtocolObject<dyn MTLCommandQueue>,
options: Option<&FilterChainOptionsMetal>,
) -> error::Result<FilterChainMetal> {
// 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<ProtocolObject<dyn MTLCommandQueue>>,
queue: &ProtocolObject<dyn MTLCommandQueue>,
options: Option<&FilterChainOptionsMetal>,
) -> error::Result<FilterChainMetal> {
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<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
/// 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<ProtocolObject<dyn MTLCommandQueue>>,
cmd: Id<ProtocolObject<dyn MTLCommandBuffer>>,
queue: &ProtocolObject<dyn MTLCommandQueue>,
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>,
) -> error::Result<FilterChainMetal> {
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<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 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<ProtocolObject<dyn MTLRenderPipelineState>>,
pub render_pass_format: MTLPixelFormat,
}
pub struct PipelineLayoutObjects {
vertex_lib: Id<ProtocolObject<dyn MTLLibrary>>,
fragment_lib: Id<ProtocolObject<dyn MTLLibrary>>,
_vertex_lib: Id<ProtocolObject<dyn MTLLibrary>>,
_fragment_lib: Id<ProtocolObject<dyn MTLLibrary>>,
vertex_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>;
}
@ -48,7 +50,7 @@ impl MslEntryPoint for NagaMslContext {
impl PipelineLayoutObjects {
pub fn new<T: MslEntryPoint>(
shader_assembly: &ShaderCompilerOutput<String, T>,
device: Id<ProtocolObject<dyn MTLDevice>>,
device: &ProtocolObject<dyn MTLDevice>,
) -> Result<Self> {
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::<f32>());
attributes.setObject_atIndexedSubscript(Some(&vertex_0), 0);
@ -119,6 +120,7 @@ impl PipelineLayoutObjects {
pub fn create_pipeline(
&self,
device: &ProtocolObject<dyn MTLDevice>,
format: MTLPixelFormat,
) -> Result<Id<ProtocolObject<dyn MTLRenderPipelineState>>> {
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<T: MslEntryPoint>(
device: Id<ProtocolObject<dyn MTLDevice>>,
device: &ProtocolObject<dyn MTLDevice>,
shader_assembly: &ShaderCompilerOutput<String, T>,
render_pass_format: MTLPixelFormat,
) -> Result<Self> {
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<dyn MTLDevice>,
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<dyn MTLTexture>>,
buffer: Id<ProtocolObject<dyn MTLCommandBuffer>>,
output: &RenderTarget<ProtocolObject<dyn MTLTexture>>,
buffer: &ProtocolObject<dyn MTLCommandBuffer>,
) -> Result<Id<ProtocolObject<dyn MTLRenderCommandEncoder>>> {
unsafe {
let descriptor = MTLRenderPassDescriptor::new();

View file

@ -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);

View file

@ -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<InputTexture> for LutTexture {
fn as_ref(&self) -> &InputTexture {
self.0.as_ref()
}
}
impl LutTexture {
pub fn new(
device: &ProtocolObject<dyn MTLDevice>,
mipmapper: &ProtocolObject<dyn MTLBlitCommandEncoder>,
image: Image<BGRA8>,
cmd: &ProtocolObject<dyn MTLCommandBuffer>,
config: &TextureConfig,
) -> Result<Self> {
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,
}))
}
}

View file

@ -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<ProtocolObject<dyn MTLTexture>>;
pub struct OwnedImage {
image: MetalTexture,
max_miplevels: u32,
pub struct OwnedTexture {
pub(crate) texture: MetalTexture,
pub(crate) max_miplevels: 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(
device: &ProtocolObject<dyn MTLDevice>,
size: Size<u32>,
max_miplevels: u32,
format: ImageFormat,
format: MTLPixelFormat,
) -> Result<Self> {
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<dyn MTLDevice>,
scaling: Scale2D,
format: ImageFormat,
format: MTLPixelFormat,
viewport_size: &Size<u32>,
source_size: &Size<u32>,
mipmap: bool,
) -> Size<u32> {
) -> Result<Size<u32>> {
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<InputTexture> {
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<dyn MTLBlitCommandEncoder>,
other: &ProtocolObject<dyn MTLTexture>,
cmd: Id<ProtocolObject<dyn MTLCommandBuffer>>,
) -> 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<ProtocolObject<dyn MTLCommandBuffer>>) {
pub fn clear(&self, cmd: &ProtocolObject<dyn MTLCommandBuffer>) {
// 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<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,
}
}