Skip to main content

vk_graph/driver/
cmd_buf.rs

1//! Command buffer types
2
3use {
4    super::{DriverError, device::Device, fence::Fence},
5    ash::vk::{self, Handle as _},
6    derive_builder::Builder,
7    log::warn,
8    std::{
9        fmt::{Debug, Formatter},
10        slice,
11        thread::panicking,
12    },
13};
14
15// TODO: Expose command functions so the fence, device, waiting flags do not
16// need to be public
17
18/// Represents a Vulkan command buffer allocation.
19///
20/// See [`VkCommandBuffer`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkCommandBuffer.html).
21#[read_only::cast]
22pub struct CommandBuffer {
23    /// The device which owns this command buffer resource.
24    ///
25    /// _Note:_ This field is read-only.
26    #[readonly]
27    pub device: Device,
28
29    /// The native Vulkan resource handle of this command buffer.
30    ///
31    /// _Note:_ This field is read-only.
32    #[readonly]
33    pub handle: vk::CommandBuffer,
34
35    /// Information used to create this object.
36    #[readonly]
37    pub info: CommandBufferInfo,
38
39    pub(crate) pool: vk::CommandPool,
40    release_semaphore: Option<vk::Semaphore>,
41}
42
43impl CommandBuffer {
44    /// Begins recording this command buffer.
45    ///
46    /// This is a thin wrapper around [`ash::Device::begin_command_buffer`] that maps Vulkan errors
47    /// to [`DriverError`] variants.
48    ///
49    /// See [`vkBeginCommandBuffer`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkBeginCommandBuffer.html).
50    pub fn begin(&self, info: &vk::CommandBufferBeginInfo) -> Result<(), DriverError> {
51        Device::begin_command_buffer(&self.device, self.handle, info)
52    }
53
54    /// Creates a command buffer allocation backed by a transient resettable command pool.
55    ///
56    /// See [`vkAllocateCommandBuffers`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkAllocateCommandBuffers.html).
57    #[profiling::function]
58    pub fn create(
59        device: &Device,
60        info: impl Into<CommandBufferInfo>,
61    ) -> Result<Self, DriverError> {
62        let info = info.into();
63        let device = device.clone();
64
65        let pool = unsafe {
66            device.create_command_pool(
67                &vk::CommandPoolCreateInfo::default()
68                    .flags(
69                        vk::CommandPoolCreateFlags::TRANSIENT
70                            | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
71                    )
72                    .queue_family_index(info.queue_family_index),
73                None,
74            )
75        }
76        .map_err(|err| {
77            warn!("unable to create command pool: {err}");
78
79            match err {
80                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
81                    DriverError::OutOfMemory
82                }
83                _ => DriverError::Unsupported,
84            }
85        })?;
86
87        let handle = unsafe {
88            device.allocate_command_buffers(
89                &vk::CommandBufferAllocateInfo::default()
90                    .command_buffer_count(1)
91                    .command_pool(pool)
92                    .level(vk::CommandBufferLevel::PRIMARY),
93            )
94        }
95        .map_err(|err| {
96            warn!("unable to allocate command buffer: {err}");
97
98            match err {
99                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
100                    DriverError::OutOfMemory
101                }
102                _ => DriverError::Unsupported,
103            }
104        })?
105        .into_iter()
106        .find(|handle| !handle.is_null())
107        .ok_or_else(|| {
108            warn!("missing command buffer handle");
109
110            DriverError::Unsupported
111        })?;
112
113        Ok(Self {
114            device,
115            handle,
116            info,
117            pool,
118            release_semaphore: None,
119        })
120    }
121
122    /// Ends recording this command buffer.
123    ///
124    /// This is a thin wrapper around [`ash::Device::end_command_buffer`] that maps Vulkan errors
125    /// to [`DriverError`] variants.
126    ///
127    /// See [`vkEndCommandBuffer`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkEndCommandBuffer.html).
128    pub fn end(&self) -> Result<(), DriverError> {
129        Device::end_command_buffer(&self.device, self.handle)
130    }
131
132    /// Ends recording a render pass.
133    pub fn end_render_pass(&self) {
134        unsafe {
135            self.device.cmd_end_render_pass(self.handle);
136        }
137    }
138
139    /// Submits command buffers to a queue using `fence`.
140    ///
141    /// This method does not begin, end, or reset `self` or `fence`. Callers are expected to
142    /// submit only executable command buffers and to manage fence waits and resets as needed.
143    ///
144    /// Typical handling is:
145    ///
146    /// 1. Begin recording with [`Self::begin`].
147    /// 2. Record commands.
148    /// 3. End recording with [`Self::end`].
149    /// 4. Submit this command buffer with `queue_submit`.
150    /// 5. Later, check or wait for completion with [`Fence::status`] or [`Fence::wait`].
151    /// 6. Before re-submitting this same command buffer, reset the fence with [`Fence::reset`],
152    ///    then begin recording again.
153    ///
154    /// See [`vkQueueSubmit`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkQueueSubmit.html).
155    pub fn queue_submit(
156        &self,
157        queue: vk::Queue,
158        fence: &mut Fence,
159        submits: &[vk::SubmitInfo],
160    ) -> Result<(), DriverError> {
161        Device::queue_submit(&self.device, queue, submits, fence.handle)?;
162        fence.mark_queued();
163
164        Ok(())
165    }
166
167    /// Submits command buffers to a queue using `vkQueueSubmit2` (Vulkan 1.3 core or
168    /// `VK_KHR_synchronization2`).
169    ///
170    /// See [`vkQueueSubmit2`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkQueueSubmit2.html)
171    /// and [`VK_KHR_synchronization2`](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_synchronization2.html).
172    pub fn queue_submit2(
173        &self,
174        queue: vk::Queue,
175        fence: &mut Fence,
176        submits: &[vk::SubmitInfo2],
177    ) -> Result<(), DriverError> {
178        Device::queue_submit2(&self.device, queue, submits, fence.handle)?;
179        fence.mark_queued();
180
181        Ok(())
182    }
183
184    /// Returns a cached semaphore used to signal temporary queue-ownership release submissions.
185    ///
186    /// The semaphore is created lazily on first use and then reused with this command buffer for
187    /// subsequent release submissions.
188    pub(crate) fn release_semaphore(&mut self) -> Result<vk::Semaphore, DriverError> {
189        if let Some(semaphore) = self.release_semaphore {
190            return Ok(semaphore);
191        }
192
193        let semaphore = Device::create_semaphore(&self.device)?;
194
195        Device::try_set_debug_utils_object_name(&self.device, semaphore, "queue ownership release");
196
197        self.release_semaphore = Some(semaphore);
198
199        Ok(semaphore)
200    }
201
202    /// Sets the debugging name assigned to this command buffer.
203    pub fn set_debug_name(&self, name: impl AsRef<str>) {
204        Device::try_set_debug_utils_object_name(&self.device, self.handle, &name);
205        Device::try_set_private_data_object_name(
206            &self.device,
207            vk::ObjectType::COMMAND_BUFFER,
208            self.handle,
209            &name,
210        );
211    }
212
213    /// Sets the debugging name assigned to this command buffer.
214    pub fn with_debug_name(self, name: impl AsRef<str>) -> Self {
215        self.set_debug_name(name);
216
217        self
218    }
219}
220
221impl AsRef<CommandBuffer> for CommandBuffer {
222    fn as_ref(&self) -> &CommandBuffer {
223        self
224    }
225}
226
227impl Debug for CommandBuffer {
228    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
229        let mut res = f.debug_struct(stringify!(CommandBuffer));
230
231        if let Some(debug_name) = &Device::private_data_object_name(
232            &self.device,
233            vk::ObjectType::COMMAND_BUFFER,
234            self.handle,
235        ) {
236            res.field("debug_name", debug_name);
237        }
238
239        res.field("handle", &self.handle).finish_non_exhaustive()
240    }
241}
242
243impl Drop for CommandBuffer {
244    #[profiling::function]
245    fn drop(&mut self) {
246        if panicking() {
247            return;
248        }
249
250        Device::try_clear_private_data_object_name(
251            &self.device,
252            vk::ObjectType::COMMAND_BUFFER,
253            self.handle,
254        );
255
256        unsafe {
257            if let Some(semaphore) = self.release_semaphore.take() {
258                self.device.destroy_semaphore(semaphore, None);
259            }
260
261            self.device
262                .free_command_buffers(self.pool, slice::from_ref(&self.handle));
263            self.device.destroy_command_pool(self.pool, None);
264        }
265    }
266}
267
268/// Information used to create a [`CommandBuffer`] instance.
269#[derive(Builder, Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
270#[builder(
271    build_fn(private, name = "fallible_build"),
272    derive(Clone, Copy, Debug),
273    pattern = "owned"
274)]
275pub struct CommandBufferInfo {
276    /// Designates the queue family used by the command pool that allocates this command buffer.
277    ///
278    /// See [`VkCommandPoolCreateInfo`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkCommandPoolCreateInfo.html).
279    #[builder(default)]
280    pub queue_family_index: u32,
281}
282
283impl CommandBufferInfo {
284    /// Creates command buffer allocation info for the given queue family.
285    pub fn new(queue_family_index: u32) -> Self {
286        Self { queue_family_index }
287    }
288}
289
290impl CommandBufferInfo {
291    /// Creates a default `CommandBufferInfoBuilder`.
292    pub fn builder() -> CommandBufferInfoBuilder {
293        Default::default()
294    }
295
296    /// Converts a `CommandBufferInfo` into a `CommandBufferInfoBuilder`.
297    pub fn into_builder(self) -> CommandBufferInfoBuilder {
298        CommandBufferInfoBuilder {
299            queue_family_index: Some(self.queue_family_index),
300        }
301    }
302}
303
304impl From<CommandBufferInfoBuilder> for CommandBufferInfo {
305    fn from(info: CommandBufferInfoBuilder) -> Self {
306        info.build()
307    }
308}
309
310impl CommandBufferInfoBuilder {
311    /// Builds a new `CommandBufferInfo`.
312    #[inline(always)]
313    pub fn build(self) -> CommandBufferInfo {
314        self.fallible_build().expect("invalid command buffer info")
315    }
316}
317
318#[cfg(test)]
319mod test {
320    use super::*;
321
322    type Info = CommandBufferInfo;
323    type Builder = CommandBufferInfoBuilder;
324
325    #[test]
326    pub fn command_buffer_info() {
327        let info = Info::new(3);
328        let builder = info.into_builder().build();
329
330        assert_eq!(info, builder);
331    }
332
333    #[test]
334    pub fn command_buffer_info_builder_default_queue_family_index() {
335        assert_eq!(Builder::default().build(), Info::new(0));
336    }
337}