use super::{CaptureDescriptor, CaptureDestination, CaptureScope, CommandQueue, Device};
use crate::Error;
use crate::metal4::CommandQueue as Metal4CommandQueue;
use std::path::{Path, PathBuf};
fn capture_path(path: Option<&Path>) -> Result<Option<&str>, Error> {
path.map(|path| {
if !path.is_absolute() {
return Err(Error::invalid_argument(
"capture output path must be absolute",
));
}
let path = path
.to_str()
.ok_or_else(|| Error::invalid_argument("capture output path must be UTF-8"))?;
if path.as_bytes().contains(&0) {
return Err(Error::invalid_argument("capture output path contains NUL"));
}
Ok(path)
})
.transpose()
}
impl CaptureDescriptor {
pub fn capture_target(&self) -> Result<Option<CaptureTarget>, Error> {
if let Some(device) = self.inner.capture_device().map_err(Error::from_ffi)? {
return Ok(Some(CaptureTarget::Device(Device::from_ffi(device))));
}
if let Some(queue) = self
.inner
.capture_command_queue()
.map_err(Error::from_ffi)?
{
return Ok(Some(CaptureTarget::CommandQueue(CommandQueue::from_ffi(
queue,
))));
}
if let Some(scope) = self.inner.capture_scope().map_err(Error::from_ffi)? {
return Ok(Some(CaptureTarget::Scope(CaptureScope::from_ffi(scope))));
}
self.inner
.validate_capture_object_type()
.map_err(Error::from_ffi)?;
Ok(None)
}
pub fn set_capture_device(&self, device: &Device) -> Result<(), Error> {
self.inner
.set_capture_device(&device.inner)
.map_err(Error::from_ffi)
}
pub fn set_capture_command_queue(&self, queue: &CommandQueue) -> Result<(), Error> {
self.inner
.set_capture_command_queue(&queue.inner)
.map_err(Error::from_ffi)
}
pub fn set_capture_scope(&self, scope: &CaptureScope) -> Result<(), Error> {
self.inner
.set_capture_scope(&scope.inner)
.map_err(Error::from_ffi)
}
pub fn set_capture_output(
&self,
destination: CaptureDestination,
output_path: Option<&Path>,
) -> Result<(), Error> {
let output_path = capture_path(output_path)?;
self.inner
.set_capture_output(destination, output_path)
.map_err(Error::from_ffi)
}
pub fn output_path(&self) -> Result<Option<PathBuf>, Error> {
self.inner.output_path().map_err(Error::from_ffi)
}
}
pub enum CaptureTarget {
Device(Device),
CommandQueue(CommandQueue),
Scope(CaptureScope),
}
impl CaptureScope {
pub fn with_scope<T>(&self, operation: impl FnOnce() -> T) -> Result<T, Error> {
self.inner.with_scope(operation).map_err(Error::from_ffi)
}
pub fn device(&self) -> Result<Device, Error> {
self.inner
.device()
.map(Device::from_ffi)
.map_err(Error::from_ffi)
}
pub fn command_queue(&self) -> Result<Option<CommandQueue>, Error> {
self.inner
.command_queue()
.map(|queue| queue.map(CommandQueue::from_ffi))
.map_err(Error::from_ffi)
}
}
pub struct CaptureSession {
inner: metal_rust_ffi::CaptureSession,
}
impl CaptureSession {
pub fn supports_destination(destination: CaptureDestination) -> Result<bool, Error> {
metal_rust_ffi::CaptureSession::supports_destination(destination).map_err(Error::from_ffi)
}
pub fn is_capturing() -> Result<bool, Error> {
metal_rust_ffi::CaptureSession::is_capturing().map_err(Error::from_ffi)
}
pub fn new_scope_for_device(device: &Device) -> Result<CaptureScope, Error> {
metal_rust_ffi::CaptureSession::new_scope_for_device(&device.inner)
.map(CaptureScope::from_ffi)
.map_err(Error::from_ffi)
}
pub fn new_scope_for_command_queue(queue: &CommandQueue) -> Result<CaptureScope, Error> {
metal_rust_ffi::CaptureSession::new_scope_for_command_queue(&queue.inner)
.map(CaptureScope::from_ffi)
.map_err(Error::from_ffi)
}
pub fn new_scope_for_mtl4_command_queue(
queue: &Metal4CommandQueue,
) -> Result<CaptureScope, Error> {
metal_rust_ffi::CaptureSession::new_scope_for_mtl4_command_queue(queue.as_ffi())
.map(CaptureScope::from_ffi)
.map_err(Error::from_ffi)
}
pub fn default_scope() -> Result<Option<CaptureScope>, Error> {
metal_rust_ffi::CaptureSession::default_scope()
.map(|scope| scope.map(CaptureScope::from_ffi))
.map_err(Error::from_ffi)
}
pub fn set_default_scope(scope: Option<&CaptureScope>) -> Result<(), Error> {
metal_rust_ffi::CaptureSession::set_default_scope(scope.map(|scope| &scope.inner))
.map_err(Error::from_ffi)
}
pub fn start_descriptor(descriptor: &CaptureDescriptor) -> Result<Self, Error> {
metal_rust_ffi::CaptureSession::start_descriptor(&descriptor.inner)
.map(|inner| Self { inner })
.map_err(Error::from_ffi)
}
pub fn start(device: &Device, output_path: &Path) -> Result<Self, Error> {
let path = capture_path(Some(output_path))?.expect("a path was supplied");
metal_rust_ffi::CaptureSession::start(&device.inner, path)
.map(|inner| Self { inner })
.map_err(Error::from_ffi)
}
pub fn start_for_device(
device: &Device,
destination: CaptureDestination,
output_path: Option<&Path>,
) -> Result<Self, Error> {
let output_path = capture_path(output_path)?;
metal_rust_ffi::CaptureSession::start_for_device(&device.inner, destination, output_path)
.map(|inner| Self { inner })
.map_err(Error::from_ffi)
}
pub fn start_for_command_queue(
queue: &CommandQueue,
destination: CaptureDestination,
output_path: Option<&Path>,
) -> Result<Self, Error> {
let output_path = capture_path(output_path)?;
metal_rust_ffi::CaptureSession::start_for_command_queue(
&queue.inner,
destination,
output_path,
)
.map(|inner| Self { inner })
.map_err(Error::from_ffi)
}
pub fn start_for_scope(
scope: &CaptureScope,
destination: CaptureDestination,
output_path: Option<&Path>,
) -> Result<Self, Error> {
let output_path = capture_path(output_path)?;
metal_rust_ffi::CaptureSession::start_for_scope(&scope.inner, destination, output_path)
.map(|inner| Self { inner })
.map_err(Error::from_ffi)
}
pub fn finish(self) {
self.inner.finish();
}
}
#[cfg(test)]
mod tests {
use super::capture_path;
use crate::ErrorKind;
use std::path::Path;
#[test]
fn capture_path_requires_an_absolute_path() {
let error = capture_path(Some(Path::new("trace.gputrace"))).unwrap_err();
assert_eq!(error.kind(), ErrorKind::InvalidArgument);
}
#[test]
fn capture_path_preserves_none_for_developer_tools() {
assert_eq!(capture_path(None).unwrap(), None);
}
}