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"] }
rayon = "1.8.1"
[dev-dependencies.icrate]
[dependencies.icrate]
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]]
name = "triangle"
@ -40,5 +40,8 @@ harness = false
features = ["librashader-cache/docsrs"]
[target.'cfg(target_vendor="apple")'.dependencies]
icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all" ]}
objc2 = { version = "0.5.0", features = ["apple"] }
#icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all" ]}
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") {
MTLResourceStorageModeShared
} else {
MTLResourceStorageModeManaged
MTLResourceStorageModeShared
};
let buffer = device
@ -35,10 +35,10 @@ impl MetalBuffer {
pub fn flush(&self) {
// We don't know what was actually written to so...
self.buffer.didModifyRange(NSRange {
location: 0,
length: self.size,
})
// self.buffer.didModifyRange(NSRange {
// location: 0,
// length: self.size,
// })
}
}

View file

@ -15,26 +15,46 @@ use crate::graphics_pipeline::VERTEX_BUFFER_INDEX;
#[repr(C)]
#[derive(Debug, Copy, Clone, Default, Zeroable, Pod)]
struct MetalVertex {
position: [f32; 4],
texcoord: [f32; 2],
pub(crate) struct MetalVertex {
pub position: [f32; 4],
pub texcoord: [f32; 2],
}
const FINAL_VBO_DATA: [MetalVertex; 4] = [
const OFFSCREEN_VBO_DATA: [MetalVertex; 4] = [
MetalVertex {
position: [0.0, 1.0, 0.0, 1.0],
position: [-1.0, -1.0, 0.0, 1.0],
texcoord: [0.0, 1.0],
},
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],
},
MetalVertex {
position: [1.0, 1.0, 0.0, 1.0],
texcoord: [1.0, 0.0],
},
];
const FINAL_VBO_DATA: [MetalVertex; 4] = [
MetalVertex {
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],
},
MetalVertex {
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],
},
];
@ -57,7 +77,7 @@ const VBO_DEFAULT_FINAL: [f32; 16] = [
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 {
buffer: Id<ProtocolObject<dyn MTLBuffer>>,
@ -66,6 +86,22 @@ pub struct DrawQuad {
impl DrawQuad {
pub fn new(device: &ProtocolObject<dyn MTLDevice>) -> Result<DrawQuad> {
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 {
device
.newBufferWithBytes_length_options(
@ -93,13 +129,10 @@ impl DrawQuad {
};
unsafe {
cmd.setVertexBuffer_offset_attributeStride_atIndex(
Some(self.buffer.as_ref()),
0,
4 * std::mem::size_of::<f32>(),
VERTEX_BUFFER_INDEX,
);
cmd.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangleStrip, offset, 4);
cmd.setVertexBuffer_offset_atIndex(Some(&self.buffer), 0, VERTEX_BUFFER_INDEX);
cmd.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveTypeTriangleStrip,
offset,
4);
}
}
}

View file

@ -202,8 +202,11 @@ impl FilterChainMetal {
fn push_history(
&mut self,
input: &ProtocolObject<dyn MTLTexture>,
cmd: &ProtocolObject<dyn MTLBlitCommandEncoder>,
cmd: &ProtocolObject<dyn MTLCommandBuffer>,
) -> error::Result<()> {
let mipmapper = cmd
.blitCommandEncoder()
.ok_or(FilterChainError::FailedToCreateCommandBuffer)?;
if let Some(mut back) = self.history_framebuffers.pop_back() {
if back.texture.height() != input.height()
|| 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);
}
mipmapper.endEncoding();
Ok(())
}
@ -317,7 +321,7 @@ impl FilterChainMetal {
/// Records shader rendering commands to the provided command encoder.
pub fn frame(
&mut self,
input: Id<ProtocolObject<dyn MTLTexture>>,
input: &ProtocolObject<dyn MTLTexture>,
viewport: &Viewport<&ProtocolObject<dyn MTLTexture>>,
cmd_buffer: &ProtocolObject<dyn MTLCommandBuffer>,
frame_count: usize,
@ -394,17 +398,14 @@ impl FilterChainMetal {
let (pass, last) = passes.split_at_mut(passes_len - 1);
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() {
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());
let out =
RenderTarget::identity(target.texture.as_ref());
pass.draw(
&cmd_buffer,
index,
@ -419,7 +420,7 @@ impl FilterChainMetal {
)?;
if target.max_miplevels > 1 && !self.disable_mipmaps {
target.generate_mipmaps(&mipmapper);
target.generate_mipmaps(&cmd_buffer)?;
}
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);
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);
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);
}
@ -95,6 +94,11 @@ impl FilterPass {
Some(self.uniform_storage.inner_ubo().as_ref()),
0,
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()),
0,
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 icrate::Foundation::NSString;
use icrate::Metal::{
MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, MTLDevice,
MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat,
MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor,
MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor,
MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture,
MTLVertexAttributeDescriptor, MTLVertexBufferLayoutDescriptor, MTLVertexDescriptor,
MTLVertexFormatFloat2, MTLVertexStepFunctionPerVertex, MTLViewport,
};
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};
use librashader_reflect::back::msl::{CrossMslContext, NagaMslContext};
use librashader_reflect::back::ShaderCompilerOutput;
use librashader_runtime::render_target::RenderTarget;
use objc2::rc::Id;
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.
pub const VERTEX_BUFFER_INDEX: usize = 4;
@ -85,20 +79,20 @@ impl PipelineLayoutObjects {
let texcoord = MTLVertexAttributeDescriptor::new();
// 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.setOffset(0);
position.setOffset(offset_of!(MetalVertex, position));
texcoord.setFormat(MTLVertexFormatFloat2);
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(&texcoord), 1);
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);
descriptor
@ -178,7 +172,8 @@ impl MetalGraphicsPipeline {
) -> Result<Id<ProtocolObject<dyn MTLRenderCommandEncoder>>> {
unsafe {
let descriptor = MTLRenderPassDescriptor::new();
let ca = descriptor.colorAttachments().objectAtIndexedSubscript(0);
let ca = descriptor.colorAttachments()
.objectAtIndexedSubscript(0);
ca.setLoadAction(MTLLoadActionDontCare);
ca.setStoreAction(MTLStoreActionStore);
ca.setTexture(Some(output.output));

View file

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

View file

@ -1,8 +1,9 @@
#![deny(unsafe_op_in_unsafe_fn)]
use core::{cell::OnceCell, ptr::NonNull};
use std::sync::RwLock;
use icrate::Metal::MTLClearColor;
use icrate::Metal::{MTLBlitCommandEncoder, MTLClearColor, MTLTexture, MTLTextureDescriptor, MTLTextureUsageRenderTarget};
use icrate::{
AppKit::{
NSApplication, NSApplicationActivationPolicyRegular, NSApplicationDelegate,
@ -20,6 +21,7 @@ use icrate::{
},
MetalKit::{MTKView, MTKViewDelegate},
};
use librashader_common::Viewport;
use librashader_presets::ShaderPreset;
use librashader_runtime_metal::FilterChainMetal;
use objc2::{
@ -123,7 +125,7 @@ struct Ivars {
start_date: Id<NSDate>,
command_queue: OnceCell<Id<ProtocolObject<dyn MTLCommandQueue>>>,
pipeline_state: OnceCell<Id<ProtocolObject<dyn MTLRenderPipelineState>>>,
filter_chain: OnceCell<FilterChainMetal>,
filter_chain: OnceCell<RwLock<FilterChainMetal>>,
window: OnceCell<Id<NSWindow>>,
}
@ -227,6 +229,8 @@ declare_class!(
)
.unwrap();
let filter_chain = RwLock::new(filter_chain);
// configure the metal view delegate
unsafe {
let object = ProtocolObject::from_ref(self);
@ -254,8 +258,10 @@ declare_class!(
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,
@ -280,6 +286,7 @@ declare_class!(
let Some(pass_descriptor) = (unsafe { mtk_view.currentRenderPassDescriptor() }) else {
return;
};
let Some(encoder) = command_buffer.renderCommandEncoderWithDescriptor(&pass_descriptor)
else {
return;
@ -348,6 +355,7 @@ declare_class!(
)
};
// configure the encoder with the pipeline and draw the triangle
encoder.setRenderPipelineState(pipeline_state);
unsafe {
@ -355,6 +363,56 @@ declare_class!(
};
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
command_buffer.presentDrawable(&current_drawable);
command_buffer.commit();