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}