Skip to main content

mtl_gpu/
log_state.rs

1//! Metal log state types.
2//!
3//! Corresponds to `Metal/MTLLogState.hpp`.
4//!
5//! Log states allow you to capture and process GPU shader logs for debugging
6//! and validation purposes.
7
8use std::ffi::c_void;
9use std::ptr::NonNull;
10
11use mtl_foundation::{Referencing, UInteger};
12use mtl_sys::{Class, msg_send_0, msg_send_1, sel};
13
14use crate::enums::LogLevel;
15
16// ============================================================================
17// LogStateDescriptor
18// ============================================================================
19
20/// Descriptor for creating a log state.
21///
22/// C++ equivalent: `MTL::LogStateDescriptor`
23#[repr(transparent)]
24pub struct LogStateDescriptor(NonNull<c_void>);
25
26impl LogStateDescriptor {
27    /// Allocate a new log state descriptor.
28    ///
29    /// C++ equivalent: `static LogStateDescriptor* alloc()`
30    pub fn alloc() -> Option<Self> {
31        unsafe {
32            let cls = Class::get("MTLLogStateDescriptor")?;
33            let ptr: *mut c_void = msg_send_0(cls.as_ptr(), sel!(alloc));
34            Self::from_raw(ptr)
35        }
36    }
37
38    /// Initialize an allocated descriptor.
39    ///
40    /// C++ equivalent: `LogStateDescriptor* init()`
41    pub fn init(&self) -> Option<Self> {
42        unsafe {
43            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(init));
44            Self::from_raw(ptr)
45        }
46    }
47
48    /// Create a new log state descriptor.
49    pub fn new() -> Option<Self> {
50        Self::alloc()?.init()
51    }
52
53    /// Create from a raw pointer.
54    ///
55    /// # Safety
56    ///
57    /// The pointer must be a valid Metal log state descriptor.
58    #[inline]
59    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
60        NonNull::new(ptr).map(Self)
61    }
62
63    /// Get the raw pointer.
64    #[inline]
65    pub fn as_raw(&self) -> *mut c_void {
66        self.0.as_ptr()
67    }
68
69    // =========================================================================
70    // Properties
71    // =========================================================================
72
73    /// Get the buffer size.
74    ///
75    /// C++ equivalent: `NS::UInteger bufferSize() const`
76    #[inline]
77    pub fn buffer_size(&self) -> UInteger {
78        unsafe { msg_send_0(self.as_ptr(), sel!(bufferSize)) }
79    }
80
81    /// Set the buffer size.
82    ///
83    /// C++ equivalent: `void setBufferSize(NS::UInteger)`
84    #[inline]
85    pub fn set_buffer_size(&self, size: UInteger) {
86        unsafe {
87            msg_send_1::<(), UInteger>(self.as_ptr(), sel!(setBufferSize:), size);
88        }
89    }
90
91    /// Get the log level.
92    ///
93    /// C++ equivalent: `LogLevel level() const`
94    #[inline]
95    pub fn level(&self) -> LogLevel {
96        unsafe { msg_send_0(self.as_ptr(), sel!(level)) }
97    }
98
99    /// Set the log level.
100    ///
101    /// C++ equivalent: `void setLevel(LogLevel)`
102    #[inline]
103    pub fn set_level(&self, level: LogLevel) {
104        unsafe {
105            msg_send_1::<(), LogLevel>(self.as_ptr(), sel!(setLevel:), level);
106        }
107    }
108}
109
110impl Default for LogStateDescriptor {
111    fn default() -> Self {
112        Self::new().expect("failed to create LogStateDescriptor")
113    }
114}
115
116impl Clone for LogStateDescriptor {
117    fn clone(&self) -> Self {
118        unsafe {
119            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(copy));
120            Self::from_raw(ptr).expect("failed to copy LogStateDescriptor")
121        }
122    }
123}
124
125impl Drop for LogStateDescriptor {
126    fn drop(&mut self) {
127        unsafe {
128            msg_send_0::<()>(self.as_ptr(), sel!(release));
129        }
130    }
131}
132
133impl Referencing for LogStateDescriptor {
134    #[inline]
135    fn as_ptr(&self) -> *const c_void {
136        self.0.as_ptr()
137    }
138}
139
140unsafe impl Send for LogStateDescriptor {}
141unsafe impl Sync for LogStateDescriptor {}
142
143impl std::fmt::Debug for LogStateDescriptor {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        f.debug_struct("LogStateDescriptor")
146            .field("buffer_size", &self.buffer_size())
147            .field("level", &self.level())
148            .finish()
149    }
150}
151
152// ============================================================================
153// LogState
154// ============================================================================
155
156/// A log state for capturing GPU shader logs.
157///
158/// C++ equivalent: `MTL::LogState`
159///
160/// Log states are created from a device using a descriptor. They can be
161/// attached to command buffers to capture shader logs.
162#[repr(transparent)]
163pub struct LogState(NonNull<c_void>);
164
165impl LogState {
166    /// Create from a raw pointer.
167    ///
168    /// # Safety
169    ///
170    /// The pointer must be a valid Metal log state.
171    #[inline]
172    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
173        NonNull::new(ptr).map(Self)
174    }
175
176    /// Get the raw pointer.
177    #[inline]
178    pub fn as_raw(&self) -> *mut c_void {
179        self.0.as_ptr()
180    }
181
182    /// Add a log handler (raw block pointer version).
183    ///
184    /// C++ equivalent: `void addLogHandler(void(^)(NS::String*, NS::String*, LogLevel, NS::String*))`
185    ///
186    /// # Safety
187    ///
188    /// The block pointer must be a valid Objective-C block.
189    pub unsafe fn add_log_handler_raw(&self, block: *const c_void) {
190        unsafe {
191            msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(addLogHandler:), block);
192        }
193    }
194
195    /// Add a log handler to receive shader log messages.
196    ///
197    /// C++ equivalent: `void addLogHandler(void(^)(NS::String*, NS::String*, LogLevel, NS::String*))`
198    ///
199    /// The handler is called with:
200    /// - `subsystem` - The subsystem that generated the log
201    /// - `category` - The category of the log message
202    /// - `level` - The severity level of the log message
203    /// - `message` - The actual log message content
204    pub fn add_log_handler<F>(&self, handler: F)
205    where
206        F: Fn(&str, &str, LogLevel, &str) + Send + 'static,
207    {
208        let block = mtl_sys::LogHandlerBlock::from_fn(
209            move |subsystem_ptr: *mut c_void,
210                  category_ptr: *mut c_void,
211                  level: isize,
212                  message_ptr: *mut c_void| {
213                unsafe {
214                    // Convert NSString pointers to Rust strings
215                    let subsystem = nsstring_to_str(subsystem_ptr);
216                    let category = nsstring_to_str(category_ptr);
217                    let message = nsstring_to_str(message_ptr);
218
219                    // Create LogLevel from the raw integer value
220                    let log_level = LogLevel(level);
221
222                    handler(&subsystem, &category, log_level, &message);
223                }
224            },
225        );
226
227        unsafe {
228            msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(addLogHandler:), block.as_ptr());
229        }
230
231        // Transfer ownership to Metal
232        std::mem::forget(block);
233    }
234}
235
236/// Convert an NSString pointer to a Rust String.
237///
238/// # Safety
239///
240/// The pointer must be a valid NSString or null.
241unsafe fn nsstring_to_str(ns_string: *mut c_void) -> String {
242    if ns_string.is_null() {
243        return String::new();
244    }
245
246    unsafe {
247        let c_str: *const i8 = msg_send_0(ns_string, sel!(UTF8String));
248        if c_str.is_null() {
249            return String::new();
250        }
251
252        std::ffi::CStr::from_ptr(c_str)
253            .to_string_lossy()
254            .into_owned()
255    }
256}
257
258impl Clone for LogState {
259    fn clone(&self) -> Self {
260        unsafe {
261            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
262        }
263        Self(self.0)
264    }
265}
266
267impl Drop for LogState {
268    fn drop(&mut self) {
269        unsafe {
270            msg_send_0::<()>(self.as_ptr(), sel!(release));
271        }
272    }
273}
274
275impl Referencing for LogState {
276    #[inline]
277    fn as_ptr(&self) -> *const c_void {
278        self.0.as_ptr()
279    }
280}
281
282unsafe impl Send for LogState {}
283unsafe impl Sync for LogState {}
284
285impl std::fmt::Debug for LogState {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        f.debug_struct("LogState").finish()
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_log_state_descriptor_size() {
297        assert_eq!(
298            std::mem::size_of::<LogStateDescriptor>(),
299            std::mem::size_of::<*mut c_void>()
300        );
301    }
302
303    #[test]
304    fn test_log_state_size() {
305        assert_eq!(
306            std::mem::size_of::<LogState>(),
307            std::mem::size_of::<*mut c_void>()
308        );
309    }
310}