rt(mtl): do intermediate passes offscreen

This commit is contained in:
chyyran 2024-02-12 00:43:51 -05:00 committed by Ronny Chan
parent 30dfa1a655
commit 43da6e60c6
10 changed files with 633 additions and 66 deletions

View file

@ -27,9 +27,9 @@ array-concat = "0.5.2"
bytemuck = { version = "1.12.3", features = ["derive"] } bytemuck = { version = "1.12.3", features = ["derive"] }
rayon = "1.8.1" rayon = "1.8.1"
[dev-dependencies.icrate] [dependencies.icrate]
version = "0.1.0" version = "0.1.0"
features = ["AppKit", "AppKit_all", "Foundation", "Foundation_all", "MetalKit", "MetalKit_all"] features = ["AppKit", "AppKit_all", "Foundation", "Foundation_all", "MetalKit", "MetalKit_all", "Metal", "Metal_all"]
[[test]] [[test]]
name = "triangle" name = "triangle"
@ -40,5 +40,8 @@ harness = false
features = ["librashader-cache/docsrs"] features = ["librashader-cache/docsrs"]
[target.'cfg(target_vendor="apple")'.dependencies] [target.'cfg(target_vendor="apple")'.dependencies]
icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all" ]} #icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all" ]}
objc2 = { version = "0.5.0", features = ["apple"] } objc2 = { version = "0.5.0", features = ["apple"] }
#
#[lib]
#crate-type = ["lib", "staticlib"]

View file

@ -24,7 +24,7 @@ impl MetalBuffer {
let resource_mode = if cfg!(target_os = "ios") { let resource_mode = if cfg!(target_os = "ios") {
MTLResourceStorageModeShared MTLResourceStorageModeShared
} else { } else {
MTLResourceStorageModeManaged MTLResourceStorageModeShared
}; };
let buffer = device let buffer = device
@ -35,10 +35,10 @@ impl MetalBuffer {
pub fn flush(&self) { pub fn flush(&self) {
// We don't know what was actually written to so... // We don't know what was actually written to so...
self.buffer.didModifyRange(NSRange { // self.buffer.didModifyRange(NSRange {
location: 0, // location: 0,
length: self.size, // length: self.size,
}) // })
} }
} }

View file

