Skip to main content

vk_graph/driver/
cmd_buf.rs

1//! Command buffer types
2
3use {
4    super::{DriverError, device::Device},
5    ash::vk,
6    derive_builder::{Builder, UninitializedFieldError},
7    log::{error, trace, warn},
8    std::{fmt::Debug, slice, thread::panicking},
9};
10
11// TODO: Expose command functions so the fence, device, waiting flags do not
12// need to be public
13
14/// Represents a Vulkan command buffer to which some work has been submitted.
15#[derive(Debug)]
16#[read_only::cast]
17pub struct CommandBuffer {
18    /// The device which owns this command buffer resource.
19    ///
20    /// _Note:_ This field is read-only.
21    #[readonly]
22    pub device: Device,
23
24    droppables: Vec<Box<dyn Debug + Send + 'static>>,
25
26    /// The native Vulkan fence handle of this command buffer.
27    ///
28    /// _Note:_ This field is read-only.
29    #[readonly]
30    pub fence: vk::Fence,
31
32    /// The native Vulkan resource handle of this command buffer.
33    ///
34    /// _Note:_ This field is read-only.
35    #[readonly]
36    pub handle: vk::CommandBuffer,
37
38    /// Information used to create this object.
39    #[readonly]
40    pub info: CommandBufferInfo,
41
42    pub(crate) pool: vk::CommandPool,
43}
44
45impl CommandBuffer {
46    /// Creates a command buffer allocation backed by a transient resettable command pool.
47    #[profiling::function]
48    pub fn create(device: &Device, info: CommandBufferInfo) -> Result<Self, DriverError> {
49        let device = device.clone();
50
51        let pool = unsafe {
52            device.create_command_pool(
53                &vk::CommandPoolCreateInfo::default()
54                    .flags(
55                        vk::CommandPoolCreateFlags::TRANSIENT
56                            | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
57                    )
58                    .queue_family_index(info.queue_family_index),
59                None,
60            )
61        }
62        .map_err(|err| {
63            warn!("unable to create command pool: {err}");
64
65            match err {
66                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
67                    DriverError::OutOfMemory
68                }
69                _ => DriverError::Unsupported,
70            }
71        })?;
72
73        let handle = unsafe {
74            device.allocate_command_buffers(
75                &vk::CommandBufferAllocateInfo::default()
76                    .command_buffer_count(1)
77                    .command_pool(pool)
78                    .level(vk::CommandBufferLevel::PRIMARY),
79            )
80        }
81        .map_err(|err| {
82            warn!("unable to allocate command buffer: {err}");
83
84            match err {
85                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
86                    DriverError::OutOfMemory
87                }
88                _ => DriverError::Unsupported,
89            }
90        })?[0];
91
92        let fence = Device::create_fence(&device, false)?;
93
94        Ok(Self {
95            device,
96            droppables: vec![],
97            fence,
98            handle,
99            info,
100            pool,
101        })
102    }
103
104    /// Drops an item after execution has been completed.
105    pub fn drop_after_executed(&mut self, x: impl Debug + Send + 'static) {
106        self.droppables.push(Box::new(x));
107    }
108
109    /// Signals that execution has completed and it is time to drop anything we collected.
110    #[profiling::function]
111    fn drop_fenced(&mut self) {
112        if !self.droppables.is_empty() {
113            trace!("dropping {} shared references", self.droppables.len());
114        }
115
116        self.droppables.clear();
117    }
118
119    /// Returns `true` after the GPU has executed the previous submission to this command buffer.
120    ///
121    /// See [`Self::wait_until_executed`] to block while checking.
122    #[profiling::function]
123    pub fn has_executed(&self) -> Result<bool, DriverError> {
124        let res = unsafe { self.device.get_fence_status(self.fence) };
125
126        match res {
127            Ok(status) => Ok(status),
128            Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
129                error!("invalid device state: lost");
130
131                Err(DriverError::InvalidData)
132            }
133            Err(err) => {
134                // VK_SUCCESS and VK_NOT_READY handled by get_fence_status in ash
135                // VK_ERROR_DEVICE_LOST already handled above, so no idea what happened
136                error!("unable to get fence status: {err}");
137
138                Err(DriverError::InvalidData)
139            }
140        }
141    }
142
143    /// Stalls by blocking the current thread until the GPU has executed the previous submission to
144    /// this command buffer.
145    ///
146    /// See [`Self::has_executed`] to check without blocking.
147    #[profiling::function]
148    pub fn wait_until_executed(&mut self) -> Result<(), DriverError> {
149        if self.droppables.is_empty() {
150            return Ok(());
151        }
152
153        Device::wait_for_fence(&self.device, &self.fence)?;
154
155        self.drop_fenced();
156
157        Ok(())
158    }
159}
160
161impl Drop for CommandBuffer {
162    #[profiling::function]
163    fn drop(&mut self) {
164        if panicking() {
165            return;
166        }
167
168        if self.wait_until_executed().is_err() {
169            return;
170        }
171
172        unsafe {
173            self.device
174                .free_command_buffers(self.pool, slice::from_ref(&self.handle));
175            self.device.destroy_command_pool(self.pool, None);
176            self.device.destroy_fence(self.fence, None);
177        }
178    }
179}
180
181/// Information used to create a [`CommandBuffer`] instance.
182#[derive(Builder, Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
183#[builder(
184    build_fn(private, name = "fallible_build", error = "UninitializedFieldError"),
185    derive(Clone, Copy, Debug),
186    pattern = "owned"
187)]
188pub struct CommandBufferInfo {
189    /// Designates the queue family used by the command pool that allocates this command buffer.
190    ///
191    /// See [`VkCommandPoolCreateInfo`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkCommandPoolCreateInfo.html).
192    pub queue_family_index: u32,
193}
194
195impl CommandBufferInfo {
196    /// Creates command buffer allocation info for the given queue family.
197    pub fn new(queue_family_index: u32) -> Self {
198        Self { queue_family_index }
199    }
200}
201
202impl CommandBufferInfo {
203    /// Creates a default `CommandBufferInfoBuilder`.
204    pub fn builder() -> CommandBufferInfoBuilder {
205        Default::default()
206    }
207
208    /// Converts a `CommandBufferInfo` into a `CommandBufferInfoBuilder`.
209    pub fn into_builder(self) -> CommandBufferInfoBuilder {
210        CommandBufferInfoBuilder {
211            queue_family_index: Some(self.queue_family_index),
212        }
213    }
214
215    #[deprecated = "use into_builder function"]
216    #[doc(hidden)]
217    pub fn to_builder(self) -> CommandBufferInfoBuilder {
218        self.into_builder()
219    }
220}
221
222impl From<CommandBufferInfoBuilder> for CommandBufferInfo {
223    fn from(info: CommandBufferInfoBuilder) -> Self {
224        info.build()
225    }
226}
227
228impl CommandBufferInfoBuilder {
229    /// Builds a new `CommandBufferInfo`.
230    #[inline(always)]
231    pub fn build(self) -> CommandBufferInfo {
232        self.fallible_build().expect("invalid command buffer info")
233    }
234}