mtl_gpu/device/
creation.rs1use std::ffi::c_void;
10
11use mtl_foundation::Referencing;
12use mtl_sys::{DeviceObserver as SysDeviceObserver, create_system_default_device};
13
14#[cfg(target_os = "macos")]
15use mtl_sys::copy_all_devices as sys_copy_all_devices;
16
17use super::Device;
18
19pub type Timestamp = u64;
23
24#[inline]
40pub fn system_default() -> Option<Device> {
41 unsafe {
42 let ptr = create_system_default_device()?;
43 let _: *mut c_void = mtl_sys::msg_send_0(ptr, mtl_sys::sel!(retain));
45 Device::from_raw(ptr)
46 }
47}
48
49#[cfg(target_os = "macos")]
66pub fn copy_all_devices() -> Vec<Device> {
67 unsafe {
68 let array_ptr = match sys_copy_all_devices() {
69 Some(ptr) => ptr,
70 None => return Vec::new(),
71 };
72
73 let count: usize = mtl_sys::msg_send_0(array_ptr, mtl_sys::sel!(count));
75
76 let mut devices = Vec::with_capacity(count);
77 for i in 0..count {
78 let device_ptr: *mut c_void =
79 mtl_sys::msg_send_1(array_ptr, mtl_sys::sel!(objectAtIndex:), i);
80 if let Some(device) = Device::from_raw(device_ptr) {
81 device.retain();
83 devices.push(device);
84 }
85 }
86
87 mtl_sys::msg_send_0::<()>(array_ptr, mtl_sys::sel!(release));
89
90 devices
91 }
92}
93
94#[cfg(target_os = "macos")]
99pub struct DeviceObserver(SysDeviceObserver);
100
101#[cfg(target_os = "macos")]
102impl DeviceObserver {
103 pub unsafe fn from_raw(observer: SysDeviceObserver) -> Self {
109 Self(observer)
110 }
111
112 pub fn as_raw(&self) -> SysDeviceObserver {
114 self.0
115 }
116}
117
118#[cfg(target_os = "macos")]
140pub fn copy_all_devices_with_observer<F>(handler: F) -> (Vec<Device>, DeviceObserver)
141where
142 F: Fn(&Device, DeviceNotificationName) + Send + 'static,
143{
144 use mtl_sys::MTLCopyAllDevicesWithObserver;
145
146 let block = mtl_sys::TwoArgBlock::from_fn(
148 move |device_ptr: *mut c_void, notification_name_ptr: *mut c_void| {
149 unsafe {
150 if let Some(device) = Device::from_raw(device_ptr) {
151 let notification = parse_notification_name(notification_name_ptr);
153 handler(&device, notification);
154 std::mem::forget(device);
156 }
157 }
158 },
159 );
160
161 let mut observer: SysDeviceObserver = std::ptr::null_mut();
162
163 let array_ptr =
164 unsafe { MTLCopyAllDevicesWithObserver(&mut observer as *mut _, block.as_ptr()) };
165
166 std::mem::forget(block);
168
169 let devices = if array_ptr.is_null() {
171 Vec::new()
172 } else {
173 unsafe {
174 let count: usize = mtl_sys::msg_send_0(array_ptr, mtl_sys::sel!(count));
175 let mut devices = Vec::with_capacity(count);
176 for i in 0..count {
177 let device_ptr: *mut c_void =
178 mtl_sys::msg_send_1(array_ptr, mtl_sys::sel!(objectAtIndex:), i);
179 if let Some(device) = Device::from_raw(device_ptr) {
180 device.retain();
182 devices.push(device);
183 }
184 }
185 mtl_sys::msg_send_0::<()>(array_ptr, mtl_sys::sel!(release));
187 devices
188 }
189 };
190
191 (devices, unsafe { DeviceObserver::from_raw(observer) })
192}
193
194#[cfg(target_os = "macos")]
196fn parse_notification_name(ns_string: *mut c_void) -> DeviceNotificationName {
197 if ns_string.is_null() {
198 return DeviceNotificationName::WasRemoved; }
200
201 unsafe {
202 let c_str: *const i8 = mtl_sys::msg_send_0(ns_string, mtl_sys::sel!(UTF8String));
203 if c_str.is_null() {
204 return DeviceNotificationName::WasRemoved;
205 }
206
207 let name = std::ffi::CStr::from_ptr(c_str).to_string_lossy();
208
209 if name.contains("WasAdded") {
211 DeviceNotificationName::WasAdded
212 } else if name.contains("RemovalRequested") {
213 DeviceNotificationName::RemovalRequested
214 } else if name.contains("WasRemoved") {
215 DeviceNotificationName::WasRemoved
216 } else {
217 DeviceNotificationName::WasRemoved
219 }
220 }
221}
222
223#[cfg(target_os = "macos")]
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum DeviceNotificationName {
227 WasAdded,
229 RemovalRequested,
231 WasRemoved,
233}
234
235#[cfg(target_os = "macos")]
241pub fn remove_device_observer(observer: DeviceObserver) {
242 use mtl_sys::MTLRemoveDeviceObserver;
243 unsafe {
244 MTLRemoveDeviceObserver(observer.0);
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_system_default() {
254 let device = system_default();
255 assert!(device.is_some(), "should have at least one Metal device");
256
257 let device = device.unwrap();
258 assert!(!device.name().is_empty(), "device should have a name");
259 }
260
261 #[cfg(target_os = "macos")]
262 #[test]
263 fn test_copy_all_devices() {
264 let devices = copy_all_devices();
265 assert!(!devices.is_empty(), "should have at least one device");
266 }
267}