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