@ -15,26 +15,46 @@ 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)]
struct MetalVertex { pub(crate) struct MetalVertex {
position: [f32; 4], pub position: [f32; 4],
texcoord: [f32; 2], pub texcoord: [f32; 2],
} }
const FINAL_VBO_DATA: [MetalVertex; 4] = [ const OFFSCREEN_VBO_DATA: [MetalVertex; 4] = [
MetalVertex { MetalVertex {
position: [0.0, 1.0, 0.0, 1.0], position: [-1.0, -1.0, 0.0, 1.0],
texcoord: [0.0, 1.0], texcoord: [0.0, 1.0],
}, },
MetalVertex { MetalVertex {
position: [1.0, 1.0, 0.0, 1.0], position: [-1.0, 1.0, 0.0, 1.0],
texcoord: [0.0, 0.0],
},
MetalVertex {
position: [1.0, -1.0, 0.0, 1.0],
texcoord: [1.0, 1.0], texcoord: [1.0, 1.0],
}, },
MetalVertex {
position: [1.0, 1.0, 0.0, 1.0],
texcoord: [1.0, 0.0],
},
];
const FINAL_VBO_DATA: [MetalVertex; 4] = [
MetalVertex { MetalVertex {
position: [0.0, 0.0, 0.0, 1.0], position: [0.0, 0.0, 0.0, 1.0],
texcoord: [0.0, 1.0],
},
MetalVertex {
position: [0.0, 1.0, 0.0, 1.0],
texcoord: [0.0, 0.0], texcoord: [0.0, 0.0],
}, },
MetalVertex { MetalVertex {
position: [1.0, 0.0, 0.0, 1.0], position: [1.0, 0.0, 0.0, 1.0],
texcoord: [1.0, 1.0],
},
MetalVertex {
position: [1.0, 1.0, 0.0, 1.0],
texcoord: [1.0, 0.0], texcoord: [1.0, 0.0],
}, },
]; ];
@ -57,7 +77,7 @@ const VBO_DEFAULT_FINAL: [f32; 16] = [
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
]; ];
const VBO_DATA: [f32; 32] = concat_arrays!(VBO_OFFSCREEN, VBO_DEFAULT_FINAL); const VBO_DATA: [MetalVertex; 8] = concat_arrays!(OFFSCREEN_VBO_DATA, FINAL_VBO_DATA);
pub struct DrawQuad { pub struct DrawQuad {
buffer: Id<ProtocolObject<dyn MTLBuffer>>, buffer: Id<ProtocolObject<dyn MTLBuffer>>,
@ -66,6 +86,22 @@ pub struct DrawQuad {
impl DrawQuad { impl DrawQuad {
pub fn new(device: &ProtocolObject<dyn MTLDevice>) -> Result<DrawQuad> { pub fn new(device: &ProtocolObject<dyn MTLDevice>) -> Result<DrawQuad> {
let vbo_data: &'static [u8] = bytemuck::cast_slice(&VBO_DATA); let vbo_data: &'static [u8] = bytemuck::cast_slice(&VBO_DATA);
// let buffer = unsafe {
// device
// .newBufferWithBytes_length_options(
// // SAFETY: this pointer is const.
// // https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes
// NonNull::new_unchecked(vbo_data.as_ptr() as *mut c_void),
// vbo_data.len(),
// if cfg!(target_os = "ios") {
// MTLResourceStorageModeShared
// } else {
// MTLResourceStorageModeManaged
// },
// )
// .ok_or(FilterChainError::BufferError)?
// };
let buffer = unsafe { let buffer = unsafe {
device device
.newBufferWithBytes_length_options( .newBufferWithBytes_length_options(
@ -93,13 +129,10 @@ impl DrawQuad {
}; };
unsafe { unsafe {
cmd.setVertexBuffer_offset_attributeStride_atIndex( cmd.setVertexBuffer_offset_atIndex(Some(&self.buffer), 0, VERTEX_BUFFER_INDEX);
Some(self.buffer.as_ref()), cmd.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangleStrip,
0, offset,
4 * std::mem::size_of::<f32>(), 4);
VERTEX_BUFFER_INDEX,
);
cmd.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangleStrip, offset, 4);
} }
} }
} }

View file

@ -202,8 +202,11 @@ impl FilterChainMetal {
fn push_history( fn push_history(
&mut self, &mut self,
input: &ProtocolObject<dyn MTLTexture>, input: &ProtocolObject<dyn MTLTexture>,
cmd: &ProtocolObject<dyn MTLBlitCommandEncoder>, cmd: &ProtocolObject<dyn MTLCommandBuffer>,
) -> error::Result<()> { ) -> error::Result<()> {
let mipmapper = cmd
.blitCommandEncoder()
.ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
if let Some(mut back) = self.history_framebuffers.pop_back() { if let Some(mut back) = self.history_framebuffers.pop_back() {
if back.texture.height() != input.height() if back.texture.height() != input.height()
|| back.texture.width() != input.width() || back.texture.width() != input.width()
@ -220,10 +223,11 @@ impl FilterChainMetal {
); );
} }
back.copy_from(cmd, input)?; back.copy_from(&mipmapper, input)?;
self.history_framebuffers.push_front(back); self.history_framebuffers.push_front(back);
} }
mipmapper.endEncoding();
Ok(()) Ok(())
} }
@ -317,7 +321,7 @@ impl FilterChainMetal {
/// Records shader rendering commands to the provided command encoder. /// Records shader rendering commands to the provided command encoder.
pub fn frame( pub fn frame(
&mut self, &mut self,
input: Id<ProtocolObject<dyn MTLTexture>>, input: &ProtocolObject<dyn MTLTexture>,
viewport: &Viewport<&ProtocolObject<dyn MTLTexture>>, viewport: &Viewport<&ProtocolObject<dyn MTLTexture>>,
cmd_buffer: &ProtocolObject<dyn MTLCommandBuffer>, cmd_buffer: &ProtocolObject<dyn MTLCommandBuffer>,
frame_count: usize, frame_count: usize,
@ -394,17 +398,14 @@ impl FilterChainMetal {
let (pass, last) = passes.split_at_mut(passes_len - 1); let (pass, last) = passes.split_at_mut(passes_len - 1);
let options = options.unwrap_or(&self.default_options); let options = options.unwrap_or(&self.default_options);
let mipmapper = cmd_buffer
.blitCommandEncoder()
.ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
for (index, pass) in pass.iter_mut().enumerate() { for (index, pass) in pass.iter_mut().enumerate() {
let target = &self.output_framebuffers[index]; let target = &self.output_framebuffers[index];
source.filter_mode = pass.config.filter; source.filter_mode = pass.config.filter;
source.wrap_mode = pass.config.wrap_mode; source.wrap_mode = pass.config.wrap_mode;
source.mip_filter = pass.config.filter; source.mip_filter = pass.config.filter;
let out = RenderTarget::identity(target.texture.as_ref()); let out =
RenderTarget::identity(target.texture.as_ref());
pass.draw( pass.draw(
&cmd_buffer, &cmd_buffer,
index, index,
@ -419,7 +420,7 @@ impl FilterChainMetal {
)?; )?;
if target.max_miplevels > 1 && !self.disable_mipmaps { if target.max_miplevels > 1 && !self.disable_mipmaps {
target.generate_mipmaps(&mipmapper); target.generate_mipmaps(&cmd_buffer)?;
} }
source = self.common.output_textures[index] source = self.common.output_textures[index]
@ -457,7 +458,7 @@ impl FilterChainMetal {
)?; )?;
} }
self.push_history(&input, &mipmapper)?; self.push_history(&input, &cmd_buffer)?;
self.common.internal_frame_count = self.common.internal_frame_count.wrapping_add(1); self.common.internal_frame_count = self.common.internal_frame_count.wrapping_add(1);
Ok(()) Ok(())
} }

View file

@ -43,7 +43,6 @@ impl BindSemantics<NoUniformBinder, Option<()>, MetalBuffer, MetalBuffer> for Fi
let sampler = samplers.get(texture.wrap_mode, texture.filter_mode, texture.mip_filter); let sampler = samplers.get(texture.wrap_mode, texture.filter_mode, texture.mip_filter);
unsafe { unsafe {
renderpass.setFragmentTexture_atIndex(Some(&texture.texture), binding.binding as usize);
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); renderpass.setFragmentSamplerState_atIndex(Some(sampler), binding.binding as usize);
} }
@ -95,6 +94,11 @@ impl FilterPass {
Some(self.uniform_storage.inner_ubo().as_ref()), Some(self.uniform_storage.inner_ubo().as_ref()),
0, 0,
ubo.binding as usize, ubo.binding as usize,
);
cmd.setFragmentBuffer_offset_atIndex(
Some(self.uniform_storage.inner_ubo().as_ref()),
0,
ubo.binding as usize,
) )
} }
} }
@ -105,6 +109,11 @@ impl FilterPass {
Some(self.uniform_storage.inner_ubo().as_ref()), Some(self.uniform_storage.inner_ubo().as_ref()),
0, 0,
pcb.binding.unwrap_or(1) as usize, pcb.binding.unwrap_or(1) as usize,
);
cmd.setFragmentBuffer_offset_atIndex(
Some(self.uniform_storage.inner_ubo().as_ref()),
0,
pcb.binding.unwrap_or(1) as usize,
) )
} }
} }

