Skip to main content

mtl_gpu/mtl4/
command_queue.rs

1//! MTL4 CommandQueue implementation.
2//!
3//! Corresponds to `Metal/MTL4CommandQueue.hpp`.
4
5use std::ffi::c_void;
6use std::ptr::NonNull;
7
8use mtl_foundation::{Referencing, UInteger};
9use mtl_sys::{msg_send_0, msg_send_1, msg_send_2, msg_send_3, msg_send_4, sel};
10
11use super::{CommandBuffer, CommitFeedback};
12use crate::{Device, Drawable, Event, ResidencySet};
13
14/// Dispatch queue type (opaque).
15pub type DispatchQueue = *mut c_void;
16
17// ============================================================
18// CommitOptions
19// ============================================================
20
21/// Options for committing command buffers.
22///
23/// C++ equivalent: `MTL4::CommitOptions`
24#[repr(transparent)]
25pub struct CommitOptions(NonNull<c_void>);
26
27impl CommitOptions {
28    /// Create a CommitOptions from a raw pointer.
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.
35    #[inline]
36    pub fn as_raw(&self) -> *mut c_void {
37        self.0.as_ptr()
38    }
39
40    /// Create new commit options.
41    pub fn new() -> Option<Self> {
42        unsafe {
43            let class = mtl_sys::Class::get("MTL4CommitOptions")?;
44            let ptr: *mut c_void = msg_send_0(class.as_ptr(), sel!(alloc));
45            if ptr.is_null() {
46                return None;
47            }
48            let ptr: *mut c_void = msg_send_0(ptr, sel!(init));
49            Self::from_raw(ptr)
50        }
51    }
52
53    /// Add a feedback handler to be called when the commit completes.
54    ///
55    /// C++ equivalent: `void addFeedbackHandler(void (^)(MTL4::CommitFeedback*))`
56    ///
57    /// The handler is called with the commit feedback when the commit completes.
58    pub fn add_feedback_handler<F>(&self, handler: F)
59    where
60        F: Fn(&CommitFeedback) + Send + 'static,
61    {
62        let block = mtl_sys::OneArgBlock::from_fn(move |feedback_ptr: *mut c_void| {
63            unsafe {
64                if let Some(feedback) = CommitFeedback::from_raw(feedback_ptr) {
65                    handler(&feedback);
66                    // Don't drop - Metal owns this reference
67                    std::mem::forget(feedback);
68                }
69            }
70        });
71
72        unsafe {
73            msg_send_1::<(), *const c_void>(
74                self.as_ptr(),
75                sel!(addFeedbackHandler:),
76                block.as_ptr(),
77            );
78        }
79
80        // The block is retained by Metal
81        std::mem::forget(block);
82    }
83}
84
85impl Clone for CommitOptions {
86    fn clone(&self) -> Self {
87        unsafe {
88            mtl_sys::msg_send_0::<*mut c_void>(self.as_ptr(), mtl_sys::sel!(retain));
89        }
90        Self(self.0)
91    }
92}
93
94impl Drop for CommitOptions {
95    fn drop(&mut self) {
96        unsafe {
97            mtl_sys::msg_send_0::<()>(self.as_ptr(), mtl_sys::sel!(release));
98        }
99    }
100}
101
102impl Referencing for CommitOptions {
103    #[inline]
104    fn as_ptr(&self) -> *const c_void {
105        self.0.as_ptr()
106    }
107}
108
109unsafe impl Send for CommitOptions {}
110unsafe impl Sync for CommitOptions {}
111
112// ============================================================
113// CommandQueueDescriptor
114// ============================================================
115
116/// Descriptor for creating a MTL4 command queue.
117///
118/// C++ equivalent: `MTL4::CommandQueueDescriptor`
119#[repr(transparent)]
120pub struct CommandQueueDescriptor(NonNull<c_void>);
121
122impl CommandQueueDescriptor {
123    /// Create a CommandQueueDescriptor from a raw pointer.
124    #[inline]
125    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
126        NonNull::new(ptr).map(Self)
127    }
128
129    /// Get the raw pointer.
130    #[inline]
131    pub fn as_raw(&self) -> *mut c_void {
132        self.0.as_ptr()
133    }
134
135    /// Create a new command queue descriptor.
136    pub fn new() -> Option<Self> {
137        unsafe {
138            let class = mtl_sys::Class::get("MTL4CommandQueueDescriptor")?;
139            let ptr: *mut c_void = msg_send_0(class.as_ptr(), sel!(alloc));
140            if ptr.is_null() {
141                return None;
142            }
143            let ptr: *mut c_void = msg_send_0(ptr, sel!(init));
144            Self::from_raw(ptr)
145        }
146    }
147
148    /// Get the label.
149    pub fn label(&self) -> Option<String> {
150        unsafe {
151            let ns_string: *mut c_void = msg_send_0(self.as_ptr(), sel!(label));
152            if ns_string.is_null() {
153                return None;
154            }
155            let c_str: *const i8 = msg_send_0(ns_string, sel!(UTF8String));
156            if c_str.is_null() {
157                return None;
158            }
159            Some(
160                std::ffi::CStr::from_ptr(c_str)
161                    .to_string_lossy()
162                    .into_owned(),
163            )
164        }
165    }
166
167    /// Set the label.
168    pub fn set_label(&self, label: &str) {
169        if let Some(ns_label) = mtl_foundation::String::from_str(label) {
170            unsafe {
171                let _: () = msg_send_1(self.as_ptr(), sel!(setLabel:), ns_label.as_ptr());
172            }
173        }
174    }
175
176    /// Get the feedback queue.
177    ///
178    /// C++ equivalent: `dispatch_queue_t feedbackQueue() const`
179    pub fn feedback_queue(&self) -> DispatchQueue {
180        unsafe { msg_send_0(self.as_ptr(), sel!(feedbackQueue)) }
181    }
182
183    /// Set the feedback queue.
184    ///
185    /// C++ equivalent: `void setFeedbackQueue(const dispatch_queue_t)`
186    pub fn set_feedback_queue(&self, queue: DispatchQueue) {
187        unsafe {
188            let _: () = msg_send_1(self.as_ptr(), sel!(setFeedbackQueue:), queue);
189        }
190    }
191}
192
193impl Clone for CommandQueueDescriptor {
194    fn clone(&self) -> Self {
195        unsafe {
196            mtl_sys::msg_send_0::<*mut c_void>(self.as_ptr(), mtl_sys::sel!(retain));
197        }
198        Self(self.0)
199    }
200}
201
202impl Drop for CommandQueueDescriptor {
203    fn drop(&mut self) {
204        unsafe {
205            mtl_sys::msg_send_0::<()>(self.as_ptr(), mtl_sys::sel!(release));
206        }
207    }
208}
209
210impl Referencing for CommandQueueDescriptor {
211    #[inline]
212    fn as_ptr(&self) -> *const c_void {
213        self.0.as_ptr()
214    }
215}
216
217unsafe impl Send for CommandQueueDescriptor {}
218unsafe impl Sync for CommandQueueDescriptor {}
219
220// ============================================================
221// CommandQueue
222// ============================================================
223
224/// MTL4 command queue with explicit residency management.
225///
226/// C++ equivalent: `MTL4::CommandQueue`
227///
228/// CommandQueue in Metal 4 provides explicit control over residency sets
229/// and command buffer submission.
230#[repr(transparent)]
231pub struct CommandQueue(NonNull<c_void>);
232
233impl CommandQueue {
234    /// Create a CommandQueue from a raw pointer.
235    #[inline]
236    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
237        NonNull::new(ptr).map(Self)
238    }
239
240    /// Get the raw pointer.
241    #[inline]
242    pub fn as_raw(&self) -> *mut c_void {
243        self.0.as_ptr()
244    }
245
246    /// Get the device.
247    ///
248    /// C++ equivalent: `MTL::Device* device() const`
249    pub fn device(&self) -> Option<Device> {
250        unsafe {
251            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(device));
252            Device::from_raw(ptr)
253        }
254    }
255
256    /// Get the label.
257    ///
258    /// C++ equivalent: `NS::String* label() const`
259    pub fn label(&self) -> Option<String> {
260        unsafe {
261            let ns_string: *mut c_void = msg_send_0(self.as_ptr(), sel!(label));
262            if ns_string.is_null() {
263                return None;
264            }
265            let c_str: *const i8 = msg_send_0(ns_string, sel!(UTF8String));
266            if c_str.is_null() {
267                return None;
268            }
269            Some(
270                std::ffi::CStr::from_ptr(c_str)
271                    .to_string_lossy()
272                    .into_owned(),
273            )
274        }
275    }
276
277    // ========== Residency Management ==========
278
279    /// Add a residency set.
280    ///
281    /// C++ equivalent: `void addResidencySet(const MTL::ResidencySet*)`
282    pub fn add_residency_set(&self, residency_set: &ResidencySet) {
283        unsafe {
284            let _: () = msg_send_1(
285                self.as_ptr(),
286                sel!(addResidencySet:),
287                residency_set.as_ptr(),
288            );
289        }
290    }
291
292    /// Add multiple residency sets.
293    ///
294    /// C++ equivalent: `void addResidencySets(const MTL::ResidencySet* const[], NS::UInteger count)`
295    pub fn add_residency_sets(&self, residency_sets: &[&ResidencySet]) {
296        let ptrs: Vec<*const c_void> = residency_sets.iter().map(|r| r.as_ptr()).collect();
297        unsafe {
298            let _: () = msg_send_2(
299                self.as_ptr(),
300                sel!(addResidencySets:count:),
301                ptrs.as_ptr(),
302                ptrs.len() as UInteger,
303            );
304        }
305    }
306
307    /// Remove a residency set.
308    ///
309    /// C++ equivalent: `void removeResidencySet(const MTL::ResidencySet*)`
310    pub fn remove_residency_set(&self, residency_set: &ResidencySet) {
311        unsafe {
312            let _: () = msg_send_1(
313                self.as_ptr(),
314                sel!(removeResidencySet:),
315                residency_set.as_ptr(),
316            );
317        }
318    }
319
320    /// Remove multiple residency sets.
321    ///
322    /// C++ equivalent: `void removeResidencySets(const MTL::ResidencySet* const[], NS::UInteger count)`
323    pub fn remove_residency_sets(&self, residency_sets: &[&ResidencySet]) {
324        let ptrs: Vec<*const c_void> = residency_sets.iter().map(|r| r.as_ptr()).collect();
325        unsafe {
326            let _: () = msg_send_2(
327                self.as_ptr(),
328                sel!(removeResidencySets:count:),
329                ptrs.as_ptr(),
330                ptrs.len() as UInteger,
331            );
332        }
333    }
334
335    // ========== Command Buffer Creation ==========
336
337    /// Create a new MTL4 command buffer.
338    ///
339    /// C++ equivalent: `MTL4::CommandBuffer* newCommandBuffer()`
340    ///
341    /// The returned command buffer must be used with `begin_command_buffer()`
342    /// and `end_command_buffer()` to record commands.
343    pub fn new_command_buffer(&self) -> Option<CommandBuffer> {
344        unsafe {
345            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(newCommandBuffer));
346            if ptr.is_null() {
347                None
348            } else {
349                CommandBuffer::from_raw(ptr)
350            }
351        }
352    }
353
354    // ========== Commit ==========
355
356    /// Commit command buffers for execution.
357    ///
358    /// C++ equivalent: `void commit(const MTL4::CommandBuffer* const[], NS::UInteger count)`
359    pub fn commit(&self, command_buffers: &[&CommandBuffer]) {
360        let ptrs: Vec<*const c_void> = command_buffers.iter().map(|c| c.as_ptr()).collect();
361        unsafe {
362            let _: () = msg_send_2(
363                self.as_ptr(),
364                sel!(commit:count:),
365                ptrs.as_ptr(),
366                ptrs.len() as UInteger,
367            );
368        }
369    }
370
371    /// Commit command buffers with options.
372    ///
373    /// C++ equivalent: `void commit(const MTL4::CommandBuffer* const[], NS::UInteger, const MTL4::CommitOptions*)`
374    pub fn commit_with_options(&self, command_buffers: &[&CommandBuffer], options: &CommitOptions) {
375        let ptrs: Vec<*const c_void> = command_buffers.iter().map(|c| c.as_ptr()).collect();
376        unsafe {
377            let _: () = msg_send_3(
378                self.as_ptr(),
379                sel!(commit:count:options:),
380                ptrs.as_ptr(),
381                ptrs.len() as UInteger,
382                options.as_ptr(),
383            );
384        }
385    }
386
387    // ========== Synchronization ==========
388
389    /// Signal an event with a value.
390    ///
391    /// C++ equivalent: `void signalEvent(const MTL::Event*, uint64_t value)`
392    pub fn signal_event(&self, event: &Event, value: u64) {
393        unsafe {
394            let _: () = msg_send_2(
395                self.as_ptr(),
396                sel!(signalEvent:value:),
397                event.as_ptr(),
398                value,
399            );
400        }
401    }
402
403    /// Wait for an event to reach a value.
404    ///
405    /// C++ equivalent: `void wait(const MTL::Event*, uint64_t value)`
406    pub fn wait_for_event(&self, event: &Event, value: u64) {
407        unsafe {
408            let _: () = msg_send_2(
409                self.as_ptr(),
410                sel!(waitForEvent:value:),
411                event.as_ptr(),
412                value,
413            );
414        }
415    }
416
417    /// Signal a drawable for presentation.
418    ///
419    /// C++ equivalent: `void signalDrawable(const MTL::Drawable*)`
420    pub fn signal_drawable(&self, drawable: &Drawable) {
421        unsafe {
422            let _: () = msg_send_1(self.as_ptr(), sel!(signalDrawable:), drawable.as_ptr());
423        }
424    }
425
426    /// Wait for a drawable.
427    ///
428    /// C++ equivalent: `void wait(const MTL::Drawable*)`
429    pub fn wait_for_drawable(&self, drawable: &Drawable) {
430        unsafe {
431            let _: () = msg_send_1(self.as_ptr(), sel!(waitForDrawable:), drawable.as_ptr());
432        }
433    }
434
435    // ========== Sparse Resource Mapping ==========
436
437    /// Copy buffer mappings from one buffer to another.
438    ///
439    /// C++ equivalent: `void copyBufferMappingsFromBuffer(const MTL::Buffer*, const MTL::Buffer*, const MTL4::CopySparseBufferMappingOperation*, NS::UInteger)`
440    ///
441    /// # Safety
442    ///
443    /// The operations pointer must be valid and point to `count` operations.
444    pub unsafe fn copy_buffer_mappings_from_buffer(
445        &self,
446        source_buffer: *const c_void,
447        destination_buffer: *const c_void,
448        operations: *const c_void,
449        count: UInteger,
450    ) {
451        unsafe {
452            let _: () = msg_send_4(
453                self.as_ptr(),
454                sel!(copyBufferMappingsFromBuffer:toBuffer:operations:count:),
455                source_buffer,
456                destination_buffer,
457                operations,
458                count,
459            );
460        }
461    }
462
463    /// Copy texture mappings from one texture to another.
464    ///
465    /// C++ equivalent: `void copyTextureMappingsFromTexture(const MTL::Texture*, const MTL::Texture*, const MTL4::CopySparseTextureMappingOperation*, NS::UInteger)`
466    ///
467    /// # Safety
468    ///
469    /// The operations pointer must be valid and point to `count` operations.
470    pub unsafe fn copy_texture_mappings_from_texture(
471        &self,
472        source_texture: *const c_void,
473        destination_texture: *const c_void,
474        operations: *const c_void,
475        count: UInteger,
476    ) {
477        unsafe {
478            let _: () = msg_send_4(
479                self.as_ptr(),
480                sel!(copyTextureMappingsFromTexture:toTexture:operations:count:),
481                source_texture,
482                destination_texture,
483                operations,
484                count,
485            );
486        }
487    }
488
489    /// Update buffer mappings for a sparse buffer.
490    ///
491    /// C++ equivalent: `void updateBufferMappings(const MTL::Buffer*, const MTL::Heap*, const MTL4::UpdateSparseBufferMappingOperation*, NS::UInteger)`
492    ///
493    /// # Safety
494    ///
495    /// The operations pointer must be valid and point to `count` operations.
496    pub unsafe fn update_buffer_mappings(
497        &self,
498        buffer: *const c_void,
499        heap: *const c_void,
500        operations: *const c_void,
501        count: UInteger,
502    ) {
503        unsafe {
504            let _: () = msg_send_4(
505                self.as_ptr(),
506                sel!(updateBufferMappings:heap:operations:count:),
507                buffer,
508                heap,
509                operations,
510                count,
511            );
512        }
513    }
514
515    /// Update texture mappings for a sparse texture.
516    ///
517    /// C++ equivalent: `void updateTextureMappings(const MTL::Texture*, const MTL::Heap*, const MTL4::UpdateSparseTextureMappingOperation*, NS::UInteger)`
518    ///
519    /// # Safety
520    ///
521    /// The operations pointer must be valid and point to `count` operations.
522    pub unsafe fn update_texture_mappings(
523        &self,
524        texture: *const c_void,
525        heap: *const c_void,
526        operations: *const c_void,
527        count: UInteger,
528    ) {
529        unsafe {
530            let _: () = msg_send_4(
531                self.as_ptr(),
532                sel!(updateTextureMappings:heap:operations:count:),
533                texture,
534                heap,
535                operations,
536                count,
537            );
538        }
539    }
540}
541
542impl Clone for CommandQueue {
543    fn clone(&self) -> Self {
544        unsafe {
545            mtl_sys::msg_send_0::<*mut c_void>(self.as_ptr(), mtl_sys::sel!(retain));
546        }
547        Self(self.0)
548    }
549}
550
551impl Drop for CommandQueue {
552    fn drop(&mut self) {
553        unsafe {
554            mtl_sys::msg_send_0::<()>(self.as_ptr(), mtl_sys::sel!(release));
555        }
556    }
557}
558
559impl Referencing for CommandQueue {
560    #[inline]
561    fn as_ptr(&self) -> *const c_void {
562        self.0.as_ptr()
563    }
564}
565
566unsafe impl Send for CommandQueue {}
567unsafe impl Sync for CommandQueue {}
568
569impl std::fmt::Debug for CommandQueue {
570    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571        f.debug_struct("CommandQueue")
572            .field("label", &self.label())
573            .finish()
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580
581    #[test]
582    fn test_commit_options_size() {
583        assert_eq!(
584            std::mem::size_of::<CommitOptions>(),
585            std::mem::size_of::<*mut c_void>()
586        );
587    }
588
589    #[test]
590    fn test_command_queue_descriptor_size() {
591        assert_eq!(
592            std::mem::size_of::<CommandQueueDescriptor>(),
593            std::mem::size_of::<*mut c_void>()
594        );
595    }
596
597    #[test]
598    fn test_command_queue_size() {
599        assert_eq!(
600            std::mem::size_of::<CommandQueue>(),
601            std::mem::size_of::<*mut c_void>()
602        );
603    }
604}