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}