rt(mtl): fix stride asssignment

This commit is contained in:
chyyran 2024-02-12 00:12:08 -05:00 committed by Ronny Chan
parent a7b1682a37
commit 5554703af7
10 changed files with 433 additions and 18 deletions

4
Cargo.lock generated
View file

@ -1793,9 +1793,9 @@ dependencies = [
[[package]]
name = "librashader-spirv-cross"
version = "0.24.0"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1842c733107d5f223c30a9d9c532d8f4bfd403d51e9ce22c53d6248a0ffa8318"
checksum = "c3576f2a17152fc9b7aa4d7ee9ad8a4c4f4d2117c301d9aea73d8635aa230214"
dependencies = [
"build-target",
"cc",

View file

@ -29,7 +29,7 @@ paste = "1.0.9"
gl = { version = "0.14.0", optional = true }
rustc-hash = "1.1.0"
ash = { version = "0.37", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.24" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" }
[target.'cfg(windows)'.dependencies.windows]
version = "0.48.0"

View file

@ -23,7 +23,7 @@ librashader-common = { path = "../librashader-common", version = "0.2.0-beta.9"
librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.9" }
librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.9" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.24", optional = true }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1", optional = true }
naga = { version = "0.19.0", optional = true }
rspirv = { version = "0.12.0", optional = true }

View file

@ -150,7 +150,7 @@ impl CompileShader<MSL> for NagaReflect {
translation_info: vertex.1,
module: self.vertex,
},
next_free_binding: vertex_binding
next_free_binding: vertex_binding,
},
})
}

View file

@ -18,7 +18,7 @@ librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-
librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.9" }
librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.9" }
librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.9" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.24" }
spirv_cross = { package = "librashader-spirv-cross", version = "0.25.1" }
rustc-hash = "1.1.0"
gl = "0.14.0"

View file

@ -30,6 +30,11 @@ rayon = "1.8.1"
[dev-dependencies]
[[test]]
name = "triangle"
path = "tests/hello_triangle/main.rs"
harness = false
[package.metadata.docs.rs]
features = ["librashader-cache/docsrs"]

View file

@ -93,7 +93,12 @@ impl DrawQuad {
};
unsafe {
cmd.setVertexBuffer_offset_atIndex(Some(self.buffer.as_ref()), 0, VERTEX_BUFFER_INDEX);
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);
}
}

View file

@ -35,6 +35,7 @@ use objc2::runtime::ProtocolObject;
use rayon::prelude::*;
use rustc_hash::FxHashMap;
use std::collections::VecDeque;
use std::fmt::{Debug, Formatter};
use std::path::Path;
type ShaderPassMeta =
@ -60,6 +61,12 @@ pub struct FilterChainMetal {
disable_mipmaps: bool,
}
impl Debug for FilterChainMetal {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("FilterChainMetal"))
}
}
pub struct FilterMutable {
pub passes_enabled: usize,
pub(crate) parameters: FxHashMap<String, f32>,

View file

@ -81,25 +81,25 @@ impl PipelineLayoutObjects {
let binding = MTLVertexBufferLayoutDescriptor::new();
let vertex_0 = MTLVertexAttributeDescriptor::new();
let vertex_1 = MTLVertexAttributeDescriptor::new();
let position = MTLVertexAttributeDescriptor::new();
let texcoord = MTLVertexAttributeDescriptor::new();
// hopefully metal fills in vertices otherwise we'll need to use the vec4 stuff.
vertex_0.setFormat(MTLVertexFormatFloat2);
vertex_0.setBufferIndex(VERTEX_BUFFER_INDEX);
vertex_0.setOffset(0);
position.setFormat(MTLVertexFormatFloat2);
position.setBufferIndex(VERTEX_BUFFER_INDEX);
position.setOffset(0);
vertex_1.setFormat(MTLVertexFormatFloat2);
vertex_1.setBufferIndex(VERTEX_BUFFER_INDEX);
vertex_1.setOffset(2 * std::mem::size_of::<f32>());
texcoord.setFormat(MTLVertexFormatFloat2);
texcoord.setBufferIndex(VERTEX_BUFFER_INDEX);
texcoord.setOffset(2 * std::mem::size_of::<f32>());
attributes.setObject_atIndexedSubscript(Some(&vertex_0), 0);
attributes.setObject_atIndexedSubscript(Some(&position), 0);
attributes.setObject_atIndexedSubscript(Some(&vertex_1), 1);
attributes.setObject_atIndexedSubscript(Some(&texcoord), 1);
binding.setStepFunction(MTLVertexStepFunctionPerVertex);
binding.setStride(4 * std::mem::size_of::<f32>());
layouts.setObject_atIndexedSubscript(Some(&binding), 0);
layouts.setObject_atIndexedSubscript(Some(&binding), VERTEX_BUFFER_INDEX);
descriptor
}

View file

@ -0,0 +1,398 @@
#![deny(unsafe_op_in_unsafe_fn)]
use core::{cell::OnceCell, ptr::NonNull};
use icrate::Metal::MTLClearColor;
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_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<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-royale.slangp").unwrap();
let filter_chain = FilterChainMetal::load_from_preset(
preset,
&command_queue,
None,
)
.unwrap();
// 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);
unsafe {
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,
};
// 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();
// 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() };
}