Skip to main content

mtl_gpu/command_queue/
mod.rs

1//! Metal command queue.
2//!
3//! Corresponds to `Metal/MTLCommandQueue.hpp`.
4//!
5//! A command queue manages the execution of command buffers on a device.
6
7use std::ffi::c_void;
8use std::ptr::NonNull;
9
10use mtl_foundation::{Referencing, UInteger};
11use mtl_sys::{msg_send_0, msg_send_1, sel};
12
13// ============================================================================
14// CommandQueueDescriptor
15// ============================================================================
16
17/// A descriptor for configuring command queue creation.
18///
19/// C++ equivalent: `MTL::CommandQueueDescriptor`
20#[repr(transparent)]
21pub struct CommandQueueDescriptor(NonNull<c_void>);
22
23impl CommandQueueDescriptor {
24    /// Create a new CommandQueueDescriptor from a raw pointer.
25    ///
26    /// # Safety
27    ///
28    /// The pointer must be a valid Metal command queue descriptor object.
29    #[inline]
30    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
31        NonNull::new(ptr).map(Self)
32    }
33
34    /// Get the raw pointer to the descriptor.
35    #[inline]
36    pub fn as_raw(&self) -> *mut c_void {
37        self.0.as_ptr()
38    }
39
40    /// Create a new command queue descriptor.
41    ///
42    /// C++ equivalent: `CommandQueueDescriptor::alloc()->init()`
43    pub fn new() -> Option<Self> {
44        unsafe {
45            let class = mtl_sys::class!(MTLCommandQueueDescriptor);
46            let obj: *mut c_void = msg_send_0(class.as_ptr(), sel!(alloc));
47            if obj.is_null() {
48                return None;
49            }
50            let obj: *mut c_void = msg_send_0(obj, sel!(init));
51            Self::from_raw(obj)
52        }
53    }
54
55    // =========================================================================
56    // Properties
57    // =========================================================================
58
59    /// Get the maximum number of command buffers in flight.
60    ///
61    /// C++ equivalent: `NS::UInteger maxCommandBufferCount() const`
62    #[inline]
63    pub fn max_command_buffer_count(&self) -> UInteger {
64        unsafe { msg_send_0(self.as_ptr(), sel!(maxCommandBufferCount)) }
65    }
66
67    /// Set the maximum number of command buffers in flight.
68    ///
69    /// C++ equivalent: `void setMaxCommandBufferCount(NS::UInteger maxCommandBufferCount)`
70    #[inline]
71    pub fn set_max_command_buffer_count(&self, count: UInteger) {
72        unsafe {
73            msg_send_1::<(), UInteger>(self.as_ptr(), sel!(setMaxCommandBufferCount:), count);
74        }
75    }
76
77    /// Get the log state for the command queue.
78    ///
79    /// C++ equivalent: `LogState* logState() const`
80    ///
81    /// Returns a raw pointer to the log state object.
82    #[inline]
83    pub fn log_state(&self) -> *mut c_void {
84        unsafe { msg_send_0(self.as_ptr(), sel!(logState)) }
85    }
86
87    /// Set the log state for the command queue.
88    ///
89    /// C++ equivalent: `void setLogState(const LogState* logState)`
90    ///
91    /// # Safety
92    ///
93    /// The log_state pointer must be valid or null.
94    #[inline]
95    pub unsafe fn set_log_state(&self, log_state: *const c_void) {
96        unsafe {
97            msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(setLogState:), log_state);
98        }
99    }
100}
101
102impl Default for CommandQueueDescriptor {
103    fn default() -> Self {
104        Self::new().expect("failed to create CommandQueueDescriptor")
105    }
106}
107
108impl Clone for CommandQueueDescriptor {
109    fn clone(&self) -> Self {
110        unsafe {
111            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
112        }
113        Self(self.0)
114    }
115}
116
117impl Drop for CommandQueueDescriptor {
118    fn drop(&mut self) {
119        unsafe {
120            msg_send_0::<()>(self.as_ptr(), sel!(release));
121        }
122    }
123}
124
125impl Referencing for CommandQueueDescriptor {
126    #[inline]
127    fn as_ptr(&self) -> *const c_void {
128        self.0.as_ptr()
129    }
130}
131
132unsafe impl Send for CommandQueueDescriptor {}
133unsafe impl Sync for CommandQueueDescriptor {}
134
135impl std::fmt::Debug for CommandQueueDescriptor {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.debug_struct("CommandQueueDescriptor")
138            .field("max_command_buffer_count", &self.max_command_buffer_count())
139            .finish()
140    }
141}
142
143// ============================================================================
144// CommandQueue
145// ============================================================================
146
147/// A queue that organizes the order of command buffer execution.
148///
149/// C++ equivalent: `MTL::CommandQueue`
150///
151/// You typically create a command queue early in your app's lifecycle and reuse
152/// it throughout the app. Command buffers created from the same queue are
153/// guaranteed to execute in the order they are committed.
154#[repr(transparent)]
155pub struct CommandQueue(pub(crate) NonNull<c_void>);
156
157impl CommandQueue {
158    /// Create a CommandQueue from a raw pointer.
159    ///
160    /// # Safety
161    ///
162    /// The pointer must be a valid Metal command queue object.
163    #[inline]
164    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
165        NonNull::new(ptr).map(Self)
166    }
167
168    /// Get the raw pointer to the command queue.
169    #[inline]
170    pub fn as_raw(&self) -> *mut c_void {
171        self.0.as_ptr()
172    }
173
174    // =========================================================================
175    // Command Buffer Creation
176    // =========================================================================
177
178    /// Create a new command buffer.
179    ///
180    /// C++ equivalent: `CommandBuffer* commandBuffer()`
181    pub fn command_buffer(&self) -> Option<crate::command_buffer::CommandBuffer> {
182        unsafe {
183            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(commandBuffer));
184            if ptr.is_null() {
185                return None;
186            }
187            // Retain to take ownership (Metal returns autoreleased object)
188            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
189            crate::command_buffer::CommandBuffer::from_raw(ptr)
190        }
191    }
192
193    /// Create a new command buffer with an unretained reference.
194    ///
195    /// The returned command buffer is autoreleased and will be deallocated unless
196    /// you explicitly retain it.
197    ///
198    /// C++ equivalent: `CommandBuffer* commandBufferWithUnretainedReferences()`
199    pub fn command_buffer_with_unretained_references(
200        &self,
201    ) -> Option<crate::command_buffer::CommandBuffer> {
202        unsafe {
203            let ptr: *mut c_void =
204                msg_send_0(self.as_ptr(), sel!(commandBufferWithUnretainedReferences));
205            // Retain to take ownership
206            if !ptr.is_null() {
207                let _: *mut c_void = msg_send_0(ptr, sel!(retain));
208            }
209            crate::command_buffer::CommandBuffer::from_raw(ptr)
210        }
211    }
212
213    /// Create a new command buffer with a descriptor (raw pointer version).
214    ///
215    /// C++ equivalent: `CommandBuffer* commandBuffer(const CommandBufferDescriptor*)`
216    ///
217    /// # Safety
218    ///
219    /// The descriptor pointer must be valid.
220    pub unsafe fn command_buffer_with_descriptor_ptr(
221        &self,
222        descriptor: *const c_void,
223    ) -> Option<crate::command_buffer::CommandBuffer> {
224        unsafe {
225            let ptr: *mut c_void = msg_send_1(
226                self.as_ptr(),
227                sel!(commandBufferWithDescriptor:),
228                descriptor,
229            );
230            if ptr.is_null() {
231                return None;
232            }
233            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
234            crate::command_buffer::CommandBuffer::from_raw(ptr)
235        }
236    }
237
238    /// Create a new command buffer with a typed descriptor.
239    ///
240    /// C++ equivalent: `CommandBuffer* commandBuffer(const CommandBufferDescriptor*)`
241    pub fn command_buffer_with_descriptor(
242        &self,
243        descriptor: &crate::command_buffer::CommandBufferDescriptor,
244    ) -> Option<crate::command_buffer::CommandBuffer> {
245        unsafe { self.command_buffer_with_descriptor_ptr(descriptor.as_ptr()) }
246    }
247
248    // =========================================================================
249    // Properties
250    // =========================================================================
251
252    /// Get the label for this command queue.
253    ///
254    /// C++ equivalent: `NS::String* label() const`
255    pub fn label(&self) -> Option<String> {
256        unsafe {
257            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(label));
258            if ptr.is_null() {
259                return None;
260            }
261            let utf8_ptr: *const std::ffi::c_char =
262                mtl_sys::msg_send_0(ptr as *const c_void, sel!(UTF8String));
263            if utf8_ptr.is_null() {
264                return None;
265            }
266            let c_str = std::ffi::CStr::from_ptr(utf8_ptr);
267            Some(c_str.to_string_lossy().into_owned())
268        }
269    }
270
271    /// Set the label for this command queue.
272    ///
273    /// C++ equivalent: `void setLabel(const NS::String*)`
274    pub fn set_label(&self, label: &str) {
275        if let Some(ns_label) = mtl_foundation::String::from_str(label) {
276            unsafe {
277                msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(setLabel:), ns_label.as_ptr());
278            }
279        }
280    }
281
282    /// Get the device that created this command queue.
283    ///
284    /// C++ equivalent: `Device* device() const`
285    pub fn device(&self) -> crate::Device {
286        unsafe {
287            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(device));
288            let _: *mut c_void = msg_send_0(ptr, sel!(retain));
289            crate::Device::from_raw(ptr).expect("command queue has no device")
290        }
291    }
292
293    // =========================================================================
294    // Residency (macOS only)
295    // =========================================================================
296
297    /// Insert a debug capture boundary.
298    ///
299    /// C++ equivalent: `void insertDebugCaptureBoundary()`
300    #[inline]
301    pub fn insert_debug_capture_boundary(&self) {
302        unsafe {
303            msg_send_0::<()>(self.as_ptr(), sel!(insertDebugCaptureBoundary));
304        }
305    }
306
307    // =========================================================================
308    // Residency Sets
309    // =========================================================================
310
311    /// Add a residency set to the command queue.
312    ///
313    /// C++ equivalent: `void addResidencySet(const ResidencySet*)`
314    ///
315    /// # Safety
316    ///
317    /// The residency_set pointer must be valid.
318    pub unsafe fn add_residency_set(&self, residency_set: *const c_void) {
319        unsafe {
320            msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(addResidencySet:), residency_set);
321        }
322    }
323
324    /// Add multiple residency sets to the command queue.
325    ///
326    /// C++ equivalent: `void addResidencySets(const ResidencySet* const*, NS::UInteger count)`
327    ///
328    /// # Safety
329    ///
330    /// The residency_sets pointer must be valid and point to count valid pointers.
331    pub unsafe fn add_residency_sets(&self, residency_sets: *const *const c_void, count: UInteger) {
332        unsafe {
333            mtl_sys::msg_send_2::<(), *const *const c_void, UInteger>(
334                self.as_ptr(),
335                sel!(addResidencySets: count:),
336                residency_sets,
337                count,
338            );
339        }
340    }
341
342    /// Remove a residency set from the command queue.
343    ///
344    /// C++ equivalent: `void removeResidencySet(const ResidencySet*)`
345    ///
346    /// # Safety
347    ///
348    /// The residency_set pointer must be valid.
349    pub unsafe fn remove_residency_set(&self, residency_set: *const c_void) {
350        unsafe {
351            msg_send_1::<(), *const c_void>(
352                self.as_ptr(),
353                sel!(removeResidencySet:),
354                residency_set,
355            );
356        }
357    }
358
359    /// Remove multiple residency sets from the command queue.
360    ///
361    /// C++ equivalent: `void removeResidencySets(const ResidencySet* const*, NS::UInteger count)`
362    ///
363    /// # Safety
364    ///
365    /// The residency_sets pointer must be valid and point to count valid pointers.
366    pub unsafe fn remove_residency_sets(
367        &self,
368        residency_sets: *const *const c_void,
369        count: UInteger,
370    ) {
371        unsafe {
372            mtl_sys::msg_send_2::<(), *const *const c_void, UInteger>(
373                self.as_ptr(),
374                sel!(removeResidencySets: count:),
375                residency_sets,
376                count,
377            );
378        }
379    }
380
381    // =========================================================================
382    // Wait Methods
383    // =========================================================================
384
385    /// Wait for an event to reach a specific value.
386    ///
387    /// C++ equivalent: `void wait(const MTL::Event* event, uint64_t value)`
388    pub fn wait_for_event(&self, event: &crate::Event, value: u64) {
389        unsafe {
390            mtl_sys::msg_send_2::<(), *const c_void, u64>(
391                self.as_ptr(),
392                sel!(waitForEvent:value:),
393                event.as_ptr(),
394                value,
395            );
396        }
397    }
398
399    /// Wait for a drawable to become available.
400    ///
401    /// C++ equivalent: `void wait(const MTL::Drawable* drawable)`
402    ///
403    /// # Safety
404    ///
405    /// The drawable pointer must be valid.
406    pub unsafe fn wait_for_drawable(&self, drawable: *const c_void) {
407        unsafe {
408            msg_send_1::<(), *const c_void>(self.as_ptr(), sel!(waitForDrawable:), drawable);
409        }
410    }
411}
412
413impl Clone for CommandQueue {
414    fn clone(&self) -> Self {
415        unsafe {
416            msg_send_0::<*mut c_void>(self.as_ptr(), sel!(retain));
417        }
418        Self(self.0)
419    }
420}
421
422impl Drop for CommandQueue {
423    fn drop(&mut self) {
424        unsafe {
425            msg_send_0::<()>(self.as_ptr(), sel!(release));
426        }
427    }
428}
429
430impl Referencing for CommandQueue {
431    #[inline]
432    fn as_ptr(&self) -> *const c_void {
433        self.0.as_ptr()
434    }
435}
436
437unsafe impl Send for CommandQueue {}
438unsafe impl Sync for CommandQueue {}
439
440impl std::fmt::Debug for CommandQueue {
441    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442        f.debug_struct("CommandQueue")
443            .field("label", &self.label())
444            .finish()
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use super::*;
451
452    #[test]
453    fn test_command_queue_size() {
454        assert_eq!(
455            std::mem::size_of::<CommandQueue>(),
456            std::mem::size_of::<*mut c_void>()
457        );
458    }
459}