phobos/core/
queue.rs

1//! Exposes Vulkan queue objects, though these are always abstracted through the ExecutionManager.
2
3use std::sync::{Arc, Mutex, MutexGuard};
4
5use anyhow::Result;
6use ash::vk;
7
8use crate::{
9    Allocator, CmdBuffer, DescriptorCache, Device, Error, Fence, IncompleteCmdBuffer, PipelineCache,
10};
11use crate::command_buffer::command_pool::CommandPool;
12
13/// Abstraction over vulkan queue capabilities. Note that in raw Vulkan, there is no 'Graphics queue'. Phobos will expose one, but behind the scenes the exposed
14/// e.g. graphics and transfer queues could point to the same hardware queue. Synchronization for this is handled for you.
15#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
16#[repr(u32)]
17pub enum QueueType {
18    /// Queue that supports graphics operations. Per the vulkan spec, this queue also always supports
19    /// transfer operations. Phobos will try to match this to a hardware queue that also
20    /// supports compute operations. This is always guaranteed to be available if graphics operations
21    /// are supported.
22    #[default]
23    Graphics = vk::QueueFlags::GRAPHICS.as_raw(),
24    /// Queue that supports compute operations. Per the vulkan spec, this queue also always supports
25    /// transfer operations. Phobos will try to match this to a hardware queue that does not support
26    /// graphics operations if possible, to make full use of async compute when available.
27    Compute = vk::QueueFlags::COMPUTE.as_raw(),
28    /// Queue that supports transfer operations. Phobos will try to match this to a hardware queue that only supports
29    /// transfer operations if possible.
30    Transfer = vk::QueueFlags::TRANSFER.as_raw(),
31}
32
33/// Stores all information of a queue that was found on the physical device.
34#[derive(Default, Debug, Copy, Clone)]
35pub struct QueueInfo {
36    /// Functionality that this queue provides.
37    pub queue_type: QueueType,
38    /// Whether this is a dedicated queue or not.
39    pub dedicated: bool,
40    /// Whether this queue is capable of presenting to a surface.
41    pub can_present: bool,
42    /// The queue family index.
43    pub family_index: u32,
44    /// All supported operations on this queue, instead of its primary type.
45    pub flags: vk::QueueFlags,
46}
47
48/// Physical VkQueue object.
49#[derive(Debug)]
50pub(crate) struct DeviceQueue {
51    pub handle: vk::Queue,
52}
53
54/// Exposes a logical command queue on the device. Note that the physical `VkQueue` object could be multiplexed
55/// between different logical queues (e.g. on devices with only one queue).
56#[derive(Derivative)]
57#[derivative(Debug)]
58pub struct Queue {
59    #[derivative(Debug = "ignore")]
60    device: Device,
61    queue: Arc<Mutex<DeviceQueue>>,
62    /// Note that we are only creating one command pool.
63    /// We will need to provide thread-safe access to this pool.
64    /// TODO: measure lock contention on command pools and determine if we need a queue of pools to pull from instead.
65    pool: CommandPool,
66    /// Information about this queue, such as supported operations, family index, etc. See also [`QueueInfo`]
67    info: QueueInfo,
68    /// This queues queue family properties.
69    family_properties: vk::QueueFamilyProperties,
70}
71
72impl Queue {
73    pub(crate) fn new(
74        device: Device,
75        queue: Arc<Mutex<DeviceQueue>>,
76        info: QueueInfo,
77        family_properties: vk::QueueFamilyProperties,
78    ) -> Result<Self> {
79        // We create a transient command pool because command buffers will be allocated and deallocated
80        // frequently.
81        let pool = CommandPool::new(
82            device.clone(),
83            info.family_index,
84            vk::CommandPoolCreateFlags::TRANSIENT,
85        )?;
86        Ok(Queue {
87            device,
88            queue,
89            pool,
90            info,
91            family_properties,
92        })
93    }
94
95    fn acquire_device_queue(&self) -> Result<MutexGuard<DeviceQueue>> {
96        Ok(self.queue.lock().map_err(|_| Error::PoisonError)?)
97    }
98
99    /// Submits a batch of submissions to the queue, and signals the given fence when the
100    /// submission is done. When possible, prefer submitting through the
101    /// execution manager.
102    #[deprecated(since = "0.9.0", note = "Prefer using Queue::submit2() instead to make full use of the synchronization2 feature.")]
103    pub fn submit(&self, submits: &[vk::SubmitInfo], fence: Option<&Fence>) -> Result<()> {
104        let fence = match fence {
105            None => vk::Fence::null(),
106            // SAFETY: The user supplied a valid fence
107            Some(fence) => unsafe { fence.handle() },
108        };
109        let queue = self.acquire_device_queue()?;
110        // SAFETY:
111        // * `fence` is null or a valid fence handle (see above).
112        // * The user supplied a valid range of `VkSubmitInfo` structures.
113        // * `queue` is a valid queue object.
114        unsafe { Ok(self.device.queue_submit(queue.handle, submits, fence)?) }
115    }
116
117    /// Submits a batch of submissions to the queue, and signals the given fence when the
118    /// submission is done. When possible, prefer submitting through the
119    /// execution manager.
120    ///
121    /// This function is different from [`Queue::submit()`] only because it accepts `VkSubmitInfo2`[vk::SubmitInfo2] structures.
122    /// This is a more modern version of the old API. The old API will be deprecated and removed eventually.
123    pub fn submit2(&self, submits: &[vk::SubmitInfo2], fence: Option<&Fence>) -> Result<()> {
124        let fence = match fence {
125            None => vk::Fence::null(),
126            // SAFETY: The user supplied a valid fence
127            Some(fence) => unsafe { fence.handle() },
128        };
129        let queue = self.acquire_device_queue()?;
130        // * `fence` is null or a valid fence handle (see above).
131        // * The user supplied a valid range of `VkSubmitInfo2` structures.
132        // * `queue` is a valid queue object.
133        unsafe { Ok(self.device.queue_submit2(queue.handle, submits, fence)?) }
134    }
135
136    /// Obtain the raw vulkan handle of a queue.
137    /// # Safety
138    /// Any vulkan calls that mutate the `VkQueue` object may lead to race conditions or undefined behaviour.
139    pub unsafe fn handle(&self) -> vk::Queue {
140        let queue = self.acquire_device_queue().unwrap();
141        queue.handle
142    }
143
144    pub(crate) fn allocate_command_buffer<'q, A: Allocator, CmdBuf: IncompleteCmdBuffer<'q, A>>(
145        device: Device,
146        queue_lock: MutexGuard<'q, Queue>,
147        pipelines: PipelineCache<A>,
148        descriptors: DescriptorCache,
149    ) -> Result<CmdBuf> {
150        let info = vk::CommandBufferAllocateInfo {
151            s_type: vk::StructureType::COMMAND_BUFFER_ALLOCATE_INFO,
152            p_next: std::ptr::null(),
153            command_pool: unsafe { queue_lock.pool.handle() },
154            level: vk::CommandBufferLevel::PRIMARY,
155            command_buffer_count: 1,
156        };
157        let handle = unsafe { device.allocate_command_buffers(&info)? }
158            .into_iter()
159            .next()
160            .ok_or_else(|| Error::Uncategorized("Command buffer allocation failed."))?;
161
162        CmdBuf::new(
163            device,
164            queue_lock,
165            handle,
166            vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT,
167            pipelines,
168            descriptors,
169        )
170    }
171
172    /// Instantly delete a command buffer, without taking synchronization into account.
173    /// This function **must** be externally synchronized.
174    pub(crate) unsafe fn free_command_buffer<CmdBuf: CmdBuffer<A>, A: Allocator>(
175        &self,
176        cmd: vk::CommandBuffer,
177    ) -> Result<()> {
178        self.device
179            .free_command_buffers(self.pool.handle(), std::slice::from_ref(&cmd));
180        Ok(())
181    }
182
183    /// Get the properties of this queue, such as whether it is dedicated or not.
184    pub fn info(&self) -> &QueueInfo {
185        &self.info
186    }
187
188    /// Get the properties of this queue's family.
189    pub fn family_properties(&self) -> &vk::QueueFamilyProperties {
190        &self.family_properties
191    }
192}