Skip to main content

apple_metal/
state.rs

1use crate::{ffi, util::take_optional_string, MetalDevice};
2use core::ffi::c_void;
3use std::ffi::CString;
4
5macro_rules! opaque_state {
6    ($(#[$meta:meta])* pub struct $name:ident;) => {
7        $(#[$meta])*
8/// Mirrors the `Metal` framework counterpart for this type.
9        pub struct $name {
10            ptr: *mut c_void,
11        }
12
13        impl Drop for $name {
14            fn drop(&mut self) {
15                if !self.ptr.is_null() {
16                    unsafe { ffi::am_object_release(self.ptr) };
17                    self.ptr = core::ptr::null_mut();
18                }
19            }
20        }
21
22        impl $name {
23/// Mirrors the `Metal` framework constant `fn`.
24            #[must_use]
25            pub const fn as_ptr(&self) -> *mut c_void {
26                self.ptr
27            }
28
29            fn wrap(ptr: *mut c_void) -> Option<Self> {
30                if ptr.is_null() {
31                    None
32                } else {
33                    Some(Self { ptr })
34                }
35            }
36
37        }
38    };
39}
40
41/// `MTLCompareFunction` enum values.
42pub mod compare_function {
43    /// Mirrors the `Metal` framework constant `NEVER`.
44    pub const NEVER: usize = 0;
45    /// Mirrors the `Metal` framework constant `LESS`.
46    pub const LESS: usize = 1;
47    /// Mirrors the `Metal` framework constant `EQUAL`.
48    pub const EQUAL: usize = 2;
49    /// Mirrors the `Metal` framework constant `LESS_EQUAL`.
50    pub const LESS_EQUAL: usize = 3;
51    /// Mirrors the `Metal` framework constant `GREATER`.
52    pub const GREATER: usize = 4;
53    /// Mirrors the `Metal` framework constant `NOT_EQUAL`.
54    pub const NOT_EQUAL: usize = 5;
55    /// Mirrors the `Metal` framework constant `GREATER_EQUAL`.
56    pub const GREATER_EQUAL: usize = 6;
57    /// Mirrors the `Metal` framework constant `ALWAYS`.
58    pub const ALWAYS: usize = 7;
59}
60
61/// `MTLStencilOperation` enum values.
62pub mod stencil_operation {
63    /// Mirrors the `Metal` framework constant `KEEP`.
64    pub const KEEP: usize = 0;
65    /// Mirrors the `Metal` framework constant `ZERO`.
66    pub const ZERO: usize = 1;
67    /// Mirrors the `Metal` framework constant `REPLACE`.
68    pub const REPLACE: usize = 2;
69    /// Mirrors the `Metal` framework constant `INCREMENT_CLAMP`.
70    pub const INCREMENT_CLAMP: usize = 3;
71    /// Mirrors the `Metal` framework constant `DECREMENT_CLAMP`.
72    pub const DECREMENT_CLAMP: usize = 4;
73    /// Mirrors the `Metal` framework constant `INVERT`.
74    pub const INVERT: usize = 5;
75    /// Mirrors the `Metal` framework constant `INCREMENT_WRAP`.
76    pub const INCREMENT_WRAP: usize = 6;
77    /// Mirrors the `Metal` framework constant `DECREMENT_WRAP`.
78    pub const DECREMENT_WRAP: usize = 7;
79}
80
81/// `MTLSamplerMinMagFilter` enum values.
82pub mod sampler_min_mag_filter {
83    /// Mirrors the `Metal` framework constant `NEAREST`.
84    pub const NEAREST: usize = 0;
85    /// Mirrors the `Metal` framework constant `LINEAR`.
86    pub const LINEAR: usize = 1;
87}
88
89/// `MTLSamplerMipFilter` enum values.
90pub mod sampler_mip_filter {
91    /// Mirrors the `Metal` framework constant `NOT_MIPMAPPED`.
92    pub const NOT_MIPMAPPED: usize = 0;
93    /// Mirrors the `Metal` framework constant `NEAREST`.
94    pub const NEAREST: usize = 1;
95    /// Mirrors the `Metal` framework constant `LINEAR`.
96    pub const LINEAR: usize = 2;
97}
98
99/// `MTLSamplerAddressMode` enum values.
100pub mod sampler_address_mode {
101    /// Mirrors the `Metal` framework constant `CLAMP_TO_EDGE`.
102    pub const CLAMP_TO_EDGE: usize = 0;
103    /// Mirrors the `Metal` framework constant `MIRROR_CLAMP_TO_EDGE`.
104    pub const MIRROR_CLAMP_TO_EDGE: usize = 1;
105    /// Mirrors the `Metal` framework constant `REPEAT`.
106    pub const REPEAT: usize = 2;
107    /// Mirrors the `Metal` framework constant `MIRROR_REPEAT`.
108    pub const MIRROR_REPEAT: usize = 3;
109    /// Mirrors the `Metal` framework constant `CLAMP_TO_ZERO`.
110    pub const CLAMP_TO_ZERO: usize = 4;
111    /// Mirrors the `Metal` framework constant `CLAMP_TO_BORDER_COLOR`.
112    pub const CLAMP_TO_BORDER_COLOR: usize = 5;
113}
114
115/// `MTLSamplerBorderColor` enum values.
116pub mod sampler_border_color {
117    /// Mirrors the `Metal` framework constant `TRANSPARENT_BLACK`.
118    pub const TRANSPARENT_BLACK: usize = 0;
119    /// Mirrors the `Metal` framework constant `OPAQUE_BLACK`.
120    pub const OPAQUE_BLACK: usize = 1;
121    /// Mirrors the `Metal` framework constant `OPAQUE_WHITE`.
122    pub const OPAQUE_WHITE: usize = 2;
123}
124
125/// `MTLSamplerReductionMode` enum values.
126pub mod sampler_reduction_mode {
127    /// Mirrors the `Metal` framework constant `WEIGHTED_AVERAGE`.
128    pub const WEIGHTED_AVERAGE: usize = 0;
129    /// Mirrors the `Metal` framework constant `MINIMUM`.
130    pub const MINIMUM: usize = 1;
131    /// Mirrors the `Metal` framework constant `MAXIMUM`.
132    pub const MAXIMUM: usize = 2;
133}
134
135/// Rust description of `MTLStencilDescriptor`.
136#[derive(Debug, Clone, Copy)]
137pub struct StencilDescriptor {
138    /// Mirrors the `Metal` framework property for `stencil_compare_function`.
139    pub stencil_compare_function: usize,
140    /// Mirrors the `Metal` framework property for `stencil_failure_operation`.
141    pub stencil_failure_operation: usize,
142    /// Mirrors the `Metal` framework property for `depth_failure_operation`.
143    pub depth_failure_operation: usize,
144    /// Mirrors the `Metal` framework property for `depth_stencil_pass_operation`.
145    pub depth_stencil_pass_operation: usize,
146    /// Mirrors the `Metal` framework property for `read_mask`.
147    pub read_mask: u32,
148    /// Mirrors the `Metal` framework property for `write_mask`.
149    pub write_mask: u32,
150}
151
152impl Default for StencilDescriptor {
153    fn default() -> Self {
154        Self {
155            stencil_compare_function: compare_function::ALWAYS,
156            stencil_failure_operation: stencil_operation::KEEP,
157            depth_failure_operation: stencil_operation::KEEP,
158            depth_stencil_pass_operation: stencil_operation::KEEP,
159            read_mask: u32::MAX,
160            write_mask: u32::MAX,
161        }
162    }
163}
164
165impl StencilDescriptor {
166    /// Create a descriptor with Metal's default compare and update behavior.
167    #[must_use]
168    pub const fn new() -> Self {
169        Self {
170            stencil_compare_function: compare_function::ALWAYS,
171            stencil_failure_operation: stencil_operation::KEEP,
172            depth_failure_operation: stencil_operation::KEEP,
173            depth_stencil_pass_operation: stencil_operation::KEEP,
174            read_mask: u32::MAX,
175            write_mask: u32::MAX,
176        }
177    }
178}
179
180/// Rust description of `MTLDepthStencilDescriptor`.
181#[derive(Debug, Clone, Default)]
182pub struct DepthStencilDescriptor {
183    /// Mirrors the `Metal` framework property for `depth_compare_function`.
184    pub depth_compare_function: usize,
185    /// Mirrors the `Metal` framework property for `depth_write_enabled`.
186    pub depth_write_enabled: bool,
187    /// Mirrors the `Metal` framework property for `front_face_stencil`.
188    pub front_face_stencil: Option<StencilDescriptor>,
189    /// Mirrors the `Metal` framework property for `back_face_stencil`.
190    pub back_face_stencil: Option<StencilDescriptor>,
191    /// Mirrors the `Metal` framework property for `label`.
192    pub label: Option<String>,
193}
194
195impl DepthStencilDescriptor {
196    /// Create a descriptor with Metal's default depth-test behavior.
197    #[must_use]
198    pub const fn new() -> Self {
199        Self {
200            depth_compare_function: compare_function::ALWAYS,
201            depth_write_enabled: false,
202            front_face_stencil: None,
203            back_face_stencil: None,
204            label: None,
205        }
206    }
207}
208
209/// Rust description of `MTLSamplerDescriptor`.
210#[derive(Debug, Clone)]
211pub struct SamplerDescriptor {
212    /// Mirrors the `Metal` framework property for `min_filter`.
213    pub min_filter: usize,
214    /// Mirrors the `Metal` framework property for `mag_filter`.
215    pub mag_filter: usize,
216    /// Mirrors the `Metal` framework property for `mip_filter`.
217    pub mip_filter: usize,
218    /// Mirrors the `Metal` framework property for `max_anisotropy`.
219    pub max_anisotropy: usize,
220    /// Mirrors the `Metal` framework property for `s_address_mode`.
221    pub s_address_mode: usize,
222    /// Mirrors the `Metal` framework property for `t_address_mode`.
223    pub t_address_mode: usize,
224    /// Mirrors the `Metal` framework property for `r_address_mode`.
225    pub r_address_mode: usize,
226    /// Mirrors the `Metal` framework property for `border_color`.
227    pub border_color: usize,
228    /// Mirrors the `Metal` framework property for `reduction_mode`.
229    pub reduction_mode: usize,
230    /// Mirrors the `Metal` framework property for `normalized_coordinates`.
231    pub normalized_coordinates: bool,
232    /// Mirrors the `Metal` framework property for `lod_min_clamp`.
233    pub lod_min_clamp: f32,
234    /// Mirrors the `Metal` framework property for `lod_max_clamp`.
235    pub lod_max_clamp: f32,
236    /// Mirrors the `Metal` framework property for `lod_average`.
237    pub lod_average: bool,
238    /// Mirrors the `Metal` framework property for `lod_bias`.
239    pub lod_bias: f32,
240    /// Mirrors the `Metal` framework property for `compare_function`.
241    pub compare_function: usize,
242    /// Mirrors the `Metal` framework property for `support_argument_buffers`.
243    pub support_argument_buffers: bool,
244    /// Mirrors the `Metal` framework property for `label`.
245    pub label: Option<String>,
246}
247
248impl Default for SamplerDescriptor {
249    fn default() -> Self {
250        Self {
251            min_filter: sampler_min_mag_filter::NEAREST,
252            mag_filter: sampler_min_mag_filter::NEAREST,
253            mip_filter: sampler_mip_filter::NOT_MIPMAPPED,
254            max_anisotropy: 1,
255            s_address_mode: sampler_address_mode::CLAMP_TO_EDGE,
256            t_address_mode: sampler_address_mode::CLAMP_TO_EDGE,
257            r_address_mode: sampler_address_mode::CLAMP_TO_EDGE,
258            border_color: sampler_border_color::TRANSPARENT_BLACK,
259            reduction_mode: sampler_reduction_mode::WEIGHTED_AVERAGE,
260            normalized_coordinates: true,
261            lod_min_clamp: 0.0,
262            lod_max_clamp: f32::MAX,
263            lod_average: false,
264            lod_bias: 0.0,
265            compare_function: compare_function::NEVER,
266            support_argument_buffers: false,
267            label: None,
268        }
269    }
270}
271
272impl SamplerDescriptor {
273    /// Create a descriptor with Metal's default sampling behavior.
274    #[must_use]
275    pub fn new() -> Self {
276        Self::default()
277    }
278}
279
280opaque_state!(
281    /// Apple's `id<MTLDepthStencilState>` — compiled depth/stencil test state.
282    pub struct DepthStencilState;
283);
284opaque_state!(
285    /// Apple's `id<MTLSamplerState>` — immutable texture-sampling state.
286    pub struct SamplerState;
287);
288
289impl DepthStencilState {
290    /// Metal's label for this state object, if one was set.
291    #[must_use]
292    pub fn label(&self) -> Option<String> {
293        unsafe { take_optional_string(ffi::am_object_copy_label(self.as_ptr())) }
294    }
295}
296
297impl SamplerState {
298    /// Metal's label for this state object, if one was set.
299    #[must_use]
300    pub fn label(&self) -> Option<String> {
301        unsafe { take_optional_string(ffi::am_object_copy_label(self.as_ptr())) }
302    }
303}
304
305impl MetalDevice {
306    /// Query the device for the supported argument-buffer tier.
307    #[must_use]
308    pub fn argument_buffers_support(&self) -> usize {
309        unsafe { ffi::am_device_argument_buffers_support(self.as_ptr()) }
310    }
311
312    /// Compile a `MTLDepthStencilState` from the given descriptor.
313    #[must_use]
314    pub fn new_depth_stencil_state(
315        &self,
316        descriptor: &DepthStencilDescriptor,
317    ) -> Option<DepthStencilState> {
318        let label = descriptor
319            .label
320            .as_deref()
321            .and_then(|value| CString::new(value).ok());
322        let label_ptr = label
323            .as_deref()
324            .map_or(core::ptr::null(), core::ffi::CStr::as_ptr);
325        let front = descriptor.front_face_stencil.unwrap_or_default();
326        let back = descriptor.back_face_stencil.unwrap_or_default();
327        DepthStencilState::wrap(unsafe {
328            ffi::am_device_new_depth_stencil_state(
329                self.as_ptr(),
330                descriptor.depth_compare_function,
331                descriptor.depth_write_enabled,
332                descriptor.front_face_stencil.is_some(),
333                front.stencil_compare_function,
334                front.stencil_failure_operation,
335                front.depth_failure_operation,
336                front.depth_stencil_pass_operation,
337                front.read_mask,
338                front.write_mask,
339                descriptor.back_face_stencil.is_some(),
340                back.stencil_compare_function,
341                back.stencil_failure_operation,
342                back.depth_failure_operation,
343                back.depth_stencil_pass_operation,
344                back.read_mask,
345                back.write_mask,
346                label_ptr,
347            )
348        })
349    }
350
351    /// Compile a `MTLSamplerState` from the given descriptor.
352    #[must_use]
353    pub fn new_sampler_state(&self, descriptor: &SamplerDescriptor) -> Option<SamplerState> {
354        let label = descriptor
355            .label
356            .as_deref()
357            .and_then(|value| CString::new(value).ok());
358        let label_ptr = label
359            .as_deref()
360            .map_or(core::ptr::null(), core::ffi::CStr::as_ptr);
361        SamplerState::wrap(unsafe {
362            ffi::am_device_new_sampler_state(
363                self.as_ptr(),
364                descriptor.min_filter,
365                descriptor.mag_filter,
366                descriptor.mip_filter,
367                descriptor.max_anisotropy,
368                descriptor.s_address_mode,
369                descriptor.t_address_mode,
370                descriptor.r_address_mode,
371                descriptor.border_color,
372                descriptor.reduction_mode,
373                descriptor.normalized_coordinates,
374                descriptor.lod_min_clamp,
375                descriptor.lod_max_clamp,
376                descriptor.lod_average,
377                descriptor.lod_bias,
378                descriptor.compare_function,
379                descriptor.support_argument_buffers,
380                label_ptr,
381            )
382        })
383    }
384}