#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
use crate::ThreadBound;
use crate::foundation::Error;
use crate::metal::{CommandBuffer, Device, PixelFormat, StorageMode, Texture};
use objc2::rc::Retained;
use objc2::runtime::{AnyClass, ProtocolObject};
use objc2::{extern_class, extern_conformance, extern_methods, extern_protocol, sel};
use objc2_foundation::{NSCopying, NSObject, NSObjectProtocol};
use objc2_metal::{MTLCommandBuffer, MTLDevice, MTLTexture, MTLTextureUsage};
extern_protocol!(
unsafe trait MTLFXTemporalScaler: NSObjectProtocol {
#[unsafe(method(encodeToCommandBuffer:))]
#[unsafe(method_family = none)]
fn encodeToCommandBuffer(&self, command_buffer: &ProtocolObject<dyn MTLCommandBuffer>);
#[unsafe(method(colorTextureUsage))]
#[unsafe(method_family = none)]
fn colorTextureUsage(&self) -> MTLTextureUsage;
#[unsafe(method(depthTextureUsage))]
#[unsafe(method_family = none)]
fn depthTextureUsage(&self) -> MTLTextureUsage;
#[unsafe(method(motionTextureUsage))]
#[unsafe(method_family = none)]
fn motionTextureUsage(&self) -> MTLTextureUsage;
#[unsafe(method(outputTextureUsage))]
#[unsafe(method_family = none)]
fn outputTextureUsage(&self) -> MTLTextureUsage;
#[unsafe(method(colorTexture))]
#[unsafe(method_family = none)]
fn colorTexture(&self) -> Option<Retained<ProtocolObject<dyn MTLTexture>>>;
#[unsafe(method(depthTexture))]
#[unsafe(method_family = none)]
fn depthTexture(&self) -> Option<Retained<ProtocolObject<dyn MTLTexture>>>;
#[unsafe(method(motionTexture))]
#[unsafe(method_family = none)]
fn motionTexture(&self) -> Option<Retained<ProtocolObject<dyn MTLTexture>>>;
#[unsafe(method(outputTexture))]
#[unsafe(method_family = none)]
fn outputTexture(&self) -> Option<Retained<ProtocolObject<dyn MTLTexture>>>;
#[unsafe(method(exposureTexture))]
#[unsafe(method_family = none)]
fn exposureTexture(&self) -> Option<Retained<ProtocolObject<dyn MTLTexture>>>;
#[unsafe(method(setExposureTexture:))]
#[unsafe(method_family = none)]
fn setExposureTexture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>);
#[unsafe(method(reactiveMaskTexture))]
#[unsafe(method_family = none)]
fn reactiveMaskTexture(&self) -> Option<Retained<ProtocolObject<dyn MTLTexture>>>;
#[unsafe(method(setReactiveMaskTexture:))]
#[unsafe(method_family = none)]
fn setReactiveMaskTexture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>);
#[unsafe(method(reactiveTextureUsage))]
#[unsafe(method_family = none)]
fn reactiveTextureUsage(&self) -> MTLTextureUsage;
#[unsafe(method(setDepthReversed:))]
#[unsafe(method_family = none)]
fn setDepthReversed(&self, value: bool);
#[unsafe(method(setInputContentWidth:))]
#[unsafe(method_family = none)]
fn setInputContentWidth(&self, width: usize);
#[unsafe(method(setInputContentHeight:))]
#[unsafe(method_family = none)]
fn setInputContentHeight(&self, height: usize);
#[unsafe(method(setColorTexture:))]
#[unsafe(method_family = none)]
fn setColorTexture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>);
#[unsafe(method(setDepthTexture:))]
#[unsafe(method_family = none)]
fn setDepthTexture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>);
#[unsafe(method(setMotionTexture:))]
#[unsafe(method_family = none)]
fn setMotionTexture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>);
#[unsafe(method(setOutputTexture:))]
#[unsafe(method_family = none)]
fn setOutputTexture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>);
}
);
extern_class!(
#[unsafe(super(NSObject))]
pub struct MTLFXTemporalScalerDescriptor;
);
extern_conformance!(
unsafe impl NSObjectProtocol for MTLFXTemporalScalerDescriptor {}
);
extern_conformance!(
unsafe impl NSCopying for MTLFXTemporalScalerDescriptor {}
);
impl MTLFXTemporalScalerDescriptor {
extern_methods!(
#[unsafe(method(new))]
#[unsafe(method_family = new)]
pub fn new() -> Retained<Self>;
#[unsafe(method(setColorTextureFormat:))]
#[unsafe(method_family = none)]
pub fn setColorTextureFormat(&self, format: objc2_metal::MTLPixelFormat);
#[unsafe(method(colorTextureFormat))]
#[unsafe(method_family = none)]
pub fn colorTextureFormat(&self) -> objc2_metal::MTLPixelFormat;
#[unsafe(method(setDepthTextureFormat:))]
#[unsafe(method_family = none)]
pub fn setDepthTextureFormat(&self, format: objc2_metal::MTLPixelFormat);
#[unsafe(method(depthTextureFormat))]
#[unsafe(method_family = none)]
pub fn depthTextureFormat(&self) -> objc2_metal::MTLPixelFormat;
#[unsafe(method(setMotionTextureFormat:))]
#[unsafe(method_family = none)]
pub fn setMotionTextureFormat(&self, format: objc2_metal::MTLPixelFormat);
#[unsafe(method(motionTextureFormat))]
#[unsafe(method_family = none)]
pub fn motionTextureFormat(&self) -> objc2_metal::MTLPixelFormat;
#[unsafe(method(setOutputTextureFormat:))]
#[unsafe(method_family = none)]
pub fn setOutputTextureFormat(&self, format: objc2_metal::MTLPixelFormat);
#[unsafe(method(outputTextureFormat))]
#[unsafe(method_family = none)]
pub fn outputTextureFormat(&self) -> objc2_metal::MTLPixelFormat;
#[unsafe(method(setInputWidth:))]
#[unsafe(method_family = none)]
pub fn setInputWidth(&self, width: usize);
#[unsafe(method(inputWidth))]
#[unsafe(method_family = none)]
pub fn inputWidth(&self) -> usize;
#[unsafe(method(setInputHeight:))]
#[unsafe(method_family = none)]
pub fn setInputHeight(&self, height: usize);
#[unsafe(method(inputHeight))]
#[unsafe(method_family = none)]
pub fn inputHeight(&self) -> usize;
#[unsafe(method(setOutputWidth:))]
#[unsafe(method_family = none)]
pub fn setOutputWidth(&self, width: usize);
#[unsafe(method(outputWidth))]
#[unsafe(method_family = none)]
pub fn outputWidth(&self) -> usize;
#[unsafe(method(setOutputHeight:))]
#[unsafe(method_family = none)]
pub fn setOutputHeight(&self, height: usize);
#[unsafe(method(outputHeight))]
#[unsafe(method_family = none)]
pub fn outputHeight(&self) -> usize;
#[unsafe(method(isAutoExposureEnabled))]
#[unsafe(method_family = none)]
pub fn isAutoExposureEnabled(&self) -> bool;
#[unsafe(method(setAutoExposureEnabled:))]
#[unsafe(method_family = none)]
pub fn setAutoExposureEnabled(&self, value: bool);
#[unsafe(method(isInputContentPropertiesEnabled))]
#[unsafe(method_family = none)]
pub fn isInputContentPropertiesEnabled(&self) -> bool;
#[unsafe(method(setInputContentPropertiesEnabled:))]
#[unsafe(method_family = none)]
pub fn setInputContentPropertiesEnabled(&self, value: bool);
#[unsafe(method(requiresSynchronousInitialization))]
#[unsafe(method_family = none)]
pub fn requiresSynchronousInitialization(&self) -> bool;
#[unsafe(method(setRequiresSynchronousInitialization:))]
#[unsafe(method_family = none)]
pub fn setRequiresSynchronousInitialization(&self, value: bool);
#[unsafe(method(isReactiveMaskTextureEnabled))]
#[unsafe(method_family = none)]
pub fn isReactiveMaskTextureEnabled(&self) -> bool;
#[unsafe(method(setReactiveMaskTextureEnabled:))]
#[unsafe(method_family = none)]
pub fn setReactiveMaskTextureEnabled(&self, value: bool);
#[unsafe(method(reactiveMaskTextureFormat))]
#[unsafe(method_family = none)]
pub fn reactiveMaskTextureFormat(&self) -> objc2_metal::MTLPixelFormat;
#[unsafe(method(setReactiveMaskTextureFormat:))]
#[unsafe(method_family = none)]
pub fn setReactiveMaskTextureFormat(&self, value: objc2_metal::MTLPixelFormat);
#[unsafe(method(inputContentMinScale))]
#[unsafe(method_family = none)]
pub fn inputContentMinScale(&self) -> f32;
#[unsafe(method(setInputContentMinScale:))]
#[unsafe(method_family = none)]
pub fn setInputContentMinScale(&self, value: f32);
#[unsafe(method(inputContentMaxScale))]
#[unsafe(method_family = none)]
pub fn inputContentMaxScale(&self) -> f32;
#[unsafe(method(setInputContentMaxScale:))]
#[unsafe(method_family = none)]
pub fn setInputContentMaxScale(&self, value: f32);
#[unsafe(method(isOutputResolutionMotionVectorsEnabled))]
#[unsafe(method_family = none)]
pub fn isOutputResolutionMotionVectorsEnabled(&self) -> bool;
#[unsafe(method(setOutputResolutionMotionVectorsEnabled:))]
#[unsafe(method_family = none)]
pub fn setOutputResolutionMotionVectorsEnabled(&self, value: bool);
#[unsafe(method(newTemporalScalerWithDevice:))]
#[unsafe(method_family = new)]
pub fn newTemporalScalerWithDevice(
&self,
device: &ProtocolObject<dyn MTLDevice>,
) -> Option<Retained<ProtocolObject<dyn MTLFXTemporalScaler>>>;
#[unsafe(method(supportsDevice:))]
#[unsafe(method_family = none)]
pub fn supportsDevice(device: &ProtocolObject<dyn MTLDevice>) -> bool;
#[unsafe(method(supportsMetal4FX:))]
#[unsafe(method_family = none)]
pub fn supportsMetal4FX(device: &ProtocolObject<dyn MTLDevice>) -> bool;
#[unsafe(method(supportedInputContentMinScaleForDevice:))]
#[unsafe(method_family = none)]
pub fn supportedInputContentMinScaleForDevice(
device: &ProtocolObject<dyn MTLDevice>,
) -> f32;
#[unsafe(method(supportedInputContentMaxScaleForDevice:))]
#[unsafe(method_family = none)]
pub fn supportedInputContentMaxScaleForDevice(
device: &ProtocolObject<dyn MTLDevice>,
) -> f32;
);
}
pub struct TemporalScalerDescriptor {
inner: Retained<MTLFXTemporalScalerDescriptor>,
_thread_bound: ThreadBound,
}
impl TemporalScalerDescriptor {
pub fn new() -> Result<Self, Error> {
if AnyClass::get(c"MTLFXTemporalScalerDescriptor").is_none() {
return Err(Error::unsupported(
"MetalFX temporal scaling is unavailable on this system",
));
}
Ok(Self {
inner: MTLFXTemporalScalerDescriptor::new(),
_thread_bound: ThreadBound::new(),
})
}
#[allow(clippy::too_many_arguments)]
pub fn configure(
&self,
color_format: PixelFormat,
depth_format: PixelFormat,
motion_format: PixelFormat,
output_format: PixelFormat,
input_width: usize,
input_height: usize,
output_width: usize,
output_height: usize,
) -> Result<(), Error> {
if input_width == 0 || input_height == 0 || output_width == 0 || output_height == 0 {
return Err(Error::invalid_argument(
"MetalFX dimensions must be greater than zero",
));
}
self.inner.setColorTextureFormat(color_format.as_objc());
self.inner.setDepthTextureFormat(depth_format.as_objc());
self.inner.setMotionTextureFormat(motion_format.as_objc());
self.inner.setOutputTextureFormat(output_format.as_objc());
self.inner.setInputWidth(input_width);
self.inner.setInputHeight(input_height);
self.inner.setOutputWidth(output_width);
self.inner.setOutputHeight(output_height);
Ok(())
}
#[must_use]
pub fn texture_formats(&self) -> (PixelFormat, PixelFormat, PixelFormat, PixelFormat) {
(
PixelFormat::from_system_raw(self.inner.colorTextureFormat().0),
PixelFormat::from_system_raw(self.inner.depthTextureFormat().0),
PixelFormat::from_system_raw(self.inner.motionTextureFormat().0),
PixelFormat::from_system_raw(self.inner.outputTextureFormat().0),
)
}
pub fn set_texture_formats(
&self,
color: PixelFormat,
depth: PixelFormat,
motion: PixelFormat,
output: PixelFormat,
) {
self.inner.setColorTextureFormat(color.as_objc());
self.inner.setDepthTextureFormat(depth.as_objc());
self.inner.setMotionTextureFormat(motion.as_objc());
self.inner.setOutputTextureFormat(output.as_objc());
}
#[must_use]
pub fn input_size(&self) -> (usize, usize) {
(self.inner.inputWidth(), self.inner.inputHeight())
}
pub fn set_input_size(&self, width: usize, height: usize) -> Result<(), Error> {
if width == 0 || height == 0 {
return Err(Error::invalid_argument(
"MetalFX input dimensions must be greater than zero",
));
}
self.inner.setInputWidth(width);
self.inner.setInputHeight(height);
Ok(())
}
#[must_use]
pub fn output_size(&self) -> (usize, usize) {
(self.inner.outputWidth(), self.inner.outputHeight())
}
pub fn set_output_size(&self, width: usize, height: usize) -> Result<(), Error> {
if width == 0 || height == 0 {
return Err(Error::invalid_argument(
"MetalFX output dimensions must be greater than zero",
));
}
self.inner.setOutputWidth(width);
self.inner.setOutputHeight(height);
Ok(())
}
#[must_use]
pub fn auto_exposure_enabled(&self) -> bool {
self.inner.isAutoExposureEnabled()
}
pub fn set_auto_exposure_enabled(&self, value: bool) {
self.inner.setAutoExposureEnabled(value);
}
#[must_use]
pub fn input_content_properties_enabled(&self) -> bool {
self.inner.isInputContentPropertiesEnabled()
}
pub fn set_input_content_properties_enabled(&self, value: bool) {
self.inner.setInputContentPropertiesEnabled(value);
}
#[must_use]
pub fn requires_synchronous_initialization(&self) -> bool {
self.inner.requiresSynchronousInitialization()
}
pub fn set_requires_synchronous_initialization(&self, value: bool) {
self.inner.setRequiresSynchronousInitialization(value);
}
#[must_use]
pub fn reactive_mask_texture_enabled(&self) -> bool {
self.inner.isReactiveMaskTextureEnabled()
}
pub fn set_reactive_mask_texture_enabled(&self, value: bool) {
self.inner.setReactiveMaskTextureEnabled(value);
}
#[must_use]
pub fn reactive_mask_texture_format(&self) -> PixelFormat {
PixelFormat::from_system_raw(self.inner.reactiveMaskTextureFormat().0)
}
pub fn set_reactive_mask_texture_format(&self, value: PixelFormat) {
self.inner.setReactiveMaskTextureFormat(value.as_objc());
}
#[must_use]
pub fn input_content_scale_range(&self) -> (f32, f32) {
(
self.inner.inputContentMinScale(),
self.inner.inputContentMaxScale(),
)
}
pub fn set_input_content_scale_range(&self, min: f32, max: f32) -> Result<(), Error> {
if !min.is_finite() || !max.is_finite() || min <= 0.0 || max < min {
return Err(Error::invalid_argument(
"MetalFX input scale range must be finite, positive, and ordered",
));
}
self.inner.setInputContentMinScale(min);
self.inner.setInputContentMaxScale(max);
Ok(())
}
#[must_use]
pub fn output_resolution_motion_vectors_enabled(&self) -> bool {
self.inner.isOutputResolutionMotionVectorsEnabled()
}
pub fn set_output_resolution_motion_vectors_enabled(&self, value: bool) {
self.inner.setOutputResolutionMotionVectorsEnabled(value);
}
#[must_use]
pub fn supports_metal4_fx(&self, device: &Device) -> bool {
MTLFXTemporalScalerDescriptor::supportsMetal4FX(&device.inner)
}
#[must_use]
pub fn supports_device(&self, device: &Device) -> bool {
MTLFXTemporalScalerDescriptor::supportsDevice(&device.inner)
}
pub fn supported_input_content_min_scale(&self, device: &Device) -> Result<f32, Error> {
let class = AnyClass::get(c"MTLFXTemporalScalerDescriptor").ok_or_else(|| {
Error::unsupported("MetalFX temporal scaling is unavailable on this system")
})?;
if !class
.metaclass()
.responds_to(sel!(supportedInputContentMinScaleForDevice:))
{
return Err(Error::unsupported(
"MetalFX temporal scale queries require macOS 14 or newer",
));
}
Ok(MTLFXTemporalScalerDescriptor::supportedInputContentMinScaleForDevice(&device.inner))
}
pub fn supported_input_content_max_scale(&self, device: &Device) -> Result<f32, Error> {
let class = AnyClass::get(c"MTLFXTemporalScalerDescriptor").ok_or_else(|| {
Error::unsupported("MetalFX temporal scaling is unavailable on this system")
})?;
if !class
.metaclass()
.responds_to(sel!(supportedInputContentMaxScaleForDevice:))
{
return Err(Error::unsupported(
"MetalFX temporal scale queries require macOS 14 or newer",
));
}
Ok(MTLFXTemporalScalerDescriptor::supportedInputContentMaxScaleForDevice(&device.inner))
}
pub fn new_scaler(&self, device: &Device) -> Result<TemporalScaler, Error> {
self.inner
.newTemporalScalerWithDevice(&device.inner)
.map(|inner| TemporalScaler {
inner,
_thread_bound: ThreadBound::new(),
})
.ok_or_else(|| Error::unsupported("MetalFX temporal scaling is unavailable"))
}
}
pub struct TemporalScaler {
inner: Retained<ProtocolObject<dyn MTLFXTemporalScaler>>,
_thread_bound: ThreadBound,
}
impl TemporalScaler {
pub fn encode(&self, command_buffer: &CommandBuffer) {
self.inner.encodeToCommandBuffer(&command_buffer.inner);
}
#[must_use]
pub fn color_texture_usage(&self) -> u64 {
self.inner.colorTextureUsage().0 as u64
}
#[must_use]
pub fn output_texture_usage(&self) -> u64 {
self.inner.outputTextureUsage().0 as u64
}
#[must_use]
pub fn depth_texture_usage(&self) -> u64 {
self.inner.depthTextureUsage().0 as u64
}
#[must_use]
pub fn motion_texture_usage(&self) -> u64 {
self.inner.motionTextureUsage().0 as u64
}
#[must_use]
pub fn textures(
&self,
) -> (
Option<Texture>,
Option<Texture>,
Option<Texture>,
Option<Texture>,
) {
(
self.inner.colorTexture().map(Texture::new),
self.inner.depthTexture().map(Texture::new),
self.inner.motionTexture().map(Texture::new),
self.inner.outputTexture().map(Texture::new),
)
}
#[must_use]
pub fn exposure_texture(&self) -> Option<Texture> {
self.inner.exposureTexture().map(Texture::new)
}
pub fn set_exposure_texture(&self, value: Option<&Texture>) {
self.inner
.setExposureTexture(value.map(|texture| &*texture.inner));
}
#[must_use]
pub fn reactive_mask_texture(&self) -> Option<Texture> {
self.inner.reactiveMaskTexture().map(Texture::new)
}
pub fn set_reactive_mask_texture(&self, value: Option<&Texture>) {
self.inner
.setReactiveMaskTexture(value.map(|texture| &*texture.inner));
}
#[must_use]
pub fn reactive_texture_usage(&self) -> u64 {
self.inner.reactiveTextureUsage().0 as u64
}
pub fn set_depth_reversed(&self, value: bool) {
self.inner.setDepthReversed(value);
}
pub fn set_input_content_size(&self, width: usize, height: usize) -> Result<(), Error> {
if width == 0 || height == 0 {
return Err(Error::invalid_argument(
"MetalFX input content dimensions must be greater than zero",
));
}
self.inner.setInputContentWidth(width);
self.inner.setInputContentHeight(height);
Ok(())
}
pub fn set_textures(
&self,
color: Option<&Texture>,
depth: Option<&Texture>,
motion: Option<&Texture>,
output: Option<&Texture>,
) -> Result<(), Error> {
if let Some(texture) = output
&& texture.storage_mode() != StorageMode::Private
{
return Err(Error::invalid_argument(
"MetalFX output texture must use private storage",
));
}
self.inner
.setColorTexture(color.map(|texture| &*texture.inner));
self.inner
.setDepthTexture(depth.map(|texture| &*texture.inner));
self.inner
.setMotionTexture(motion.map(|texture| &*texture.inner));
self.inner
.setOutputTexture(output.map(|texture| &*texture.inner));
Ok(())
}
}