#![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 crate::metal_fx::SpatialScalerColorProcessingMode;
use objc2::rc::Retained;
use objc2::runtime::{AnyClass, ProtocolObject};
use objc2::{extern_class, extern_conformance, extern_methods, extern_protocol};
use objc2_foundation::{NSCopying, NSObject, NSObjectProtocol};
use objc2_metal::{MTLCommandBuffer, MTLDevice, MTLTexture, MTLTextureUsage};
extern_protocol!(
unsafe trait MTLFXSpatialScaler: 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(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(outputTexture))]
#[unsafe(method_family = none)]
fn outputTexture(&self) -> Option<Retained<ProtocolObject<dyn MTLTexture>>>;
#[unsafe(method(colorProcessingMode))]
#[unsafe(method_family = none)]
fn colorProcessingMode(&self) -> isize;
#[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(setOutputTexture:))]
#[unsafe(method_family = none)]
fn setOutputTexture(&self, texture: Option<&ProtocolObject<dyn MTLTexture>>);
}
);
extern_class!(
#[unsafe(super(NSObject))]
pub struct MTLFXSpatialScalerDescriptor;
);
extern_conformance!(
unsafe impl NSObjectProtocol for MTLFXSpatialScalerDescriptor {}
);
extern_conformance!(
unsafe impl NSCopying for MTLFXSpatialScalerDescriptor {}
);
impl MTLFXSpatialScalerDescriptor {
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(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(colorProcessingMode))]
#[unsafe(method_family = none)]
pub fn colorProcessingMode(&self) -> isize;
#[unsafe(method(setColorProcessingMode:))]
#[unsafe(method_family = none)]
pub fn setColorProcessingMode(&self, mode: isize);
#[unsafe(method(newSpatialScalerWithDevice:))]
#[unsafe(method_family = new)]
pub fn newSpatialScalerWithDevice(
&self,
device: &ProtocolObject<dyn MTLDevice>,
) -> Option<Retained<ProtocolObject<dyn MTLFXSpatialScaler>>>;
#[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;
);
}
pub struct SpatialScalerDescriptor {
inner: Retained<MTLFXSpatialScalerDescriptor>,
_thread_bound: ThreadBound,
}
impl SpatialScalerDescriptor {
pub fn new() -> Result<Self, Error> {
if AnyClass::get(c"MTLFXSpatialScalerDescriptor").is_none() {
return Err(Error::unsupported(
"MetalFX spatial scaling is unavailable on this system",
));
}
Ok(Self {
inner: MTLFXSpatialScalerDescriptor::new(),
_thread_bound: ThreadBound::new(),
})
}
pub fn configure(
&self,
color_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.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 color_texture_format(&self) -> PixelFormat {
PixelFormat::from_system_raw(self.inner.colorTextureFormat().0)
}
pub fn set_color_texture_format(&self, value: PixelFormat) {
self.inner.setColorTextureFormat(value.as_objc());
}
#[must_use]
pub fn output_texture_format(&self) -> PixelFormat {
PixelFormat::from_system_raw(self.inner.outputTextureFormat().0)
}
pub fn set_output_texture_format(&self, value: PixelFormat) {
self.inner.setOutputTextureFormat(value.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(())
}
pub fn color_processing_mode(&self) -> Result<SpatialScalerColorProcessingMode, Error> {
SpatialScalerColorProcessingMode::try_from(self.inner.colorProcessingMode())
.map_err(|()| Error::unsupported("MetalFX returned an unknown color-processing mode"))
}
pub fn set_color_processing_mode(&self, value: SpatialScalerColorProcessingMode) {
self.inner.setColorProcessingMode(value.as_raw());
}
#[must_use]
pub fn supports_metal4_fx(&self, device: &Device) -> bool {
MTLFXSpatialScalerDescriptor::supportsMetal4FX(&device.inner)
}
#[must_use]
pub fn supports_device(&self, device: &Device) -> bool {
MTLFXSpatialScalerDescriptor::supportsDevice(&device.inner)
}
pub fn new_scaler(&self, device: &Device) -> Result<SpatialScaler, Error> {
self.inner
.newSpatialScalerWithDevice(&device.inner)
.map(|inner| SpatialScaler {
inner,
_thread_bound: ThreadBound::new(),
})
.ok_or_else(|| Error::unsupported("MetalFX spatial scaling is unavailable"))
}
}
pub struct SpatialScaler {
inner: Retained<ProtocolObject<dyn MTLFXSpatialScaler>>,
_thread_bound: ThreadBound,
}
impl SpatialScaler {
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 color_texture(&self) -> Option<Texture> {
self.inner.colorTexture().map(Texture::new)
}
#[must_use]
pub fn output_texture(&self) -> Option<Texture> {
self.inner.outputTexture().map(Texture::new)
}
pub fn color_processing_mode(&self) -> Result<SpatialScalerColorProcessingMode, Error> {
SpatialScalerColorProcessingMode::try_from(self.inner.colorProcessingMode())
.map_err(|()| Error::unsupported("MetalFX returned an unknown color-processing mode"))
}
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>,
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
.setOutputTexture(output.map(|texture| &*texture.inner));
Ok(())
}
}