nuvec_gpu/metal/
capture.rs1use std::path::Path;
5
6use objc2::rc::autoreleasepool;
7use objc2_foundation::{NSString, NSURL};
8use objc2_metal::{MTLCaptureDescriptor, MTLCaptureDestination, MTLCaptureManager};
9
10use super::Device;
11use crate::{GpuError, Result};
12
13impl Device {
14 pub fn start_capture(&self, path: impl AsRef<Path>) -> Result<()> {
18 let path = path.as_ref();
19 if path.extension().is_none_or(|e| e != "gputrace") {
20 return Err(GpuError::Capture(format!(
21 "the capture file must end in .gputrace: {}",
22 path.display()
23 )));
24 }
25 if path.exists() {
26 return Err(GpuError::Capture(format!(
27 "the capture file already exists: {}",
28 path.display()
29 )));
30 }
31 autoreleasepool(|_| {
32 let manager = unsafe { MTLCaptureManager::sharedCaptureManager() };
34 if !manager.supportsDestination(MTLCaptureDestination::GPUTraceDocument) {
35 return Err(GpuError::Capture(
36 "captures to a file are disabled: set MTL_CAPTURE_ENABLED=1".into(),
37 ));
38 }
39 let descriptor = MTLCaptureDescriptor::new();
40 let device: &objc2::runtime::AnyObject = self.inner.device.as_ref();
41 unsafe { descriptor.setCaptureObject(Some(device)) };
43 descriptor.setDestination(MTLCaptureDestination::GPUTraceDocument);
44 let url = NSURL::fileURLWithPath(&NSString::from_str(&path.to_string_lossy()));
45 descriptor.setOutputURL(Some(&url));
46 manager
47 .startCaptureWithDescriptor_error(&descriptor)
48 .map_err(|e| GpuError::Capture(e.localizedDescription().to_string()))
49 })
50 }
51
52 pub fn stop_capture(&self) {
54 autoreleasepool(|_| {
55 let manager = unsafe { MTLCaptureManager::sharedCaptureManager() };
57 if manager.isCapturing() {
58 manager.stopCapture();
59 }
60 });
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn capture_paths_and_the_environment_are_checked() {
70 let Some(d) = Device::shared() else {
71 return;
72 };
73 let dir = std::env::temp_dir().join(format!("nuvec-capture-{}", std::process::id()));
74 let e = d.start_capture(dir.join("trace.txt")).unwrap_err();
75 assert!(e.to_string().contains(".gputrace"), "{e}");
76 let trace = dir.join("trace.gputrace");
77 if std::env::var_os("MTL_CAPTURE_ENABLED").is_none() {
78 let e = d.start_capture(&trace).unwrap_err();
79 assert!(e.to_string().contains("MTL_CAPTURE_ENABLED"), "{e}");
80 } else {
81 std::fs::create_dir_all(&dir).unwrap();
82 d.start_capture(&trace).unwrap();
83 let again = d.start_capture(dir.join("again.gputrace")).unwrap_err();
84 assert!(matches!(again, GpuError::Capture(_)), "{again}");
85 d.stop_capture();
86 assert!(trace.exists());
87 let e = d.start_capture(&trace).unwrap_err();
88 assert!(e.to_string().contains("already exists"), "{e}");
89 std::fs::remove_dir_all(&dir).unwrap();
90 }
91 d.stop_capture();
92 }
93}