use std::path::{Path, PathBuf};
use objc2::{
ProtocolType, extern_class, extern_conformance, extern_methods, msg_send,
rc::{Allocated, Retained},
runtime::{AnyObject, NSObject, ProtocolObject},
};
use objc2_foundation::{CopyingHelper, NSCopying, NSObjectProtocol, NSURL};
use crate::{MTLCaptureScope, MTLCommandQueue, MTLDevice, capture_manager::MTLCaptureDestination};
#[derive(Clone, Debug)]
pub enum MTLCaptureTarget {
Device(Retained<ProtocolObject<dyn MTLDevice>>),
CommandQueue(Retained<ProtocolObject<dyn MTLCommandQueue>>),
Scope(Retained<ProtocolObject<dyn MTLCaptureScope>>),
}
impl MTLCaptureTarget {
fn as_any_object(&self) -> &AnyObject {
match self {
Self::Device(target) => protocol_object_as_any(target),
Self::CommandQueue(target) => protocol_object_as_any(target),
Self::Scope(target) => protocol_object_as_any(target),
}
}
}
fn protocol_object_as_any<P: ?Sized + 'static>(object: &ProtocolObject<P>) -> &AnyObject {
<ProtocolObject<P> as AsRef<AnyObject>>::as_ref(object)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UnknownMTLCaptureTarget;
impl core::fmt::Display for UnknownMTLCaptureTarget {
fn fmt(
&self,
formatter: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
formatter.write_str("capture object is not a Metal device, command queue, or capture scope")
}
}
impl std::error::Error for UnknownMTLCaptureTarget {}
fn conforms_to<P: ?Sized + ProtocolType>(object: &AnyObject) -> bool {
let Some(protocol) = P::protocol() else {
return false;
};
unsafe { msg_send![object, conformsToProtocol: protocol] }
}
extern_class!(
#[unsafe(super(NSObject))]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct MTLCaptureDescriptor;
);
extern_conformance!(
unsafe impl NSCopying for MTLCaptureDescriptor {}
);
unsafe impl CopyingHelper for MTLCaptureDescriptor {
type Result = Self;
}
extern_conformance!(
unsafe impl NSObjectProtocol for MTLCaptureDescriptor {}
);
impl MTLCaptureDescriptor {
extern_methods!(
#[unsafe(method(destination))]
#[unsafe(method_family = none)]
pub fn destination(&self) -> MTLCaptureDestination;
#[unsafe(method(setDestination:))]
#[unsafe(method_family = none)]
pub fn set_destination(
&self,
destination: MTLCaptureDestination,
);
);
pub fn capture_object(&self) -> Result<Option<MTLCaptureTarget>, UnknownMTLCaptureTarget> {
let object: Option<Retained<AnyObject>> = unsafe { msg_send![self, captureObject] };
let Some(object) = object else {
return Ok(None);
};
if conforms_to::<dyn MTLDevice>(&object) {
let target = unsafe { Retained::cast_unchecked(object) };
return Ok(Some(MTLCaptureTarget::Device(target)));
}
if conforms_to::<dyn MTLCommandQueue>(&object) {
let target = unsafe { Retained::cast_unchecked(object) };
return Ok(Some(MTLCaptureTarget::CommandQueue(target)));
}
if conforms_to::<dyn MTLCaptureScope>(&object) {
let target = unsafe { Retained::cast_unchecked(object) };
return Ok(Some(MTLCaptureTarget::Scope(target)));
}
Err(UnknownMTLCaptureTarget)
}
pub fn set_capture_object(
&self,
capture_object: Option<&MTLCaptureTarget>,
) {
let capture_object = capture_object.map(MTLCaptureTarget::as_any_object);
unsafe {
let _: () = msg_send![self, setCaptureObject: capture_object];
}
}
pub fn output_path(&self) -> Option<PathBuf> {
let output_url: Option<Retained<NSURL>> = unsafe { msg_send![self, outputURL] };
output_url.and_then(|url| url.to_file_path())
}
pub fn set_output_path(
&self,
output_path: Option<&Path>,
) {
let output_url = output_path.and_then(NSURL::from_file_path);
unsafe {
let _: () = msg_send![self, setOutputURL: output_url.as_deref()];
}
}
}
impl MTLCaptureDescriptor {
extern_methods!(
#[unsafe(method(init))]
#[unsafe(method_family = init)]
pub fn init(this: Allocated<Self>) -> Retained<Self>;
#[unsafe(method(new))]
#[unsafe(method_family = new)]
pub fn new() -> Retained<Self>;
);
}
#[cfg(test)]
mod tests {
use super::{MTLCaptureDescriptor, MTLCaptureTarget, UnknownMTLCaptureTarget};
#[test]
fn capture_object_access_has_rust_native_signatures() {
let _: fn(&MTLCaptureDescriptor) -> Result<Option<MTLCaptureTarget>, UnknownMTLCaptureTarget> =
MTLCaptureDescriptor::capture_object;
let _: fn(&MTLCaptureDescriptor, Option<&MTLCaptureTarget>) = MTLCaptureDescriptor::set_capture_object;
}
}