View file

@ -1,19 +1,13 @@
use std::mem::offset_of;
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, MTLClearColor, MTLCommandBuffer, MTLDevice, MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat, MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor, MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor, MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture, MTLVertexAttributeDescriptor, MTLVertexBufferLayoutDescriptor, MTLVertexDescriptor, MTLVertexFormatFloat2, MTLVertexFormatFloat4, MTLVertexStepFunctionPerVertex, MTLViewport};
MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, MTLDevice,
MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat,
MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor,
MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor,
MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture,
MTLVertexAttributeDescriptor, MTLVertexBufferLayoutDescriptor, MTLVertexDescriptor,
MTLVertexFormatFloat2, MTLVertexStepFunctionPerVertex, MTLViewport,
};
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_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;
use crate::draw_quad::MetalVertex;
/// This is only really plausible for SPIRV-Cross, for Naga we need to supply the next plausible binding. /// 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 const VERTEX_BUFFER_INDEX: usize = 4;
@ -85,20 +79,20 @@ impl PipelineLayoutObjects {
let texcoord = MTLVertexAttributeDescriptor::new(); let texcoord = MTLVertexAttributeDescriptor::new();
// 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.
position.setFormat(MTLVertexFormatFloat2); position.setFormat(MTLVertexFormatFloat4);
position.setBufferIndex(VERTEX_BUFFER_INDEX); position.setBufferIndex(VERTEX_BUFFER_INDEX);
position.setOffset(0); position.setOffset(offset_of!(MetalVertex, position));
texcoord.setFormat(MTLVertexFormatFloat2); texcoord.setFormat(MTLVertexFormatFloat2);
texcoord.setBufferIndex(VERTEX_BUFFER_INDEX); texcoord.setBufferIndex(VERTEX_BUFFER_INDEX);
texcoord.setOffset(2 * std::mem::size_of::<f32>()); texcoord.setOffset(offset_of!(MetalVertex, texcoord));
attributes.setObject_atIndexedSubscript(Some(&position), 0); attributes.setObject_atIndexedSubscript(Some(&position), 0);
attributes.setObject_atIndexedSubscript(Some(&texcoord), 1); attributes.setObject_atIndexedSubscript(Some(&texcoord), 1);
binding.setStepFunction(MTLVertexStepFunctionPerVertex); binding.setStepFunction(MTLVertexStepFunctionPerVertex);
binding.setStride(4 * std::mem::size_of::<f32>()); binding.setStride(std::mem::size_of::<MetalVertex>());
layouts.setObject_atIndexedSubscript(Some(&binding), VERTEX_BUFFER_INDEX); layouts.setObject_atIndexedSubscript(Some(&binding), VERTEX_BUFFER_INDEX);
descriptor descriptor
@ -178,7 +172,8 @@ impl MetalGraphicsPipeline {
) -> Result<Id<ProtocolObject<dyn MTLRenderCommandEncoder>>> { ) -> Result<Id<ProtocolObject<dyn MTLRenderCommandEncoder>>> {
unsafe { unsafe {
let descriptor = MTLRenderPassDescriptor::new(); let descriptor = MTLRenderPassDescriptor::new();
let ca = descriptor.colorAttachments().objectAtIndexedSubscript(0); let ca = descriptor.colorAttachments()
.objectAtIndexedSubscript(0);
ca.setLoadAction(MTLLoadActionDontCare); ca.setLoadAction(MTLLoadActionDontCare);
ca.setStoreAction(MTLStoreActionStore); ca.setStoreAction(MTLStoreActionStore);
ca.setTexture(Some(output.output)); ca.setTexture(Some(output.output));

View file

@ -36,11 +36,13 @@ impl LutTexture {
); );
descriptor.setSampleCount(1); descriptor.setSampleCount(1);
descriptor.setMipmapLevelCount(if config.mipmap { // descriptor.setMipmapLevelCount(if config.mipmap {
image.size.calculate_miplevels() as usize // image.size.calculate_miplevels() as usize
} else { // } else {
1 // 1
}); // });
descriptor.setMipmapLevelCount(1);
descriptor.setUsage(MTLTextureUsageShaderRead); descriptor.setUsage(MTLTextureUsageShaderRead);
@ -71,7 +73,7 @@ impl LutTexture {
} }
if config.mipmap { if config.mipmap {
mipmapper.generateMipmapsForTexture(&texture); // mipmapper.generateMipmapsForTexture(&texture);
} }
Ok(LutTexture(InputTexture { Ok(LutTexture(InputTexture {

View file

@ -0,0 +1,460 @@
#![deny(unsafe_op_in_unsafe_fn)]
use core::{cell::OnceCell, ptr::NonNull};
use std::sync::RwLock;
use icrate::Metal::{MTLBlitCommandEncoder, MTLClearColor, MTLPixelFormatRGBA8Unorm, MTLTexture, MTLTextureDescriptor, MTLTextureUsagePixelFormatView, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead};
use icrate::{
AppKit::{
NSApplication, NSApplicationActivationPolicyRegular, NSApplicationDelegate,
NSBackingStoreBuffered, NSWindow, NSWindowStyleMaskClosable, NSWindowStyleMaskResizable,
NSWindowStyleMaskTitled,
},
Foundation::{
ns_string, MainThreadMarker, NSDate, NSNotification, NSObject, NSObjectProtocol, NSPoint,
NSRect, NSSize,
},
Metal::{
MTLCommandBuffer, MTLCommandEncoder, MTLCommandQueue, MTLCreateSystemDefaultDevice,
MTLDevice, MTLDrawable, MTLLibrary, MTLPrimitiveTypeTriangle, MTLRenderCommandEncoder,
MTLRenderPipelineDescriptor, MTLRenderPipelineState,
},
MetalKit::{MTKView, MTKViewDelegate},
};
use librashader_common::Viewport;
use librashader_presets::ShaderPreset;
use librashader_runtime_metal::FilterChainMetal;
use objc2::{
declare_class, msg_send_id, mutability::MainThreadOnly, rc::Id, runtime::ProtocolObject,
ClassType, DeclaredClass,
};
#[rustfmt::skip]
const SHADERS: &str = r#"
#include <metal_stdlib>
struct SceneProperties {
float time;
};
struct VertexInput {
metal::packed_float3 position;
metal::packed_float3 color;
};
struct VertexOutput {
metal::float4 position [[position]];
metal::float4 color;
};
vertex VertexOutput vertex_main(
device const SceneProperties& properties [[buffer(0)]],
device const VertexInput* vertices [[buffer(1)]],
uint vertex_idx [[vertex_id]]
) {
VertexOutput out;
VertexInput in = vertices[vertex_idx];
out.position =
metal::float4(
metal::float2x2(
metal::cos(properties.time), -metal::sin(properties.time),
metal::sin(properties.time), metal::cos(properties.time)
) * in.position.xy,
in.position.z,
1);
out.color = metal::float4(in.color, 1);
return out;
}
fragment metal::float4 fragment_main(VertexOutput in [[stage_in]]) {
return in.color;
}
"#;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct SceneProperties {
pub time: f32,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct VertexInput {
pub position: Position,
pub color: Color,
}
#[derive(Copy, Clone)]
// NOTE: this has the same ABI as `MTLPackedFloat3`
#[repr(C)]
pub struct Position {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Copy, Clone)]
// NOTE: this has the same ABI as `MTLPackedFloat3`
#[repr(C)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
}
macro_rules! idcell {
($name:ident => $this:expr) => {
$this.ivars().$name.set($name).expect(&format!(
"ivar should not already be initialized: `{}`",
stringify!($name)
));
};
($name:ident <= $this:expr) => {
#[rustfmt::skip]
let Some($name) = $this.ivars().$name.get() else {
unreachable!(
"ivar should be initialized: `{}`",
stringify!($name)
)
};
};
}
// declare the desired instance variables
struct Ivars {
start_date: Id<NSDate>,
command_queue: OnceCell<Id<ProtocolObject<dyn MTLCommandQueue>>>,
pipeline_state: OnceCell<Id<ProtocolObject<dyn MTLRenderPipelineState>>>,
filter_chain: OnceCell<RwLock<FilterChainMetal>>,
window: OnceCell<Id<NSWindow>>,
}
// declare the Objective-C class machinery
declare_class!(
struct Delegate;
// SAFETY:
// - The superclass NSObject does not have any subclassing requirements.
// - Main thread only mutability is correct, since this is an application delegate.
// - `Delegate` does not implement `Drop`.
unsafe impl ClassType for Delegate {
type Super = NSObject;
type Mutability = MainThreadOnly;
const NAME: &'static str = "Delegate";
}
impl DeclaredClass for Delegate {
type Ivars = Ivars;
}
unsafe impl NSObjectProtocol for Delegate {}
// define the delegate methods for the `NSApplicationDelegate` protocol
unsafe impl NSApplicationDelegate for Delegate {
#[method(applicationDidFinishLaunching:)]
#[allow(non_snake_case)]
unsafe fn applicationDidFinishLaunching(&self, _notification: &NSNotification) {
let mtm = MainThreadMarker::from(self);
// create the app window
let window = {
let content_rect = NSRect::new(NSPoint::new(0., 0.), NSSize::new(768., 768.));
let style = NSWindowStyleMaskClosable
| NSWindowStyleMaskResizable
| NSWindowStyleMaskTitled;
let backing_store_type = NSBackingStoreBuffered;
let flag = false;
unsafe {
NSWindow::initWithContentRect_styleMask_backing_defer(
mtm.alloc(),
content_rect,
style,
backing_store_type,
flag,
)
}
};
// get the default device
let device = {
let ptr = unsafe { MTLCreateSystemDefaultDevice() };
unsafe { Id::retain(ptr) }.expect("Failed to get default system device.")
};
// create the command queue
let command_queue = device
.newCommandQueue()
.expect("Failed to create a command queue.");
// create the metal view
let mtk_view = {
let frame_rect = window.frame();
unsafe { MTKView::initWithFrame_device(mtm.alloc(), frame_rect, Some(&device)) }
};
// create the pipeline descriptor
let pipeline_descriptor = MTLRenderPipelineDescriptor::new();
unsafe {
pipeline_descriptor
.colorAttachments()
.objectAtIndexedSubscript(0)
.setPixelFormat(mtk_view.colorPixelFormat());
}
// compile the shaders
let library = device
.newLibraryWithSource_options_error(ns_string!(SHADERS), None)
.expect("Failed to create a library.");
// configure the vertex shader
let vertex_function = library.newFunctionWithName(ns_string!("vertex_main"));
pipeline_descriptor.setVertexFunction(vertex_function.as_deref());
// configure the fragment shader
let fragment_function = library.newFunctionWithName(ns_string!("fragment_main"));
pipeline_descriptor.setFragmentFunction(fragment_function.as_deref());
// create the pipeline state
let pipeline_state = device
.newRenderPipelineStateWithDescriptor_error(&pipeline_descriptor)
.expect("Failed to create a pipeline state.");
// let preset = ShaderPreset::try_parse("./test/shaders_slang/crt/crt-lottes.slangp").unwrap();
// let preset = ShaderPreset::try_parse("./test/shaders_slang/crt/crt-lottes.slangp").unwrap();
let preset = ShaderPreset::try_parse("./test/basic.slangp").unwrap();
let filter_chain = FilterChainMetal::load_from_preset(
preset,
&command_queue,
None,
)
.unwrap();
let filter_chain = RwLock::new(filter_chain);
// configure the metal view delegate
unsafe {
let object = ProtocolObject::from_ref(self);
mtk_view.setDelegate(Some(object));
}
// configure the window
window.setContentView(Some(&mtk_view));
window.center();
window.setTitle(ns_string!("metal example"));
window.makeKeyAndOrderFront(None);
// initialize the delegate state
idcell!(command_queue => self);
idcell!(pipeline_state => self);
idcell!(filter_chain => self);
idcell!(window => self);
}
}
// define the delegate methods for the `MTKViewDelegate` protocol
unsafe impl MTKViewDelegate for Delegate {
#[method(drawInMTKView:)]
#[allow(non_snake_case)]
unsafe fn drawInMTKView(&self, mtk_view: &MTKView) {
idcell!(command_queue <= self);
idcell!(pipeline_state <= self);
idcell!(filter_chain <= self);
unsafe {
mtk_view.setFramebufferOnly(false);
mtk_view.setClearColor(MTLClearColor {
red: 0.3,
blue: 0.5,
green: 0.3,
alpha: 0.0,
});
}
// FIXME: icrate `MTKView` doesn't have a generated binding for `currentDrawable` yet
// (because it needs a definition of `CAMetalDrawable`, which we don't support yet) so
// we have to use a raw `msg_send_id` call here instead.
let current_drawable: Option<Id<ProtocolObject<dyn MTLDrawable>>> =
msg_send_id![mtk_view, currentDrawable];
// prepare for drawing
let Some(current_drawable) = current_drawable else {
return;
};
let Some(command_buffer) = command_queue.commandBuffer() else {
return;
};
let Some(pass_descriptor) = (unsafe { mtk_view.currentRenderPassDescriptor() }) else {
return;
};
let Some(encoder) = command_buffer.renderCommandEncoderWithDescriptor(&pass_descriptor)
else {
return;
};
// compute the scene properties
let scene_properties_data = &SceneProperties {
// time: unsafe { self.ivars().start_date.timeIntervalSinceNow() } as f32,
time: 0.0
};
// write the scene properties to the vertex shader argument buffer at index 0
let scene_properties_bytes = NonNull::from(scene_properties_data);
unsafe {
encoder.setVertexBytes_length_atIndex(
scene_properties_bytes.cast::<core::ffi::c_void>(),
core::mem::size_of_val(scene_properties_data),
0,
)
};
// compute the triangle geometry
let vertex_input_data: &[VertexInput] = &[
VertexInput {
position: Position {
x: -f32::sqrt(3.0) / 4.0,
y: -0.25,
z: 0.,
},
color: Color {
r: 1.,
g: 0.,
b: 0.,
},
},
VertexInput {
position: Position {
x: f32::sqrt(3.0) / 4.0,
y: -0.25,
z: 0.,
},
color: Color {
r: 0.,
g: 1.,
b: 0.,
},
},
VertexInput {
position: Position {
x: 0.,
y: 0.5,
z: 0.,
},
color: Color {
r: 0.,
g: 0.,
b: 1.,
},
},
];
// write the triangle geometry to the vertex shader argument buffer at index 1
let vertex_input_bytes = NonNull::from(vertex_input_data);
unsafe {
encoder.setVertexBytes_length_atIndex(
vertex_input_bytes.cast::<core::ffi::c_void>(),
core::mem::size_of_val(vertex_input_data),
1,
)
};
// configure the encoder with the pipeline and draw the triangle
encoder.setRenderPipelineState(pipeline_state);
unsafe {
encoder.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangle, 0, 3)
};
encoder.endEncoding();
unsafe {
let mut filter_chain = filter_chain.write().unwrap();
let texture = pass_descriptor
.colorAttachments()
.objectAtIndexedSubscript(0)
.texture()
.unwrap();
let tex_desc = MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
texture.pixelFormat(),
texture.width(),
texture.height(),
false
);
tex_desc.setUsage(MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsagePixelFormatView);
// tex_desc.setPixelFormat(MTLPixelFormatRGBA8Unorm);
let frontbuffer = command_queue
.device()
.newTextureWithDescriptor(&tex_desc)
.unwrap();
let backbuffer = command_queue
.device()
.newTextureWithDescriptor(&tex_desc)
.unwrap();
let blit = command_buffer
.blitCommandEncoder()
.unwrap();
blit.copyFromTexture_toTexture(&texture, &frontbuffer);
blit.endEncoding();
filter_chain.frame(&frontbuffer,
&Viewport {
x: 0.0,
y: 0.0,
mvp: None,
output: &backbuffer
}, &command_buffer, 1, None)
.expect("frame");
let blit = command_buffer
.blitCommandEncoder()
.unwrap();
blit.copyFromTexture_toTexture(&backbuffer, &texture);
blit.endEncoding();
}
// schedule the command buffer for display and commit
command_buffer.presentDrawable(&current_drawable);
command_buffer.commit();
}
#[method(mtkView:drawableSizeWillChange:)]
#[allow(non_snake_case)]
unsafe fn mtkView_drawableSizeWillChange(&self, _view: &MTKView, _size: NSSize) {
// println!("mtkView_drawableSizeWillChange");
}
}
);
impl Delegate {
pub fn new(mtm: MainThreadMarker) -> Id<Self> {
let this = mtm.alloc();
let this = this.set_ivars(Ivars {
start_date: unsafe { NSDate::now() },
command_queue: OnceCell::default(),
pipeline_state: OnceCell::default(),
filter_chain: OnceCell::default(),
window: OnceCell::default(),
});
unsafe { msg_send_id![super(this), init] }
}
}
fn main() {
let mtm = MainThreadMarker::new().unwrap();
// configure the app
let app = NSApplication::sharedApplication(mtm);
app.setActivationPolicy(NSApplicationActivationPolicyRegular);
// configure the application delegate
let delegate = Delegate::new(mtm);
let object = ProtocolObject::from_ref(&*delegate);
app.setDelegate(Some(object));
// run the app
unsafe { app.run() };
}

View file

@ -1,7 +1,7 @@
use crate::error::{FilterChainError, Result}; use crate::error::{FilterChainError, Result};
use icrate::Metal::{ use icrate::Metal::{
MTLBlitCommandEncoder, MTLCommandBuffer, MTLDevice, MTLPixelFormat, MTLTexture, MTLBlitCommandEncoder, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLPixelFormat,
MTLTextureDescriptor, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead, MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget, MTLTextureUsageShaderRead,
MTLTextureUsageShaderWrite, MTLTextureUsageShaderWrite,
}; };
use librashader_common::{FilterMode, ImageFormat, Size, WrapMode}; use librashader_common::{FilterMode, ImageFormat, Size, WrapMode};
@ -61,11 +61,13 @@ impl OwnedTexture {
); );
descriptor.setSampleCount(1); descriptor.setSampleCount(1);
descriptor.setMipmapLevelCount(if max_miplevels <= 1 { // descriptor.setMipmapLevelCount(if max_miplevels <= 1 {
size.calculate_miplevels() as usize // size.calculate_miplevels() as usize
} else { // } else {
1 // 1
}); // });
descriptor.setMipmapLevelCount(1);
descriptor.setUsage( descriptor.setUsage(
MTLTextureUsageShaderRead MTLTextureUsageShaderRead
@ -140,9 +142,13 @@ impl OwnedTexture {
// cmd.clear_texture(&self.image, &wgpu::ImageSubresourceRange::default()); // cmd.clear_texture(&self.image, &wgpu::ImageSubresourceRange::default());
} }
/// caller must end the blit encoder after. pub fn generate_mipmaps(&self, cmd: &ProtocolObject<dyn MTLCommandBuffer>) -> Result<()> {
pub fn generate_mipmaps(&self, mipmapper: &ProtocolObject<dyn MTLBlitCommandEncoder>) { let mipmapper = cmd
mipmapper.generateMipmapsForTexture(&self.texture); .blitCommandEncoder()
.ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
// mipmapper.generateMipmapsForTexture(&self.texture);
mipmapper.endEncoding();
Ok(())
} }
} }

View file

@ -1,8 +1,9 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
use core::{cell::OnceCell, ptr::NonNull}; use core::{cell::OnceCell, ptr::NonNull};
use std::sync::RwLock;
use icrate::Metal::MTLClearColor; use icrate::Metal::{MTLBlitCommandEncoder, MTLClearColor, MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget};
use icrate::{ use icrate::{
AppKit::{ AppKit::{
NSApplication, NSApplicationActivationPolicyRegular, NSApplicationDelegate, NSApplication, NSApplicationActivationPolicyRegular, NSApplicationDelegate,
@ -20,6 +21,7 @@ use icrate::{
}, },
MetalKit::{MTKView, MTKViewDelegate}, MetalKit::{MTKView, MTKViewDelegate},
}; };
use librashader_common::Viewport;
use librashader_presets::ShaderPreset; use librashader_presets::ShaderPreset;
use librashader_runtime_metal::FilterChainMetal; use librashader_runtime_metal::FilterChainMetal;
use objc2::{ use objc2::{
@ -123,7 +125,7 @@ struct Ivars {
start_date: Id<NSDate>, start_date: Id<NSDate>,
command_queue: OnceCell<Id<ProtocolObject<dyn MTLCommandQueue>>>, command_queue: OnceCell<Id<ProtocolObject<dyn MTLCommandQueue>>>,
pipeline_state: OnceCell<Id<ProtocolObject<dyn MTLRenderPipelineState>>>, pipeline_state: OnceCell<Id<ProtocolObject<dyn MTLRenderPipelineState>>>,
filter_chain: OnceCell<FilterChainMetal>, filter_chain: OnceCell<RwLock<FilterChainMetal>>,
window: OnceCell<Id<NSWindow>>, window: OnceCell<Id<NSWindow>>,
} }
@ -227,6 +229,8 @@ declare_class!(
) )
.unwrap(); .unwrap();
let filter_chain = RwLock::new(filter_chain);
// configure the metal view delegate // configure the metal view delegate
unsafe { unsafe {
let object = ProtocolObject::from_ref(self); let object = ProtocolObject::from_ref(self);
@ -254,8 +258,10 @@ declare_class!(
unsafe fn drawInMTKView(&self, mtk_view: &MTKView) { unsafe fn drawInMTKView(&self, mtk_view: &MTKView) {
idcell!(command_queue <= self); idcell!(command_queue <= self);
idcell!(pipeline_state <= self); idcell!(pipeline_state <= self);
idcell!(filter_chain <= self);
unsafe { unsafe {
mtk_view.setFramebufferOnly(false);
mtk_view.setClearColor(MTLClearColor { mtk_view.setClearColor(MTLClearColor {
red: 0.3, red: 0.3,
blue: 0.5, blue: 0.5,
@ -280,6 +286,7 @@ declare_class!(
let Some(pass_descriptor) = (unsafe { mtk_view.currentRenderPassDescriptor() }) else { let Some(pass_descriptor) = (unsafe { mtk_view.currentRenderPassDescriptor() }) else {
return; return;
}; };
let Some(encoder) = command_buffer.renderCommandEncoderWithDescriptor(&pass_descriptor) let Some(encoder) = command_buffer.renderCommandEncoderWithDescriptor(&pass_descriptor)
else { else {
return; return;
@ -348,6 +355,7 @@ declare_class!(
) )
}; };
// configure the encoder with the pipeline and draw the triangle // configure the encoder with the pipeline and draw the triangle
encoder.setRenderPipelineState(pipeline_state); encoder.setRenderPipelineState(pipeline_state);
unsafe { unsafe {
@ -355,6 +363,56 @@ declare_class!(
}; };
encoder.endEncoding(); encoder.endEncoding();
unsafe {
let mut filter_chain = filter_chain.write().unwrap();
let texture = pass_descriptor
.colorAttachments()
.objectAtIndexedSubscript(0)
.texture()
.unwrap();
let tex_desc = MTLTextureDescriptor::texture2DDescriptorWithPixelFormat_width_height_mipmapped(
texture.pixelFormat(),
texture.width(),
texture.height(),
false
);
tex_desc.setUsage(MTLTextureUsageRenderTarget);
// let frontbuffer = command_queue
// .device()
// .newTextureWithDescriptor(&tex_desc)
// .unwrap();
let backbuffer = command_queue
.device()
.newTextureWithDescriptor(&tex_desc)
.unwrap();
// let blit = command_buffer
// .blitCommandEncoder()
// .unwrap();
// blit.copyFromTexture_toTexture(&texture, &frontbuffer);
// blit.endEncoding();
filter_chain.frame(&texture,
&Viewport {
x: 0.0,
y: 0.0,
mvp: None,
output: &backbuffer
}, &command_buffer, 1, None)
.expect("frame");
let blit = command_buffer
.blitCommandEncoder()
.unwrap();
blit.copyFromTexture_toTexture(&backbuffer, &texture);
blit.endEncoding();
}
// schedule the command buffer for display and commit // schedule the command buffer for display and commit
command_buffer.presentDrawable(&current_drawable); command_buffer.presentDrawable(&current_drawable);
command_buffer.commit(); command_buffer.commit();