saxaboom_runtime/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[expect(
4    clippy::missing_safety_doc,
5    clippy::ptr_offset_with_cast,
6    clippy::useless_transmute,
7    non_camel_case_types,
8    non_snake_case,
9    non_upper_case_globals
10)]
11pub mod bindings {
12    // Use an include to be able to import more items into this module as below
13    include!("bindings.rs");
14
15    pub use objc2_metal::MTLResourceID;
16}
17pub use bindings as ffi;
18
19use std::ptr::NonNull;
20
21use objc2::runtime::ProtocolObject;
22use objc2_metal::{
23    MTLBuffer, MTLIndexType, MTLPrimitiveType, MTLRenderCommandEncoder, MTLSamplerState, MTLTexture,
24};
25
26/// Rust version of `IRBufferView` using [`metal`] types.
27#[doc(alias = "IRBufferView")]
28pub struct BufferView<'a> {
29    pub buffer: &'a ProtocolObject<dyn MTLBuffer>,
30    pub buffer_offset: u64,
31    pub buffer_size: u64,
32    pub texture_buffer_view: Option<&'a ProtocolObject<dyn MTLTexture>>,
33    pub texture_view_offset_in_elements: u32,
34    pub typed_buffer: bool,
35}
36
37impl ffi::IRDescriptorTableEntry {
38    /// Encode a buffer descriptor.
39    ///
40    /// This function is a port of the `IRDescriptorTableSetBuffer` function in the `metal_irconverter_runtime.h` header.
41    /// See <https://developer.apple.com/metal/shader-converter/> for more info.
42    // TODO: This function seems to have no reason to exist, in favour of `buffer_view()` the
43    // `metadata` argument here needs to be constructed in the exact same way.  However, for a
44    // full buffer descriptor, setting the metadata to `0` seems to be fine?
45    // TODO: The docs say  "buffer view" for metadata: can we take a BufferView struct and set
46    // `Self::buffer_metadata()` instead? There are special constructors for atomic/counter buffers
47    // after all...
48    #[doc(alias = "IRDescriptorTableSetBuffer")]
49    pub fn buffer(gpu_address: u64, metadata: u64) -> Self {
50        Self {
51            gpuVA: gpu_address,
52            textureViewID: 0,
53            metadata,
54        }
55    }
56
57    /// Encode a buffer view descriptor.
58    ///
59    /// This function is a port of the `IRDescriptorTableSetBufferView` function in the `metal_irconverter_runtime.h` header.
60    /// See <https://developer.apple.com/metal/shader-converter/> for more info.
61    #[doc(alias = "IRDescriptorTableSetBufferView")]
62    pub fn buffer_view(buffer_view: &BufferView<'_>) -> Self {
63        Self {
64            gpuVA: buffer_view.buffer.gpuAddress() + buffer_view.buffer_offset,
65            textureViewID: match buffer_view.texture_buffer_view {
66                Some(texture) => unsafe { texture.gpuResourceID() }.to_raw(),
67                None => 0,
68            },
69            metadata: Self::buffer_metadata(buffer_view),
70        }
71    }
72
73    /// Encode a texture in this descriptor.
74    ///
75    /// This function is a port of the `IRDescriptorTableSetTexture` function in the `metal_irconverter_runtime.h` header.
76    /// See <https://developer.apple.com/metal/shader-converter/> for more info.
77    #[doc(alias = "IRDescriptorTableSetTexture")]
78    pub fn texture(argument: &ProtocolObject<dyn MTLTexture>, min_lod_clamp: f32) -> Self {
79        const METADATA: u32 = 0; // According to the current docs, the metadata must be 0
80        Self {
81            gpuVA: 0,
82            textureViewID: unsafe { argument.gpuResourceID() }.to_raw(),
83            metadata: min_lod_clamp.to_bits() as u64 | ((METADATA as u64) << 32),
84        }
85    }
86
87    /// Encode a sampler in this descriptor.
88    ///
89    /// This function is a port of the `IRDescriptorTableSetSampler` function in the `metal_irconverter_runtime.h` header.
90    /// See <https://developer.apple.com/metal/shader-converter/> for more info.
91    #[doc(alias = "IRDescriptorTableSetSampler")]
92    pub fn sampler(argument: &ProtocolObject<dyn MTLSamplerState>, lod_bias: f32) -> Self {
93        Self {
94            gpuVA: unsafe { argument.gpuResourceID() }.to_raw(),
95            textureViewID: 0,
96            metadata: lod_bias.to_bits() as u64,
97        }
98    }
99
100    /// Encode an acceleration structure in this descriptor.
101    ///
102    /// This function is a port of the `IRDescriptorTableSetAccelerationStructure` function in the `ir_raytracing.h` header.
103    /// See <https://developer.apple.com/metal/shader-converter/> for more info.
104    #[doc(alias = "IRDescriptorTableSetAccelerationStructure")]
105    pub fn acceleration_structure(gpu_address: u64) -> Self {
106        Self {
107            gpuVA: gpu_address,
108            textureViewID: 0,
109            metadata: 0,
110        }
111    }
112
113    /// Get the metadata value for a buffer view.
114    ///
115    /// This function is a port of the `IRDescriptorTableGetBufferMetadata` function in the `metal_irconverter_runtime.h` header.
116    /// See <https://developer.apple.com/metal/shader-converter/> for more info.
117    #[doc(alias = "IRDescriptorTableGetBufferMetadata")]
118    pub fn buffer_metadata(view: &BufferView<'_>) -> u64 {
119        let mut metadata = (view.buffer_size & ffi::kIRBufSizeMask) << ffi::kIRBufSizeOffset;
120        metadata |= (view.texture_view_offset_in_elements as u64 & ffi::kIRTexViewMask)
121            << ffi::kIRTexViewOffset;
122        metadata |= (view.typed_buffer as u64) << ffi::kIRTypedBufferOffset;
123        metadata
124    }
125}
126
127#[doc(alias = "IRRuntimeDrawPrimitives")]
128pub fn draw_primitives(
129    encoder: &ProtocolObject<dyn MTLRenderCommandEncoder>,
130    primitive_type: MTLPrimitiveType,
131    vertex_start: usize,
132    vertex_count: usize,
133    instance_count: usize,
134    base_instance: usize,
135) {
136    let mut dp = ffi::IRRuntimeDrawParams {
137        u_1: ffi::IRRuntimeDrawParams_u {
138            draw: ffi::IRRuntimeDrawArgument {
139                vertexCountPerInstance: vertex_count as u32,
140                instanceCount: instance_count as u32,
141                startVertexLocation: vertex_start as u32,
142                startInstanceLocation: base_instance as u32,
143            },
144        },
145    };
146    unsafe {
147        encoder.setVertexBytes_length_atIndex(
148            NonNull::new(&raw mut dp).unwrap().cast(),
149            size_of_val(&dp),
150            ffi::kIRArgumentBufferDrawArgumentsBindPoint as usize,
151        );
152        let mut non_indexed_draw = ffi::kIRNonIndexedDraw;
153        encoder.setVertexBytes_length_atIndex(
154            NonNull::new(&raw mut non_indexed_draw).unwrap().cast(),
155            size_of_val(&non_indexed_draw),
156            ffi::kIRArgumentBufferUniformsBindPoint as usize,
157        );
158        encoder.drawPrimitives_vertexStart_vertexCount_instanceCount_baseInstance(
159            primitive_type,
160            vertex_start,
161            vertex_count,
162            instance_count,
163            base_instance,
164        );
165    }
166}
167
168#[doc(alias = "IRMetalIndexToIRIndex")]
169pub fn metal_index_to_ir_index(index_type: MTLIndexType) -> u16 {
170    index_type.0 as u16 + 1
171}
172
173#[doc(alias = "IRRuntimeDrawIndexedPrimitives")]
174#[expect(clippy::too_many_arguments)]
175pub fn draw_indexed_primitives(
176    encoder: &ProtocolObject<dyn MTLRenderCommandEncoder>,
177    primitive_type: MTLPrimitiveType,
178    index_count: usize,
179    index_type: MTLIndexType,
180    index_buffer: &ProtocolObject<dyn MTLBuffer>,
181    index_buffer_offset: usize,
182    instance_count: usize,
183    base_vertex: isize,
184    base_instance: usize,
185) {
186    let mut dp = ffi::IRRuntimeDrawParams {
187        u_1: ffi::IRRuntimeDrawParams_u {
188            drawIndexed: ffi::IRRuntimeDrawIndexedArgument {
189                indexCountPerInstance: index_count as u32,
190                instanceCount: instance_count as u32,
191                startIndexLocation: index_buffer_offset as u32,
192                baseVertexLocation: base_vertex as i32,
193                startInstanceLocation: base_instance as u32,
194            },
195        },
196    };
197    let mut ir_index_type = metal_index_to_ir_index(index_type);
198    unsafe {
199        encoder.setVertexBytes_length_atIndex(
200            NonNull::new(&raw mut dp).unwrap().cast(),
201            size_of_val(&dp),
202            ffi::kIRArgumentBufferDrawArgumentsBindPoint as usize,
203        );
204        encoder.setVertexBytes_length_atIndex(
205            NonNull::new(&raw mut ir_index_type).unwrap().cast(),
206            size_of_val(&ir_index_type),
207            ffi::kIRArgumentBufferUniformsBindPoint as usize,
208        );
209        encoder.drawIndexedPrimitives_indexCount_indexType_indexBuffer_indexBufferOffset_instanceCount_baseVertex_baseInstance(
210            primitive_type,
211            index_count,
212            index_type,
213            index_buffer,
214            index_buffer_offset,
215            instance_count,
216            base_vertex,
217            base_instance,
218        );
219    }
220}
221
222#[doc(alias = "IRRuntimeDrawIndexedPrimitives")]
223pub fn draw_indexed_primitives_indirect(
224    encoder: &ProtocolObject<dyn MTLRenderCommandEncoder>,
225    primitive_type: MTLPrimitiveType,
226    index_type: MTLIndexType,
227    index_buffer: &ProtocolObject<dyn MTLBuffer>,
228    index_buffer_offset: usize,
229    indirect_buffer: &ProtocolObject<dyn MTLBuffer>,
230    indirect_buffer_offset: usize,
231) {
232    let mut ir_index_type = metal_index_to_ir_index(index_type);
233
234    unsafe {
235        encoder.setVertexBuffer_offset_atIndex(
236            Some(indirect_buffer),
237            0,
238            ffi::kIRArgumentBufferDrawArgumentsBindPoint as usize,
239        );
240        encoder.setVertexBytes_length_atIndex(
241            NonNull::new(&raw mut ir_index_type).unwrap().cast(),
242            size_of_val(&ir_index_type),
243            ffi::kIRArgumentBufferUniformsBindPoint as usize,
244        );
245        encoder.drawIndexedPrimitives_indexType_indexBuffer_indexBufferOffset_indirectBuffer_indirectBufferOffset(
246            primitive_type,
247            index_type,
248            index_buffer,
249            index_buffer_offset,
250            indirect_buffer,
251            indirect_buffer_offset
252        );
253    }
254}