Skip to main content

apple_metal/
metalfx.rs

1use crate::{ffi, CommandBuffer, Fence, MetalDevice, MetalTexture};
2use core::ffi::c_void;
3
4macro_rules! opaque_metalfx_handle {
5    ($(#[$meta:meta])* pub struct $name:ident;) => {
6        $(#[$meta])*
7/// Mirrors the `Metal` framework counterpart for this type.
8        pub struct $name {
9            ptr: *mut c_void,
10        }
11
12        impl Drop for $name {
13            fn drop(&mut self) {
14                if !self.ptr.is_null() {
15                    unsafe { ffi::am_object_release(self.ptr) };
16                    self.ptr = core::ptr::null_mut();
17                }
18            }
19        }
20
21        impl $name {
22/// Mirrors the `Metal` framework constant `fn`.
23            #[must_use]
24            pub const fn as_ptr(&self) -> *mut c_void {
25                self.ptr
26            }
27
28            fn wrap(ptr: *mut c_void) -> Option<Self> {
29                if ptr.is_null() {
30                    None
31                } else {
32                    Some(Self { ptr })
33                }
34            }
35        }
36    };
37}
38
39/// `MTLFXSpatialScalerColorProcessingMode` enum values.
40pub mod spatial_scaler_color_processing_mode {
41    /// Mirrors the `Metal` framework constant `PERCEPTUAL`.
42    pub const PERCEPTUAL: isize = 0;
43    /// Mirrors the `Metal` framework constant `LINEAR`.
44    pub const LINEAR: isize = 1;
45    /// Mirrors the `Metal` framework constant `HDR`.
46    pub const HDR: isize = 2;
47}
48
49/// Marker trait for `MetalFX` scalers that conform to `MTLFXFrameInterpolatableScaler`.
50pub trait FrameInterpolatableScaler {}
51
52/// Safe Rust description of `MTLFXSpatialScalerDescriptor`.
53#[derive(Debug, Clone, Copy)]
54pub struct SpatialScalerDescriptor {
55    /// Mirrors the `Metal` framework property for `color_texture_format`.
56    pub color_texture_format: usize,
57    /// Mirrors the `Metal` framework property for `output_texture_format`.
58    pub output_texture_format: usize,
59    /// Mirrors the `Metal` framework property for `input_width`.
60    pub input_width: usize,
61    /// Mirrors the `Metal` framework property for `input_height`.
62    pub input_height: usize,
63    /// Mirrors the `Metal` framework property for `output_width`.
64    pub output_width: usize,
65    /// Mirrors the `Metal` framework property for `output_height`.
66    pub output_height: usize,
67    /// Mirrors the `Metal` framework property for `color_processing_mode`.
68    pub color_processing_mode: isize,
69}
70
71impl SpatialScalerDescriptor {
72    /// Create a `MetalFX` spatial-scaler descriptor.
73    #[must_use]
74    pub const fn new(
75        color_texture_format: usize,
76        output_texture_format: usize,
77        input_width: usize,
78        input_height: usize,
79        output_width: usize,
80        output_height: usize,
81    ) -> Self {
82        Self {
83            color_texture_format,
84            output_texture_format,
85            input_width,
86            input_height,
87            output_width,
88            output_height,
89            color_processing_mode: spatial_scaler_color_processing_mode::PERCEPTUAL,
90        }
91    }
92
93    /// Query whether the given device supports `MetalFX` spatial scaling.
94    #[must_use]
95    pub fn supports_device(device: &MetalDevice) -> bool {
96        unsafe { ffi::am_spatial_scaler_supports_device(device.as_ptr()) }
97    }
98}
99
100/// Safe Rust description of `MTLFXTemporalScalerDescriptor`.
101#[allow(clippy::struct_excessive_bools)]
102#[derive(Debug, Clone, Copy)]
103pub struct TemporalScalerDescriptor {
104    /// Mirrors the `Metal` framework property for `color_texture_format`.
105    pub color_texture_format: usize,
106    /// Mirrors the `Metal` framework property for `depth_texture_format`.
107    pub depth_texture_format: usize,
108    /// Mirrors the `Metal` framework property for `motion_texture_format`.
109    pub motion_texture_format: usize,
110    /// Mirrors the `Metal` framework property for `output_texture_format`.
111    pub output_texture_format: usize,
112    /// Mirrors the `Metal` framework property for `input_width`.
113    pub input_width: usize,
114    /// Mirrors the `Metal` framework property for `input_height`.
115    pub input_height: usize,
116    /// Mirrors the `Metal` framework property for `output_width`.
117    pub output_width: usize,
118    /// Mirrors the `Metal` framework property for `output_height`.
119    pub output_height: usize,
120    /// Mirrors the `Metal` framework property for `auto_exposure_enabled`.
121    pub auto_exposure_enabled: bool,
122    /// Mirrors the `Metal` framework property for `requires_synchronous_initialization`.
123    pub requires_synchronous_initialization: bool,
124    /// Mirrors the `Metal` framework property for `input_content_properties_enabled`.
125    pub input_content_properties_enabled: bool,
126    /// Mirrors the `Metal` framework property for `input_content_min_scale`.
127    pub input_content_min_scale: f32,
128    /// Mirrors the `Metal` framework property for `input_content_max_scale`.
129    pub input_content_max_scale: f32,
130    /// Mirrors the `Metal` framework property for `reactive_mask_texture_enabled`.
131    pub reactive_mask_texture_enabled: bool,
132    /// Mirrors the `Metal` framework property for `reactive_mask_texture_format`.
133    pub reactive_mask_texture_format: usize,
134}
135
136impl TemporalScalerDescriptor {
137    /// Create a `MetalFX` temporal-scaler descriptor.
138    #[must_use]
139    pub const fn new(
140        color_texture_format: usize,
141        depth_texture_format: usize,
142        motion_texture_format: usize,
143        output_texture_format: usize,
144        input_size: (usize, usize),
145        output_size: (usize, usize),
146    ) -> Self {
147        Self {
148            color_texture_format,
149            depth_texture_format,
150            motion_texture_format,
151            output_texture_format,
152            input_width: input_size.0,
153            input_height: input_size.1,
154            output_width: output_size.0,
155            output_height: output_size.1,
156            auto_exposure_enabled: false,
157            requires_synchronous_initialization: false,
158            input_content_properties_enabled: false,
159            input_content_min_scale: 1.0,
160            input_content_max_scale: 1.0,
161            reactive_mask_texture_enabled: false,
162            reactive_mask_texture_format: 0,
163        }
164    }
165
166    /// Query whether the given device supports `MetalFX` temporal scaling.
167    #[must_use]
168    pub fn supports_device(device: &MetalDevice) -> bool {
169        unsafe { ffi::am_temporal_scaler_supports_device(device.as_ptr()) }
170    }
171
172    /// Query the smallest supported temporal scale factor for a device.
173    #[must_use]
174    pub fn supported_input_content_min_scale(device: &MetalDevice) -> f32 {
175        unsafe { ffi::am_temporal_scaler_supported_input_content_min_scale(device.as_ptr()) }
176    }
177
178    /// Query the largest supported temporal scale factor for a device.
179    #[must_use]
180    pub fn supported_input_content_max_scale(device: &MetalDevice) -> f32 {
181        unsafe { ffi::am_temporal_scaler_supported_input_content_max_scale(device.as_ptr()) }
182    }
183}
184
185/// Per-frame bindings for `MTLFXTemporalScaler`.
186#[derive(Clone, Copy)]
187pub struct TemporalScalerTextures<'a> {
188    /// Mirrors the `Metal` framework property for `color_texture`.
189    pub color_texture: &'a MetalTexture,
190    /// Mirrors the `Metal` framework property for `depth_texture`.
191    pub depth_texture: &'a MetalTexture,
192    /// Mirrors the `Metal` framework property for `motion_texture`.
193    pub motion_texture: &'a MetalTexture,
194    /// Mirrors the `Metal` framework property for `output_texture`.
195    pub output_texture: &'a MetalTexture,
196    /// Mirrors the `Metal` framework property for `exposure_texture`.
197    pub exposure_texture: Option<&'a MetalTexture>,
198    /// Mirrors the `Metal` framework property for `reactive_mask_texture`.
199    pub reactive_mask_texture: Option<&'a MetalTexture>,
200    /// Mirrors the `Metal` framework property for `fence`.
201    pub fence: Option<&'a Fence>,
202}
203
204/// Per-frame mutable state for `MTLFXTemporalScaler`.
205#[derive(Debug, Clone, Copy)]
206pub struct TemporalScalerFrameState {
207    /// Mirrors the `Metal` framework property for `input_content_width`.
208    pub input_content_width: usize,
209    /// Mirrors the `Metal` framework property for `input_content_height`.
210    pub input_content_height: usize,
211    /// Mirrors the `Metal` framework property for `pre_exposure`.
212    pub pre_exposure: f32,
213    /// Mirrors the `Metal` framework property for `jitter_offset_x`.
214    pub jitter_offset_x: f32,
215    /// Mirrors the `Metal` framework property for `jitter_offset_y`.
216    pub jitter_offset_y: f32,
217    /// Mirrors the `Metal` framework property for `motion_vector_scale_x`.
218    pub motion_vector_scale_x: f32,
219    /// Mirrors the `Metal` framework property for `motion_vector_scale_y`.
220    pub motion_vector_scale_y: f32,
221    /// Mirrors the `Metal` framework property for `reset`.
222    pub reset: bool,
223    /// Mirrors the `Metal` framework property for `depth_reversed`.
224    pub depth_reversed: bool,
225}
226
227impl TemporalScalerFrameState {
228    /// Create a per-frame state payload with default exposure, jitter, and motion-vector scales.
229    #[must_use]
230    pub const fn new(input_content_width: usize, input_content_height: usize) -> Self {
231        Self {
232            input_content_width,
233            input_content_height,
234            pre_exposure: 1.0,
235            jitter_offset_x: 0.0,
236            jitter_offset_y: 0.0,
237            motion_vector_scale_x: 1.0,
238            motion_vector_scale_y: 1.0,
239            reset: false,
240            depth_reversed: false,
241        }
242    }
243}
244
245opaque_metalfx_handle!(
246    /// Apple's `id<MTLFXSpatialScaler>` — `MetalFX`'s spatial upscaler.
247    pub struct SpatialScaler;
248);
249opaque_metalfx_handle!(
250    /// Apple's `id<MTLFXTemporalScaler>` — `MetalFX`'s temporal upscaler.
251    pub struct TemporalScaler;
252);
253
254impl FrameInterpolatableScaler for TemporalScaler {}
255
256impl MetalDevice {
257    /// Create a `MTLFXSpatialScaler` for this device.
258    #[must_use]
259    pub fn new_spatial_scaler(
260        &self,
261        descriptor: &SpatialScalerDescriptor,
262    ) -> Option<SpatialScaler> {
263        SpatialScaler::wrap(unsafe {
264            ffi::am_device_new_spatial_scaler(
265                self.as_ptr(),
266                descriptor.color_texture_format,
267                descriptor.output_texture_format,
268                descriptor.input_width,
269                descriptor.input_height,
270                descriptor.output_width,
271                descriptor.output_height,
272                descriptor.color_processing_mode,
273            )
274        })
275    }
276
277    /// Create a `MTLFXTemporalScaler` for this device.
278    #[must_use]
279    pub fn new_temporal_scaler(
280        &self,
281        descriptor: &TemporalScalerDescriptor,
282    ) -> Option<TemporalScaler> {
283        TemporalScaler::wrap(unsafe {
284            ffi::am_device_new_temporal_scaler(
285                self.as_ptr(),
286                descriptor.color_texture_format,
287                descriptor.depth_texture_format,
288                descriptor.motion_texture_format,
289                descriptor.output_texture_format,
290                descriptor.input_width,
291                descriptor.input_height,
292                descriptor.output_width,
293                descriptor.output_height,
294                descriptor.auto_exposure_enabled,
295                descriptor.requires_synchronous_initialization,
296                descriptor.input_content_properties_enabled,
297                descriptor.input_content_min_scale,
298                descriptor.input_content_max_scale,
299                descriptor.reactive_mask_texture_enabled,
300                descriptor.reactive_mask_texture_format,
301            )
302        })
303    }
304}
305
306impl SpatialScaler {
307    /// Required texture usage bits for the input color texture.
308    #[must_use]
309    pub fn color_texture_usage(&self) -> usize {
310        unsafe { ffi::am_spatial_scaler_texture_usage(self.as_ptr(), 0) }
311    }
312
313    /// Required texture usage bits for the output texture.
314    #[must_use]
315    pub fn output_texture_usage(&self) -> usize {
316        unsafe { ffi::am_spatial_scaler_texture_usage(self.as_ptr(), 1) }
317    }
318
319    /// Configure the textures and content size for one upscaling pass.
320    pub fn configure(
321        &self,
322        input_content_width: usize,
323        input_content_height: usize,
324        color_texture: &MetalTexture,
325        output_texture: &MetalTexture,
326        fence: Option<&Fence>,
327    ) {
328        unsafe {
329            ffi::am_spatial_scaler_configure(
330                self.as_ptr(),
331                input_content_width,
332                input_content_height,
333                color_texture.as_ptr(),
334                output_texture.as_ptr(),
335                fence.map_or(core::ptr::null_mut(), Fence::as_ptr),
336            );
337        }
338    }
339
340    /// Encode this scaler's work into a command buffer.
341    pub fn encode_to_command_buffer(&self, command_buffer: &CommandBuffer) {
342        unsafe { ffi::am_spatial_scaler_encode(self.as_ptr(), command_buffer.as_ptr()) };
343    }
344}
345
346impl TemporalScaler {
347    fn texture_usage(&self, kind: usize) -> usize {
348        unsafe { ffi::am_temporal_scaler_texture_usage(self.as_ptr(), kind) }
349    }
350
351    /// Required texture usage bits for the color input texture.
352    #[must_use]
353    pub fn color_texture_usage(&self) -> usize {
354        self.texture_usage(0)
355    }
356
357    /// Required texture usage bits for the depth input texture.
358    #[must_use]
359    pub fn depth_texture_usage(&self) -> usize {
360        self.texture_usage(1)
361    }
362
363    /// Required texture usage bits for the motion-vector texture.
364    #[must_use]
365    pub fn motion_texture_usage(&self) -> usize {
366        self.texture_usage(2)
367    }
368
369    /// Required texture usage bits for the reactive-mask texture.
370    #[must_use]
371    pub fn reactive_texture_usage(&self) -> usize {
372        self.texture_usage(3)
373    }
374
375    /// Required texture usage bits for the output texture.
376    #[must_use]
377    pub fn output_texture_usage(&self) -> usize {
378        self.texture_usage(4)
379    }
380
381    /// Bind the textures and optional fence used by this temporal scaler.
382    pub fn set_textures(&self, textures: TemporalScalerTextures<'_>) {
383        unsafe {
384            ffi::am_temporal_scaler_set_textures(
385                self.as_ptr(),
386                textures.color_texture.as_ptr(),
387                textures.depth_texture.as_ptr(),
388                textures.motion_texture.as_ptr(),
389                textures.output_texture.as_ptr(),
390                textures
391                    .exposure_texture
392                    .map_or(core::ptr::null_mut(), MetalTexture::as_ptr),
393                textures
394                    .reactive_mask_texture
395                    .map_or(core::ptr::null_mut(), MetalTexture::as_ptr),
396                textures.fence.map_or(core::ptr::null_mut(), Fence::as_ptr),
397            );
398        }
399    }
400
401    /// Update the temporal scaler's per-frame motion, exposure, and jitter state.
402    pub fn set_frame_state(&self, frame_state: TemporalScalerFrameState) {
403        unsafe {
404            ffi::am_temporal_scaler_set_frame_state(
405                self.as_ptr(),
406                frame_state.input_content_width,
407                frame_state.input_content_height,
408                frame_state.pre_exposure,
409                frame_state.jitter_offset_x,
410                frame_state.jitter_offset_y,
411                frame_state.motion_vector_scale_x,
412                frame_state.motion_vector_scale_y,
413                frame_state.reset,
414                frame_state.depth_reversed,
415            );
416        }
417    }
418
419    /// Encode this scaler's work into a command buffer.
420    pub fn encode_to_command_buffer(&self, command_buffer: &CommandBuffer) {
421        unsafe { ffi::am_temporal_scaler_encode(self.as_ptr(), command_buffer.as_ptr()) };
422    }
423}