Skip to main content

oximedia_gpu/
queue.rs

1//! Command queue management for GPU operations
2//!
3//! This module provides abstractions for managing command queues,
4//! including command buffer submission, synchronization, and queue families.
5
6use crate::GpuDevice;
7use std::sync::Arc;
8use wgpu::{CommandBuffer, CommandEncoder, Queue, SubmissionIndex};
9
10/// Queue type for different workload categories
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum QueueType {
13    /// Compute queue for compute operations
14    Compute,
15    /// Transfer queue for data transfers
16    Transfer,
17    /// Graphics queue for graphics operations (rarely used in compute-only context)
18    Graphics,
19}
20
21/// Command queue wrapper with additional functionality
22pub struct CommandQueue {
23    queue: Arc<Queue>,
24    device: Arc<wgpu::Device>,
25    queue_type: QueueType,
26}
27
28impl CommandQueue {
29    /// Create a new command queue
30    #[must_use]
31    pub fn new(device: &GpuDevice, queue_type: QueueType) -> Self {
32        Self {
33            queue: Arc::clone(device.queue()),
34            device: Arc::clone(device.device()),
35            queue_type,
36        }
37    }
38
39    /// Submit a single command buffer to the queue
40    ///
41    /// # Arguments
42    ///
43    /// * `command_buffer` - Command buffer to submit
44    ///
45    /// # Returns
46    ///
47    /// Submission index for synchronization
48    #[must_use]
49    pub fn submit_single(&self, command_buffer: CommandBuffer) -> SubmissionIndex {
50        self.queue.submit(Some(command_buffer))
51    }
52
53    /// Submit multiple command buffers to the queue
54    ///
55    /// # Arguments
56    ///
57    /// * `command_buffers` - Command buffers to submit
58    ///
59    /// # Returns
60    ///
61    /// Submission index for synchronization
62    #[must_use]
63    pub fn submit_many(&self, command_buffers: Vec<CommandBuffer>) -> SubmissionIndex {
64        self.queue.submit(command_buffers)
65    }
66
67    /// Submit commands created by an encoder
68    ///
69    /// # Arguments
70    ///
71    /// * `encoder` - Command encoder to finish and submit
72    ///
73    /// # Returns
74    ///
75    /// Submission index for synchronization
76    #[must_use]
77    pub fn submit_encoder(&self, encoder: CommandEncoder) -> SubmissionIndex {
78        self.queue.submit(Some(encoder.finish()))
79    }
80
81    /// Write data directly to a buffer
82    ///
83    /// This is a convenience method that bypasses the staging buffer
84    /// and directly writes to the destination buffer.
85    ///
86    /// # Arguments
87    ///
88    /// * `buffer` - Target buffer
89    /// * `offset` - Offset in bytes
90    /// * `data` - Data to write
91    pub fn write_buffer(&self, buffer: &wgpu::Buffer, offset: u64, data: &[u8]) {
92        self.queue.write_buffer(buffer, offset, data);
93    }
94
95    /// Wait for all pending operations on this queue to complete
96    pub fn wait(&self) {
97        self.device.poll(wgpu::Maintain::Wait);
98    }
99
100    /// Get the queue type
101    #[must_use]
102    pub fn queue_type(&self) -> QueueType {
103        self.queue_type
104    }
105
106    /// Get the underlying WGPU queue
107    #[must_use]
108    pub fn queue(&self) -> &Arc<Queue> {
109        &self.queue
110    }
111}
112
113/// Queue manager for multi-queue support
114pub struct QueueManager {
115    compute_queue: CommandQueue,
116    transfer_queue: CommandQueue,
117    graphics_queue: CommandQueue,
118}
119
120impl QueueManager {
121    /// Create a new queue manager
122    ///
123    /// Note: In wgpu, we typically have a single queue that handles all operations.
124    /// This abstraction provides a logical separation for different workload types.
125    #[must_use]
126    pub fn new(device: &GpuDevice) -> Self {
127        Self {
128            compute_queue: CommandQueue::new(device, QueueType::Compute),
129            transfer_queue: CommandQueue::new(device, QueueType::Transfer),
130            graphics_queue: CommandQueue::new(device, QueueType::Graphics),
131        }
132    }
133
134    /// Get the compute queue
135    #[must_use]
136    pub fn compute(&self) -> &CommandQueue {
137        &self.compute_queue
138    }
139
140    /// Get the transfer queue
141    #[must_use]
142    pub fn transfer(&self) -> &CommandQueue {
143        &self.transfer_queue
144    }
145
146    /// Get the graphics queue
147    #[must_use]
148    pub fn graphics(&self) -> &CommandQueue {
149        &self.graphics_queue
150    }
151
152    /// Get a queue by type
153    #[must_use]
154    pub fn get_queue(&self, queue_type: QueueType) -> &CommandQueue {
155        match queue_type {
156            QueueType::Compute => &self.compute_queue,
157            QueueType::Transfer => &self.transfer_queue,
158            QueueType::Graphics => &self.graphics_queue,
159        }
160    }
161
162    /// Wait for all queues to complete
163    pub fn wait_all(&self) {
164        self.compute_queue.wait();
165        self.transfer_queue.wait();
166        self.graphics_queue.wait();
167    }
168}
169
170/// Command buffer builder with fluent API
171pub struct CommandBufferBuilder {
172    encoder: CommandEncoder,
173    label: String,
174}
175
176impl CommandBufferBuilder {
177    /// Create a new command buffer builder
178    pub fn new(device: &GpuDevice, label: impl Into<String>) -> Self {
179        let label_string = label.into();
180        let encoder = device
181            .device()
182            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
183                label: Some(&label_string),
184            });
185
186        Self {
187            encoder,
188            label: label_string,
189        }
190    }
191
192    /// Get a mutable reference to the encoder
193    pub fn encoder(&mut self) -> &mut CommandEncoder {
194        &mut self.encoder
195    }
196
197    /// Finish building and return the command buffer
198    #[must_use]
199    pub fn finish(self) -> CommandBuffer {
200        self.encoder.finish()
201    }
202
203    /// Finish building and submit to a queue
204    #[must_use]
205    pub fn submit(self, queue: &CommandQueue) -> SubmissionIndex {
206        queue.submit_encoder(self.encoder)
207    }
208
209    /// Get the label
210    #[must_use]
211    pub fn label(&self) -> &str {
212        &self.label
213    }
214}
215
216/// Async command submission handle
217pub struct AsyncSubmission {
218    submission_index: SubmissionIndex,
219    device: Arc<wgpu::Device>,
220}
221
222impl AsyncSubmission {
223    /// Create a new async submission handle
224    #[must_use]
225    pub fn new(submission_index: SubmissionIndex, device: Arc<wgpu::Device>) -> Self {
226        Self {
227            submission_index,
228            device,
229        }
230    }
231
232    /// Wait for this submission to complete
233    pub fn wait(&self) {
234        self.device.poll(wgpu::Maintain::Wait);
235    }
236
237    /// Get the submission index
238    #[must_use]
239    pub fn index(&self) -> &SubmissionIndex {
240        &self.submission_index
241    }
242}
243
244/// Batch command submission for improved performance
245pub struct BatchSubmitter {
246    command_buffers: Vec<CommandBuffer>,
247    max_batch_size: usize,
248}
249
250impl BatchSubmitter {
251    /// Create a new batch submitter
252    ///
253    /// # Arguments
254    ///
255    /// * `max_batch_size` - Maximum number of command buffers to batch
256    #[must_use]
257    pub fn new(max_batch_size: usize) -> Self {
258        Self {
259            command_buffers: Vec::with_capacity(max_batch_size),
260            max_batch_size,
261        }
262    }
263
264    /// Add a command buffer to the batch
265    ///
266    /// If the batch is full, it will be automatically submitted.
267    ///
268    /// # Arguments
269    ///
270    /// * `command_buffer` - Command buffer to add
271    /// * `queue` - Queue to submit to when batch is full
272    ///
273    /// # Returns
274    ///
275    /// Submission index if batch was submitted, None otherwise
276    pub fn add(
277        &mut self,
278        command_buffer: CommandBuffer,
279        queue: &CommandQueue,
280    ) -> Option<SubmissionIndex> {
281        self.command_buffers.push(command_buffer);
282
283        if self.command_buffers.len() >= self.max_batch_size {
284            Some(self.flush(queue))
285        } else {
286            None
287        }
288    }
289
290    /// Flush all pending command buffers to the queue
291    ///
292    /// # Arguments
293    ///
294    /// * `queue` - Queue to submit to
295    ///
296    /// # Returns
297    ///
298    /// Submission index
299    pub fn flush(&mut self, queue: &CommandQueue) -> SubmissionIndex {
300        let buffers = std::mem::take(&mut self.command_buffers);
301        queue.submit_many(buffers)
302    }
303
304    /// Get the number of pending command buffers
305    #[must_use]
306    pub fn pending_count(&self) -> usize {
307        self.command_buffers.len()
308    }
309
310    /// Check if the batch is empty
311    #[must_use]
312    pub fn is_empty(&self) -> bool {
313        self.command_buffers.is_empty()
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    #[test]
322    fn test_queue_type() {
323        assert_eq!(QueueType::Compute, QueueType::Compute);
324        assert_ne!(QueueType::Compute, QueueType::Transfer);
325        assert_ne!(QueueType::Compute, QueueType::Graphics);
326    }
327
328    #[test]
329    fn test_batch_submitter() {
330        let submitter = BatchSubmitter::new(5);
331
332        assert_eq!(submitter.pending_count(), 0);
333        assert!(submitter.is_empty());
334    }
335}