Skip to main content

mtl_gpu/counter/
mod.rs

1//! Metal GPU counter facilities for profiling.
2//!
3//! Corresponds to `Metal/MTLCounters.hpp`.
4//!
5//! The counter API allows you to sample GPU performance counters for profiling
6//! and performance analysis.
7//!
8//! # Counter Result Types
9//!
10//! Metal provides three types of counter results:
11//! - [`CounterResultTimestamp`] - GPU timestamp values
12//! - [`CounterResultStageUtilization`] - Cycle counts per pipeline stage
13//! - [`CounterResultStatistic`] - Invocation counts and statistics
14
15use std::ffi::c_void;
16use std::ptr::NonNull;
17
18use mtl_foundation::{Referencing, UInteger};
19use mtl_sys::{msg_send_0, msg_send_1, sel};
20
21use crate::Device;
22use crate::enums::{CounterSampleBufferError, StorageMode};
23
24// ============================================================================
25// Counter Result Structures
26// ============================================================================
27
28/// Timestamp counter result.
29///
30/// C++ equivalent: `MTL::CounterResultTimestamp`
31#[repr(C, packed)]
32#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
33pub struct CounterResultTimestamp {
34    /// The GPU timestamp value.
35    pub timestamp: u64,
36}
37
38/// Stage utilization counter result.
39///
40/// C++ equivalent: `MTL::CounterResultStageUtilization`
41#[repr(C, packed)]
42#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
43pub struct CounterResultStageUtilization {
44    /// Total GPU cycles.
45    pub total_cycles: u64,
46    /// Cycles spent in vertex processing.
47    pub vertex_cycles: u64,
48    /// Cycles spent in tessellation.
49    pub tessellation_cycles: u64,
50    /// Cycles spent in post-tessellation vertex processing.
51    pub post_tessellation_vertex_cycles: u64,
52    /// Cycles spent in fragment processing.
53    pub fragment_cycles: u64,
54    /// Cycles spent in render target writes.
55    pub render_target_cycles: u64,
56}
57
58/// Statistics counter result.
59///
60/// C++ equivalent: `MTL::CounterResultStatistic`
61#[repr(C, packed)]
62#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
63pub struct CounterResultStatistic {
64    /// Number of tessellation input patches.
65    pub tessellation_input_patches: u64,
66    /// Number of vertex shader invocations.
67    pub vertex_invocations: u64,
68    /// Number of post-tessellation vertex invocations.
69    pub post_tessellation_vertex_invocations: u64,
70    /// Number of clipper invocations.
71    pub clipper_invocations: u64,
72    /// Number of primitives output by the clipper.
73    pub clipper_primitives_out: u64,
74    /// Number of fragment shader invocations.
75    pub fragment_invocations: u64,
76    /// Number of fragments that passed all tests.
77    pub fragments_passed: u64,
78    /// Number of compute kernel invocations.
79    pub compute_kernel_invocations: u64,
80}
81
82// ============================================================================
83// Constants
84// ============================================================================
85
86/// Error value returned when a counter sample fails.
87pub const COUNTER_ERROR_VALUE: UInteger = !0;
88
89/// Value indicating that a counter should not be sampled.
90pub const COUNTER_DONT_SAMPLE: UInteger = !0;
91
92// ============================================================================
93// Counter
94// ============================================================================
95
96/// A single GPU performance counter.
97///
98/// C++ equivalent: `MTL::Counter`
99///
100/// Counters are obtained from a [`CounterSet`] and represent individual
101/// performance metrics that can be sampled.
102#[repr(transparent)]
103pub struct Counter(NonNull<c_void>);
104
105impl Counter {
106    /// Create from a raw pointer.
107    ///
108    /// # Safety
109    ///
110    /// The pointer must be a valid Metal counter.
111    #[inline]
112    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
113        NonNull::new(ptr).map(Self)
114    }
115
116    /// Get the raw pointer.
117    #[inline]
118    pub fn as_raw(&self) -> *mut c_void {
119        self.0.as_ptr()
120    }
121
122    /// Get the name of this counter.
123    ///
124    /// C++ equivalent: `NS::String* name() const`
125    pub fn name(&self) -> Option<String> {
126        unsafe {
127            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(name));
128            if ptr.is_null() {
129                return None;
130            }
131            let utf8_ptr: *const std::ffi::c_char =
132                mtl_sys::msg_send_0(ptr as *const c_void, sel!(UTF8String));
133            if utf8_ptr.is_null() {
134                return None;
135            }
136            let c_str = std::ffi::CStr::from_ptr(utf8_ptr);
137            Some(c_str.to_string_lossy().into_owned())
138        }
139    }
140}
141
142impl Clone for Counter {
143    fn clone(&self) -> Self {
144        unsafe {
145            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
146        }
147        Self(self.0)
148    }
149}
150
151impl Drop for Counter {
152    fn drop(&mut self) {
153        unsafe {
154            msg_send_0::<()>(self.as_ptr(), sel!(release));
155        }
156    }
157}
158
159impl Referencing for Counter {
160    #[inline]
161    fn as_ptr(&self) -> *const c_void {
162        self.0.as_ptr()
163    }
164}
165
166unsafe impl Send for Counter {}
167unsafe impl Sync for Counter {}
168
169impl std::fmt::Debug for Counter {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        f.debug_struct("Counter")
172            .field("name", &self.name())
173            .finish()
174    }
175}
176
177// ============================================================================
178// CounterSet
179// ============================================================================
180
181/// A set of related GPU performance counters.
182///
183/// C++ equivalent: `MTL::CounterSet`
184///
185/// Counter sets group related counters together. You can query available
186/// counter sets from a device.
187#[repr(transparent)]
188pub struct CounterSet(NonNull<c_void>);
189
190impl CounterSet {
191    /// Create from a raw pointer.
192    ///
193    /// # Safety
194    ///
195    /// The pointer must be a valid Metal counter set.
196    #[inline]
197    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
198        NonNull::new(ptr).map(Self)
199    }
200
201    /// Get the raw pointer.
202    #[inline]
203    pub fn as_raw(&self) -> *mut c_void {
204        self.0.as_ptr()
205    }
206
207    /// Get the name of this counter set.
208    ///
209    /// C++ equivalent: `NS::String* name() const`
210    pub fn name(&self) -> Option<String> {
211        unsafe {
212            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(name));
213            if ptr.is_null() {
214                return None;
215            }
216            let utf8_ptr: *const std::ffi::c_char =
217                mtl_sys::msg_send_0(ptr as *const c_void, sel!(UTF8String));
218            if utf8_ptr.is_null() {
219                return None;
220            }
221            let c_str = std::ffi::CStr::from_ptr(utf8_ptr);
222            Some(c_str.to_string_lossy().into_owned())
223        }
224    }
225
226    /// Get the counters in this counter set.
227    ///
228    /// C++ equivalent: `NS::Array* counters() const`
229    ///
230    /// Returns the raw NSArray pointer. Use with Foundation array iteration.
231    pub fn counters_raw(&self) -> *mut c_void {
232        unsafe { msg_send_0(self.as_ptr(), sel!(counters)) }
233    }
234
235    /// Get the number of counters in this counter set.
236    pub fn counter_count(&self) -> UInteger {
237        unsafe {
238            let array = self.counters_raw();
239            if array.is_null() {
240                return 0;
241            }
242            msg_send_0(array as *const c_void, sel!(count))
243        }
244    }
245
246    /// Get a counter at the specified index.
247    pub fn counter_at_index(&self, index: UInteger) -> Option<Counter> {
248        unsafe {
249            let array = self.counters_raw();
250            if array.is_null() {
251                return None;
252            }
253            let ptr: *mut c_void = msg_send_1(array as *const c_void, sel!(objectAtIndex:), index);
254            if ptr.is_null() {
255                return None;
256            }
257            msg_send_0::<*mut c_void>(ptr as *const c_void, sel!(retain));
258            Counter::from_raw(ptr)
259        }
260    }
261}
262
263impl Clone for CounterSet {
264    fn clone(&self) -> Self {
265        unsafe {
266            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
267        }
268        Self(self.0)
269    }
270}
271
272impl Drop for CounterSet {
273    fn drop(&mut self) {
274        unsafe {
275            msg_send_0::<()>(self.as_ptr(), sel!(release));
276        }
277    }
278}
279
280impl Referencing for CounterSet {
281    #[inline]
282    fn as_ptr(&self) -> *const c_void {
283        self.0.as_ptr()
284    }
285}
286
287unsafe impl Send for CounterSet {}
288unsafe impl Sync for CounterSet {}
289
290impl std::fmt::Debug for CounterSet {
291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292        f.debug_struct("CounterSet")
293            .field("name", &self.name())
294            .field("counter_count", &self.counter_count())
295            .finish()
296    }
297}
298
299// ============================================================================
300// CounterSampleBufferDescriptor
301// ============================================================================
302
303/// Descriptor for creating a counter sample buffer.
304///
305/// C++ equivalent: `MTL::CounterSampleBufferDescriptor`
306#[repr(transparent)]
307pub struct CounterSampleBufferDescriptor(NonNull<c_void>);
308
309impl CounterSampleBufferDescriptor {
310    /// Allocate a new counter sample buffer descriptor.
311    ///
312    /// C++ equivalent: `static CounterSampleBufferDescriptor* alloc()`
313    pub fn alloc() -> Option<Self> {
314        unsafe {
315            let cls = mtl_sys::Class::get("MTLCounterSampleBufferDescriptor")?;
316            let ptr: *mut c_void = msg_send_0(cls.as_ptr(), sel!(alloc));
317            Self::from_raw(ptr)
318        }
319    }
320
321    /// Initialize an allocated counter sample buffer descriptor.
322    ///
323    /// C++ equivalent: `CounterSampleBufferDescriptor* init()`
324    pub fn init(&self) -> Option<Self> {
325        unsafe {
326            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(init));
327            Self::from_raw(ptr)
328        }
329    }
330
331    /// Create a new counter sample buffer descriptor.
332    pub fn new() -> Option<Self> {
333        Self::alloc()?.init()
334    }
335
336    /// Create from a raw pointer.
337    ///
338    /// # Safety
339    ///
340    /// The pointer must be a valid Metal counter sample buffer descriptor.
341    #[inline]
342    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
343        NonNull::new(ptr).map(Self)
344    }
345
346    /// Get the raw pointer.
347    #[inline]
348    pub fn as_raw(&self) -> *mut c_void {
349        self.0.as_ptr()
350    }
351
352    /// Get the counter set to sample.
353    ///
354    /// C++ equivalent: `CounterSet* counterSet() const`
355    pub fn counter_set(&self) -> Option<CounterSet> {
356        unsafe {
357            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(counterSet));
358            if ptr.is_null() {
359                return None;
360            }
361            msg_send_0::<*mut c_void>(ptr as *const c_void, sel!(retain));
362            CounterSet::from_raw(ptr)
363        }
364    }
365
366    /// Set the counter set to sample.
367    ///
368    /// C++ equivalent: `void setCounterSet(const CounterSet*)`
369    pub fn set_counter_set(&self, counter_set: &CounterSet) {
370        unsafe {
371            msg_send_1::<(), *const c_void>(
372                self.as_ptr(),
373                sel!(setCounterSet:),
374                counter_set.as_ptr(),
375            );
376        }
377    }
378
379    /// Get the label for this descriptor.
380    ///
381    /// C++ equivalent: `NS::String* label() const`
382    pub fn label(&self) -> Option<String> {
383        unsafe {
384            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(label));
385            if ptr.is_null() {
386                return None;
387            }
388            let utf8_ptr: *const std::ffi::c_char =
389                mtl_sys::msg_send_0(ptr as *const c_void, sel!(UTF8String));
390            if utf8_ptr.is_null() {
391                return None;
392            }
393            let c_str = std::ffi::CStr::from_ptr(utf8_ptr);
394            Some(c_str.to_string_lossy().into_owned())
395        }
396    }
397
398    /// Set the label for this descriptor.
399    ///
400    /// C++ equivalent: `void setLabel(const NS::String*)`
401    pub fn set_label(&self, label: &str) {
402        if let Some(ns_label) = mtl_foundation::String::from_str(label) {
403            unsafe {
404                msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(setLabel:), ns_label.as_ptr());
405            }
406        }
407    }
408
409    /// Get the number of samples to store.
410    ///
411    /// C++ equivalent: `NS::UInteger sampleCount() const`
412    #[inline]
413    pub fn sample_count(&self) -> UInteger {
414        unsafe { msg_send_0(self.as_ptr(), sel!(sampleCount)) }
415    }
416
417    /// Set the number of samples to store.
418    ///
419    /// C++ equivalent: `void setSampleCount(NS::UInteger)`
420    #[inline]
421    pub fn set_sample_count(&self, count: UInteger) {
422        unsafe {
423            msg_send_1::<(), UInteger>(self.as_ptr(), sel!(setSampleCount:), count);
424        }
425    }
426
427    /// Get the storage mode for the sample buffer.
428    ///
429    /// C++ equivalent: `StorageMode storageMode() const`
430    #[inline]
431    pub fn storage_mode(&self) -> StorageMode {
432        unsafe { msg_send_0(self.as_ptr(), sel!(storageMode)) }
433    }
434
435    /// Set the storage mode for the sample buffer.
436    ///
437    /// C++ equivalent: `void setStorageMode(StorageMode)`
438    #[inline]
439    pub fn set_storage_mode(&self, mode: StorageMode) {
440        unsafe {
441            msg_send_1::<(), StorageMode>(self.as_ptr(), sel!(setStorageMode:), mode);
442        }
443    }
444}
445
446impl Default for CounterSampleBufferDescriptor {
447    fn default() -> Self {
448        Self::new().expect("failed to create CounterSampleBufferDescriptor")
449    }
450}
451
452impl Clone for CounterSampleBufferDescriptor {
453    fn clone(&self) -> Self {
454        unsafe {
455            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(copy));
456            Self::from_raw(ptr).expect("failed to copy CounterSampleBufferDescriptor")
457        }
458    }
459}
460
461impl Drop for CounterSampleBufferDescriptor {
462    fn drop(&mut self) {
463        unsafe {
464            msg_send_0::<()>(self.as_ptr(), sel!(release));
465        }
466    }
467}
468
469impl Referencing for CounterSampleBufferDescriptor {
470    #[inline]
471    fn as_ptr(&self) -> *const c_void {
472        self.0.as_ptr()
473    }
474}
475
476unsafe impl Send for CounterSampleBufferDescriptor {}
477unsafe impl Sync for CounterSampleBufferDescriptor {}
478
479impl std::fmt::Debug for CounterSampleBufferDescriptor {
480    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481        f.debug_struct("CounterSampleBufferDescriptor")
482            .field("label", &self.label())
483            .field("sample_count", &self.sample_count())
484            .field("storage_mode", &self.storage_mode())
485            .finish()
486    }
487}
488
489// ============================================================================
490// CounterSampleBuffer
491// ============================================================================
492
493/// A buffer that stores GPU counter samples.
494///
495/// C++ equivalent: `MTL::CounterSampleBuffer`
496///
497/// Counter sample buffers are created from a device using a descriptor.
498/// They store sampled counter values that can be resolved to get the results.
499#[repr(transparent)]
500pub struct CounterSampleBuffer(NonNull<c_void>);
501
502impl CounterSampleBuffer {
503    /// Create from a raw pointer.
504    ///
505    /// # Safety
506    ///
507    /// The pointer must be a valid Metal counter sample buffer.
508    #[inline]
509    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
510        NonNull::new(ptr).map(Self)
511    }
512
513    /// Get the raw pointer.
514    #[inline]
515    pub fn as_raw(&self) -> *mut c_void {
516        self.0.as_ptr()
517    }
518
519    /// Get the device that created this buffer.
520    ///
521    /// C++ equivalent: `Device* device() const`
522    pub fn device(&self) -> Device {
523        unsafe {
524            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(device));
525            msg_send_0::<*mut c_void>(ptr as *const c_void, sel!(retain));
526            Device::from_raw(ptr).expect("counter sample buffer has no device")
527        }
528    }
529
530    /// Get the label for this buffer.
531    ///
532    /// C++ equivalent: `NS::String* label() const`
533    pub fn label(&self) -> Option<String> {
534        unsafe {
535            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(label));
536            if ptr.is_null() {
537                return None;
538            }
539            let utf8_ptr: *const std::ffi::c_char =
540                mtl_sys::msg_send_0(ptr as *const c_void, sel!(UTF8String));
541            if utf8_ptr.is_null() {
542                return None;
543            }
544            let c_str = std::ffi::CStr::from_ptr(utf8_ptr);
545            Some(c_str.to_string_lossy().into_owned())
546        }
547    }
548
549    /// Get the number of samples in this buffer.
550    ///
551    /// C++ equivalent: `NS::UInteger sampleCount() const`
552    #[inline]
553    pub fn sample_count(&self) -> UInteger {
554        unsafe { msg_send_0(self.as_ptr(), sel!(sampleCount)) }
555    }
556
557    /// Resolve counter values for a range of samples.
558    ///
559    /// Returns the raw NSData pointer containing the resolved counter values.
560    /// The data format depends on the counter set used.
561    ///
562    /// C++ equivalent: `NS::Data* resolveCounterRange(NS::Range)`
563    pub fn resolve_counter_range_raw(
564        &self,
565        location: UInteger,
566        length: UInteger,
567    ) -> Result<*mut c_void, CounterSampleBufferError> {
568        unsafe {
569            let range = mtl_foundation::Range::new(location, length);
570            // Note: This method can throw an exception in ObjC, but we handle
571            // the error case by checking if the result is null
572            let ptr: *mut c_void = msg_send_1(self.as_ptr(), sel!(resolveCounterRange:), range);
573            if ptr.is_null() {
574                Err(CounterSampleBufferError::INVALID)
575            } else {
576                Ok(ptr)
577            }
578        }
579    }
580}
581
582impl Clone for CounterSampleBuffer {
583    fn clone(&self) -> Self {
584        unsafe {
585            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
586        }
587        Self(self.0)
588    }
589}
590
591impl Drop for CounterSampleBuffer {
592    fn drop(&mut self) {
593        unsafe {
594            msg_send_0::<()>(self.as_ptr(), sel!(release));
595        }
596    }
597}
598
599impl Referencing for CounterSampleBuffer {
600    #[inline]
601    fn as_ptr(&self) -> *const c_void {
602        self.0.as_ptr()
603    }
604}
605
606unsafe impl Send for CounterSampleBuffer {}
607unsafe impl Sync for CounterSampleBuffer {}
608
609impl std::fmt::Debug for CounterSampleBuffer {
610    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
611        f.debug_struct("CounterSampleBuffer")
612            .field("label", &self.label())
613            .field("sample_count", &self.sample_count())
614            .finish()
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    #[test]
623    fn test_counter_size() {
624        assert_eq!(
625            std::mem::size_of::<Counter>(),
626            std::mem::size_of::<*mut c_void>()
627        );
628    }
629
630    #[test]
631    fn test_counter_set_size() {
632        assert_eq!(
633            std::mem::size_of::<CounterSet>(),
634            std::mem::size_of::<*mut c_void>()
635        );
636    }
637
638    #[test]
639    fn test_counter_sample_buffer_descriptor_size() {
640        assert_eq!(
641            std::mem::size_of::<CounterSampleBufferDescriptor>(),
642            std::mem::size_of::<*mut c_void>()
643        );
644    }
645
646    #[test]
647    fn test_counter_sample_buffer_size() {
648        assert_eq!(
649            std::mem::size_of::<CounterSampleBuffer>(),
650            std::mem::size_of::<*mut c_void>()
651        );
652    }
653
654    #[test]
655    fn test_counter_result_timestamp_size() {
656        assert_eq!(std::mem::size_of::<CounterResultTimestamp>(), 8);
657    }
658
659    #[test]
660    fn test_counter_result_stage_utilization_size() {
661        assert_eq!(std::mem::size_of::<CounterResultStageUtilization>(), 48);
662    }
663
664    #[test]
665    fn test_counter_result_statistic_size() {
666        assert_eq!(std::mem::size_of::<CounterResultStatistic>(), 64);
667    }
668
669    #[test]
670    fn test_constants() {
671        assert_eq!(COUNTER_ERROR_VALUE, !0);
672        assert_eq!(COUNTER_DONT_SAMPLE, !0);
673    }
674}