rt(mtl): do intermediate passes offscreen
This commit is contained in:
parent
30dfa1a655
commit
43da6e60c6
10 changed files with 633 additions and 66 deletions
|
@ -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"]
|
|
@ -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,
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 {
|
||||
|
|
460
librashader-runtime-metal/src/main.rs
Normal file
460
librashader-runtime-metal/src/main.rs
Normal 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(¤t_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() };
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(¤t_drawable);
|
||||
command_buffer.commit();
|
||||
|
|
Loading…
Add table
Reference in a new issue