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])*
7pub 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#[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
39pub mod spatial_scaler_color_processing_mode {
41 pub const PERCEPTUAL: isize = 0;
43 pub const LINEAR: isize = 1;
45 pub const HDR: isize = 2;
47}
48
49pub trait FrameInterpolatableScaler {}
51
52#[derive(Debug, Clone, Copy)]
54pub struct SpatialScalerDescriptor {
55 pub color_texture_format: usize,
57 pub output_texture_format: usize,
59 pub input_width: usize,
61 pub input_height: usize,
63 pub output_width: usize,
65 pub output_height: usize,
67 pub color_processing_mode: isize,
69}
70
71impl SpatialScalerDescriptor {
72 #[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 #[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#[allow(clippy::struct_excessive_bools)]
102#[derive(Debug, Clone, Copy)]
103pub struct TemporalScalerDescriptor {
104 pub color_texture_format: usize,
106 pub depth_texture_format: usize,
108 pub motion_texture_format: usize,
110 pub output_texture_format: usize,
112 pub input_width: usize,
114 pub input_height: usize,
116 pub output_width: usize,
118 pub output_height: usize,
120 pub auto_exposure_enabled: bool,
122 pub requires_synchronous_initialization: bool,
124 pub input_content_properties_enabled: bool,
126 pub input_content_min_scale: f32,
128 pub input_content_max_scale: f32,
130 pub reactive_mask_texture_enabled: bool,
132 pub reactive_mask_texture_format: usize,
134}
135
136impl TemporalScalerDescriptor {
137 #[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 #[must_use]
168 pub fn supports_device(device: &MetalDevice) -> bool {
169 unsafe { ffi::am_temporal_scaler_supports_device(device.as_ptr()) }
170 }
171
172 #[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 #[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#[derive(Clone, Copy)]
187pub struct TemporalScalerTextures<'a> {
188 pub color_texture: &'a MetalTexture,
190 pub depth_texture: &'a MetalTexture,
192 pub motion_texture: &'a MetalTexture,
194 pub output_texture: &'a MetalTexture,
196 pub exposure_texture: Option<&'a MetalTexture>,
198 pub reactive_mask_texture: Option<&'a MetalTexture>,
200 pub fence: Option<&'a Fence>,
202}
203
204#[derive(Debug, Clone, Copy)]
206pub struct TemporalScalerFrameState {
207 pub input_content_width: usize,
209 pub input_content_height: usize,
211 pub pre_exposure: f32,
213 pub jitter_offset_x: f32,
215 pub jitter_offset_y: f32,
217 pub motion_vector_scale_x: f32,
219 pub motion_vector_scale_y: f32,
221 pub reset: bool,
223 pub depth_reversed: bool,
225}
226
227impl TemporalScalerFrameState {
228 #[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 pub struct SpatialScaler;
248);
249opaque_metalfx_handle!(
250 pub struct TemporalScaler;
252);
253
254impl FrameInterpolatableScaler for TemporalScaler {}
255
256impl MetalDevice {
257 #[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 #[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 #[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 #[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 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 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 #[must_use]
353 pub fn color_texture_usage(&self) -> usize {
354 self.texture_usage(0)
355 }
356
357 #[must_use]
359 pub fn depth_texture_usage(&self) -> usize {
360 self.texture_usage(1)
361 }
362
363 #[must_use]
365 pub fn motion_texture_usage(&self) -> usize {
366 self.texture_usage(2)
367 }
368
369 #[must_use]
371 pub fn reactive_texture_usage(&self) -> usize {
372 self.texture_usage(3)
373 }
374
375 #[must_use]
377 pub fn output_texture_usage(&self) -> usize {
378 self.texture_usage(4)
379 }
380
381 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 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 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}