Skip to main content

mtl_gpu/device/
properties.rs

1//! Device properties.
2//!
3//! Corresponds to property getters in `MTL::Device` from `Metal/MTLDevice.hpp`.
4
5use std::ffi::c_void;
6
7use mtl_foundation::{Referencing, UInteger};
8use mtl_sys::{msg_send_0, msg_send_2, sel};
9
10use super::{Architecture, Device};
11use crate::enums::{ArgumentBuffersTier, DeviceLocation, ReadWriteTextureTier};
12use crate::types::SamplePosition;
13
14impl Device {
15    /// Get the device name.
16    ///
17    /// C++ equivalent: `NS::String* name() const`
18    ///
19    /// # Example
20    ///
21    /// ```ignore
22    /// let device = device::system_default().unwrap();
23    /// println!("GPU: {}", device.name());
24    /// ```
25    #[inline]
26    pub fn name(&self) -> &str {
27        unsafe {
28            let ns_string: *mut c_void = msg_send_0(self.as_ptr(), sel!(name));
29            if ns_string.is_null() {
30                return "";
31            }
32            let c_str: *const std::ffi::c_char = msg_send_0(ns_string, sel!(UTF8String));
33            if c_str.is_null() {
34                return "";
35            }
36            std::ffi::CStr::from_ptr(c_str).to_str().unwrap_or("")
37        }
38    }
39
40    /// Get the device architecture.
41    ///
42    /// C++ equivalent: `Architecture* architecture() const`
43    #[inline]
44    pub fn architecture(&self) -> Option<Architecture> {
45        unsafe {
46            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(architecture));
47            if ptr.is_null() {
48                return None;
49            }
50            // Property getter returns autoreleased, so retain it
51            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
52            Architecture::from_raw(ptr)
53        }
54    }
55
56    /// Get the device registry ID.
57    ///
58    /// This is a unique identifier for the device that persists across reboots.
59    ///
60    /// C++ equivalent: `uint64_t registryID() const`
61    #[inline]
62    pub fn registry_id(&self) -> u64 {
63        unsafe { msg_send_0(self.as_ptr(), sel!(registryID)) }
64    }
65
66    /// Get the device location.
67    ///
68    /// C++ equivalent: `DeviceLocation location() const`
69    #[inline]
70    pub fn location(&self) -> DeviceLocation {
71        unsafe { msg_send_0(self.as_ptr(), sel!(location)) }
72    }
73
74    /// Get the device location number.
75    ///
76    /// C++ equivalent: `NS::UInteger locationNumber() const`
77    #[inline]
78    pub fn location_number(&self) -> UInteger {
79        unsafe { msg_send_0(self.as_ptr(), sel!(locationNumber)) }
80    }
81
82    /// Check if the device has unified memory.
83    ///
84    /// Returns true if the CPU and GPU share the same memory.
85    ///
86    /// C++ equivalent: `bool hasUnifiedMemory() const`
87    #[inline]
88    pub fn has_unified_memory(&self) -> bool {
89        unsafe { msg_send_0(self.as_ptr(), sel!(hasUnifiedMemory)) }
90    }
91
92    /// Check if the device is headless (has no display attached).
93    ///
94    /// C++ equivalent: `bool isHeadless() const`
95    #[inline]
96    pub fn is_headless(&self) -> bool {
97        unsafe { msg_send_0(self.as_ptr(), sel!(isHeadless)) }
98    }
99
100    /// Check if the device is low-power.
101    ///
102    /// C++ equivalent: `bool isLowPower() const`
103    #[inline]
104    pub fn is_low_power(&self) -> bool {
105        unsafe { msg_send_0(self.as_ptr(), sel!(isLowPower)) }
106    }
107
108    /// Check if the device is removable.
109    ///
110    /// C++ equivalent: `bool isRemovable() const`
111    #[inline]
112    pub fn is_removable(&self) -> bool {
113        unsafe { msg_send_0(self.as_ptr(), sel!(isRemovable)) }
114    }
115
116    /// Get the current allocated memory size in bytes.
117    ///
118    /// C++ equivalent: `NS::UInteger currentAllocatedSize() const`
119    #[inline]
120    pub fn current_allocated_size(&self) -> UInteger {
121        unsafe { msg_send_0(self.as_ptr(), sel!(currentAllocatedSize)) }
122    }
123
124    /// Get the recommended maximum working set size.
125    ///
126    /// C++ equivalent: `uint64_t recommendedMaxWorkingSetSize() const`
127    #[inline]
128    pub fn recommended_max_working_set_size(&self) -> u64 {
129        unsafe { msg_send_0(self.as_ptr(), sel!(recommendedMaxWorkingSetSize)) }
130    }
131
132    /// Get the read-write texture tier supported by this device.
133    ///
134    /// C++ equivalent: `ReadWriteTextureTier readWriteTextureSupport() const`
135    #[inline]
136    pub fn read_write_texture_support(&self) -> ReadWriteTextureTier {
137        unsafe { msg_send_0(self.as_ptr(), sel!(readWriteTextureSupport)) }
138    }
139
140    /// Get the argument buffers tier supported by this device.
141    ///
142    /// C++ equivalent: `ArgumentBuffersTier argumentBuffersSupport() const`
143    #[inline]
144    pub fn argument_buffers_support(&self) -> ArgumentBuffersTier {
145        unsafe { msg_send_0(self.as_ptr(), sel!(argumentBuffersSupport)) }
146    }
147
148    /// Get the peer group ID for multi-GPU configurations.
149    ///
150    /// C++ equivalent: `uint64_t peerGroupID() const`
151    #[inline]
152    pub fn peer_group_id(&self) -> u64 {
153        unsafe { msg_send_0(self.as_ptr(), sel!(peerGroupID)) }
154    }
155
156    /// Get the peer index within a multi-GPU configuration.
157    ///
158    /// C++ equivalent: `uint32_t peerIndex() const`
159    #[inline]
160    pub fn peer_index(&self) -> u32 {
161        unsafe { msg_send_0(self.as_ptr(), sel!(peerIndex)) }
162    }
163
164    /// Get the number of peer devices.
165    ///
166    /// C++ equivalent: `uint32_t peerCount() const`
167    #[inline]
168    pub fn peer_count(&self) -> u32 {
169        unsafe { msg_send_0(self.as_ptr(), sel!(peerCount)) }
170    }
171
172    /// Get the maximum data transfer rate in bytes per second.
173    ///
174    /// C++ equivalent: `uint64_t maxTransferRate() const`
175    #[inline]
176    pub fn max_transfer_rate(&self) -> u64 {
177        unsafe { msg_send_0(self.as_ptr(), sel!(maxTransferRate)) }
178    }
179
180    /// Get whether to maximize concurrent compilation.
181    ///
182    /// C++ equivalent: `bool shouldMaximizeConcurrentCompilation() const`
183    #[inline]
184    pub fn should_maximize_concurrent_compilation(&self) -> bool {
185        unsafe { msg_send_0(self.as_ptr(), sel!(shouldMaximizeConcurrentCompilation)) }
186    }
187
188    /// Set whether to maximize concurrent compilation.
189    ///
190    /// C++ equivalent: `void setShouldMaximizeConcurrentCompilation(bool)`
191    #[inline]
192    pub fn set_should_maximize_concurrent_compilation(&self, value: bool) {
193        unsafe {
194            mtl_sys::msg_send_1::<(), bool>(
195                self.as_ptr(),
196                sel!(setShouldMaximizeConcurrentCompilation:),
197                value,
198            );
199        }
200    }
201
202    /// Get the maximum number of concurrent compilation tasks.
203    ///
204    /// C++ equivalent: `NS::UInteger maximumConcurrentCompilationTaskCount() const`
205    #[inline]
206    pub fn maximum_concurrent_compilation_task_count(&self) -> UInteger {
207        unsafe { msg_send_0(self.as_ptr(), sel!(maximumConcurrentCompilationTaskCount)) }
208    }
209
210    /// Get the GPU timestamp frequency in Hz.
211    ///
212    /// C++ equivalent: `uint64_t queryTimestampFrequency()`
213    #[inline]
214    pub fn query_timestamp_frequency(&self) -> u64 {
215        unsafe { msg_send_0(self.as_ptr(), sel!(queryTimestampFrequency)) }
216    }
217
218    /// Sample CPU and GPU timestamps simultaneously.
219    ///
220    /// C++ equivalent: `void sampleTimestamps(MTL::Timestamp*, MTL::Timestamp*)`
221    #[inline]
222    pub fn sample_timestamps(&self) -> (u64, u64) {
223        let mut cpu_timestamp: u64 = 0;
224        let mut gpu_timestamp: u64 = 0;
225        unsafe {
226            msg_send_2::<(), *mut u64, *mut u64>(
227                self.as_ptr(),
228                sel!(sampleTimestamps: gpuTimestamp:),
229                &mut cpu_timestamp as *mut u64,
230                &mut gpu_timestamp as *mut u64,
231            );
232        }
233        (cpu_timestamp, gpu_timestamp)
234    }
235
236    /// Get default sample positions for a given sample count.
237    ///
238    /// C++ equivalent: `void getDefaultSamplePositions(MTL::SamplePosition*, NS::UInteger)`
239    #[inline]
240    pub fn get_default_sample_positions(&self, positions: &mut [SamplePosition]) {
241        unsafe {
242            msg_send_2::<(), *mut SamplePosition, usize>(
243                self.as_ptr(),
244                sel!(getDefaultSamplePositions: count:),
245                positions.as_mut_ptr(),
246                positions.len(),
247            );
248        }
249    }
250
251    // =========================================================================
252    // Deprecated properties (included for 1:1 API compatibility)
253    // =========================================================================
254
255    /// Check if barycentric coordinates are supported.
256    ///
257    /// C++ equivalent: `bool barycentricCoordsSupported() const`
258    #[deprecated(note = "Use are_barycentric_coords_supported() instead")]
259    #[inline]
260    pub fn barycentric_coords_supported(&self) -> bool {
261        self.are_barycentric_coords_supported()
262    }
263
264    /// Check if programmable sample positions are supported.
265    ///
266    /// C++ equivalent: `bool programmableSamplePositionsSupported() const`
267    #[deprecated(note = "Use are_programmable_sample_positions_supported() instead")]
268    #[inline]
269    pub fn programmable_sample_positions_supported(&self) -> bool {
270        self.are_programmable_sample_positions_supported()
271    }
272
273    /// Check if raster order groups are supported.
274    ///
275    /// C++ equivalent: `bool rasterOrderGroupsSupported() const`
276    #[deprecated(note = "Use are_raster_order_groups_supported() instead")]
277    #[inline]
278    pub fn raster_order_groups_supported(&self) -> bool {
279        self.are_raster_order_groups_supported()
280    }
281
282    /// Check if the device is headless.
283    ///
284    /// C++ equivalent: `bool headless() const`
285    #[deprecated(note = "Use is_headless() instead")]
286    #[inline]
287    pub fn headless(&self) -> bool {
288        self.is_headless()
289    }
290
291    /// Check if the device is low-power.
292    ///
293    /// C++ equivalent: `bool lowPower() const`
294    #[deprecated(note = "Use is_low_power() instead")]
295    #[inline]
296    pub fn low_power(&self) -> bool {
297        self.is_low_power()
298    }
299
300    /// Check if the device is removable.
301    ///
302    /// C++ equivalent: `bool removable() const`
303    #[deprecated(note = "Use is_removable() instead")]
304    #[inline]
305    pub fn removable(&self) -> bool {
306        self.is_removable()
307    }
308
309    /// Check if depth24 stencil8 pixel format is supported.
310    ///
311    /// C++ equivalent: `bool depth24Stencil8PixelFormatSupported() const`
312    #[deprecated(note = "Use is_depth24_stencil8_pixel_format_supported() instead")]
313    #[inline]
314    pub fn depth24_stencil8_pixel_format_supported(&self) -> bool {
315        self.is_depth24_stencil8_pixel_format_supported()
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use crate::device::system_default;
322
323    #[test]
324    fn test_device_properties() {
325        let device = system_default().expect("no Metal device");
326
327        // Name should not be empty
328        assert!(!device.name().is_empty());
329
330        // Registry ID should be non-zero
331        assert!(device.registry_id() != 0);
332
333        // Architecture should be available
334        let arch = device.architecture();
335        assert!(arch.is_some());
336    }
337
338    #[test]
339    fn test_device_timestamps() {
340        let device = system_default().expect("no Metal device");
341
342        let (cpu, gpu) = device.sample_timestamps();
343        // Both timestamps should be non-zero
344        assert!(cpu > 0);
345        assert!(gpu > 0);
346    }
347}