Skip to main content

vulkano/command_buffer/
pool.rs

1//! Memory and resource pool for recording command buffers.
2//!
3//! A command pool holds and manages the memory of one or more command buffers. If you destroy a
4//! command pool, all command buffers recorded from it become invalid. This could lead to invalid
5//! usage and unsoundness, so to ensure safety you must use a [command buffer allocator].
6//!
7//! [command buffer allocator]: crate::command_buffer::allocator
8
9use crate::{
10    command_buffer::CommandBufferLevel,
11    device::{Device, DeviceOwned},
12    instance::InstanceOwnedDebugWrapper,
13    macros::{impl_id_counter, vulkan_bitflags},
14    Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError,
15    VulkanObject,
16};
17use smallvec::SmallVec;
18use std::{cell::Cell, marker::PhantomData, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc};
19
20/// Represents a Vulkan command pool.
21///
22/// A command pool is always tied to a specific queue family. Command buffers allocated from a pool
23/// can only be executed on the corresponding queue family.
24///
25/// This struct doesn't implement the `Sync` trait because Vulkan command pools are not thread
26/// safe. In other words, you can only use a pool from one thread at a time.
27#[derive(Debug)]
28pub struct CommandPool {
29    handle: ash::vk::CommandPool,
30    device: InstanceOwnedDebugWrapper<Arc<Device>>,
31    id: NonZeroU64,
32
33    flags: CommandPoolCreateFlags,
34    queue_family_index: u32,
35
36    // Unimplement `Sync`, as Vulkan command pools are not thread-safe.
37    _marker: PhantomData<Cell<ash::vk::CommandPool>>,
38}
39
40impl CommandPool {
41    /// Creates a new `CommandPool`.
42    pub fn new(
43        device: Arc<Device>,
44        create_info: CommandPoolCreateInfo,
45    ) -> Result<CommandPool, Validated<VulkanError>> {
46        Self::validate_new(&device, &create_info)?;
47
48        Ok(unsafe { Self::new_unchecked(device, create_info) }?)
49    }
50
51    fn validate_new(
52        device: &Device,
53        create_info: &CommandPoolCreateInfo,
54    ) -> Result<(), Box<ValidationError>> {
55        create_info
56            .validate(device)
57            .map_err(|err| err.add_context("create_info"))?;
58
59        Ok(())
60    }
61
62    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
63    pub unsafe fn new_unchecked(
64        device: Arc<Device>,
65        create_info: CommandPoolCreateInfo,
66    ) -> Result<Self, VulkanError> {
67        let create_info_vk = create_info.to_vk();
68
69        let handle = {
70            let fns = device.fns();
71            let mut output = MaybeUninit::uninit();
72            unsafe {
73                (fns.v1_0.create_command_pool)(
74                    device.handle(),
75                    &create_info_vk,
76                    ptr::null(),
77                    output.as_mut_ptr(),
78                )
79            }
80            .result()
81            .map_err(VulkanError::from)?;
82            unsafe { output.assume_init() }
83        };
84
85        Ok(unsafe { Self::from_handle(device, handle, create_info) })
86    }
87
88    /// Creates a new `CommandPool` from a raw object handle.
89    ///
90    /// # Safety
91    ///
92    /// - `handle` must be a valid Vulkan object handle created from `device`.
93    /// - `create_info` must match the info used to create the object.
94    #[inline]
95    pub unsafe fn from_handle(
96        device: Arc<Device>,
97        handle: ash::vk::CommandPool,
98        create_info: CommandPoolCreateInfo,
99    ) -> CommandPool {
100        let CommandPoolCreateInfo {
101            flags,
102            queue_family_index,
103            _ne: _,
104        } = create_info;
105
106        CommandPool {
107            handle,
108            device: InstanceOwnedDebugWrapper(device),
109            id: Self::next_id(),
110
111            flags,
112            queue_family_index,
113
114            _marker: PhantomData,
115        }
116    }
117
118    /// Returns the flags that the command pool was created with.
119    #[inline]
120    pub fn flags(&self) -> CommandPoolCreateFlags {
121        self.flags
122    }
123
124    /// Returns the queue family on which command buffers of this pool can be executed.
125    #[inline]
126    pub fn queue_family_index(&self) -> u32 {
127        self.queue_family_index
128    }
129
130    /// Resets the pool, which resets all the command buffers that were allocated from it.
131    ///
132    /// # Safety
133    ///
134    /// - The command buffers allocated from this pool must not be in the pending state.
135    #[inline]
136    pub unsafe fn reset(&self, flags: CommandPoolResetFlags) -> Result<(), Validated<VulkanError>> {
137        self.validate_reset(flags)?;
138
139        Ok(unsafe { self.reset_unchecked(flags) }?)
140    }
141
142    fn validate_reset(&self, flags: CommandPoolResetFlags) -> Result<(), Box<ValidationError>> {
143        flags.validate_device(self.device()).map_err(|err| {
144            err.add_context("flags")
145                .set_vuids(&["VUID-vkResetCommandPool-flags-parameter"])
146        })?;
147
148        Ok(())
149    }
150
151    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
152    pub unsafe fn reset_unchecked(&self, flags: CommandPoolResetFlags) -> Result<(), VulkanError> {
153        let fns = self.device.fns();
154        unsafe { (fns.v1_0.reset_command_pool)(self.device.handle(), self.handle, flags.into()) }
155            .result()
156            .map_err(VulkanError::from)?;
157
158        Ok(())
159    }
160
161    /// Allocates command buffers.
162    #[inline]
163    pub fn allocate_command_buffers(
164        &self,
165        allocate_info: CommandBufferAllocateInfo,
166    ) -> Result<impl ExactSizeIterator<Item = CommandPoolAlloc>, VulkanError> {
167        let CommandBufferAllocateInfo {
168            level,
169            command_buffer_count,
170            _ne: _,
171        } = allocate_info;
172
173        // VUID-vkAllocateCommandBuffers-pAllocateInfo::commandBufferCount-arraylength
174        let out = if command_buffer_count == 0 {
175            vec![]
176        } else {
177            let allocate_info_vk = allocate_info.to_vk(self.handle);
178            let command_buffer_count = command_buffer_count as usize;
179
180            let fns = self.device.fns();
181            let mut out = Vec::with_capacity(command_buffer_count);
182
183            unsafe {
184                (fns.v1_0.allocate_command_buffers)(
185                    self.device.handle(),
186                    &allocate_info_vk,
187                    out.as_mut_ptr(),
188                )
189            }
190            .result()
191            .map_err(VulkanError::from)?;
192            unsafe { out.set_len(command_buffer_count) };
193
194            out
195        };
196
197        let device = self.device.clone();
198
199        Ok(out.into_iter().map(move |command_buffer| CommandPoolAlloc {
200            handle: command_buffer,
201            device: InstanceOwnedDebugWrapper(device.clone()),
202            id: CommandPoolAlloc::next_id(),
203            level,
204        }))
205    }
206
207    /// Frees individual command buffers.
208    ///
209    /// # Safety
210    ///
211    /// - The `command_buffers` must have been allocated from this pool.
212    /// - The `command_buffers` must not be in the pending state.
213    pub unsafe fn free_command_buffers(
214        &self,
215        command_buffers: impl IntoIterator<Item = CommandPoolAlloc>,
216    ) -> Result<(), Box<ValidationError>> {
217        let command_buffers: SmallVec<[_; 4]> = command_buffers.into_iter().collect();
218        self.validate_free_command_buffers(&command_buffers)?;
219
220        unsafe { self.free_command_buffers_unchecked(command_buffers) };
221        Ok(())
222    }
223
224    fn validate_free_command_buffers(
225        &self,
226        _command_buffers: &[CommandPoolAlloc],
227    ) -> Result<(), Box<ValidationError>> {
228        // VUID-vkFreeCommandBuffers-pCommandBuffers-00047
229        // VUID-vkFreeCommandBuffers-pCommandBuffers-parent
230        // Unsafe
231
232        Ok(())
233    }
234
235    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
236    pub unsafe fn free_command_buffers_unchecked(
237        &self,
238        command_buffers: impl IntoIterator<Item = CommandPoolAlloc>,
239    ) {
240        let command_buffers_vk: SmallVec<[_; 4]> =
241            command_buffers.into_iter().map(|cb| cb.handle).collect();
242
243        let fns = self.device.fns();
244        unsafe {
245            (fns.v1_0.free_command_buffers)(
246                self.device.handle(),
247                self.handle,
248                command_buffers_vk.len() as u32,
249                command_buffers_vk.as_ptr(),
250            )
251        }
252    }
253
254    /// Trims a command pool, which recycles unused internal memory from the command pool back to
255    /// the system.
256    ///
257    /// Command buffers allocated from the pool are not affected by trimming.
258    ///
259    /// This function is supported only if the
260    /// [`khr_maintenance1`](crate::device::DeviceExtensions::khr_maintenance1) extension is
261    /// enabled on the device. Otherwise an error is returned.
262    /// Since this operation is purely an optimization it is legitimate to call this function and
263    /// simply ignore any possible error.
264    #[inline]
265    pub fn trim(&self) -> Result<(), Box<ValidationError>> {
266        self.validate_trim()?;
267
268        unsafe { self.trim_unchecked() }
269        Ok(())
270    }
271
272    fn validate_trim(&self) -> Result<(), Box<ValidationError>> {
273        if !(self.device.api_version() >= Version::V1_1
274            || self.device.enabled_extensions().khr_maintenance1)
275        {
276            return Err(Box::new(ValidationError {
277                requires_one_of: RequiresOneOf(&[
278                    RequiresAllOf(&[Requires::APIVersion(Version::V1_1)]),
279                    RequiresAllOf(&[Requires::DeviceExtension("khr_maintenance1")]),
280                ]),
281                ..Default::default()
282            }));
283        }
284
285        Ok(())
286    }
287
288    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
289    pub unsafe fn trim_unchecked(&self) {
290        let fns = self.device.fns();
291
292        if self.device.api_version() >= Version::V1_1 {
293            unsafe {
294                (fns.v1_1.trim_command_pool)(
295                    self.device.handle(),
296                    self.handle,
297                    ash::vk::CommandPoolTrimFlags::empty(),
298                )
299            };
300        } else {
301            unsafe {
302                (fns.khr_maintenance1.trim_command_pool_khr)(
303                    self.device.handle(),
304                    self.handle,
305                    ash::vk::CommandPoolTrimFlagsKHR::empty(),
306                )
307            };
308        }
309    }
310}
311
312impl Drop for CommandPool {
313    #[inline]
314    fn drop(&mut self) {
315        let fns = self.device.fns();
316        unsafe { (fns.v1_0.destroy_command_pool)(self.device.handle(), self.handle, ptr::null()) };
317    }
318}
319
320unsafe impl VulkanObject for CommandPool {
321    type Handle = ash::vk::CommandPool;
322
323    #[inline]
324    fn handle(&self) -> Self::Handle {
325        self.handle
326    }
327}
328
329unsafe impl DeviceOwned for CommandPool {
330    #[inline]
331    fn device(&self) -> &Arc<Device> {
332        &self.device
333    }
334}
335
336impl_id_counter!(CommandPool);
337
338/// Parameters to create an `CommandPool`.
339#[derive(Clone, Debug)]
340pub struct CommandPoolCreateInfo {
341    /// Additional properties of the command pool.
342    ///
343    /// The default value is empty.
344    pub flags: CommandPoolCreateFlags,
345
346    /// The index of the queue family that this pool is created for. All command buffers allocated
347    /// from this pool must be submitted on a queue belonging to that family.
348    ///
349    /// The default value is `u32::MAX`, which must be overridden.
350    pub queue_family_index: u32,
351
352    pub _ne: crate::NonExhaustive,
353}
354
355impl Default for CommandPoolCreateInfo {
356    #[inline]
357    fn default() -> Self {
358        Self {
359            flags: CommandPoolCreateFlags::empty(),
360            queue_family_index: u32::MAX,
361            _ne: crate::NonExhaustive(()),
362        }
363    }
364}
365
366impl CommandPoolCreateInfo {
367    pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
368        let &Self {
369            flags,
370            queue_family_index,
371            _ne: _,
372        } = self;
373
374        flags.validate_device(device).map_err(|err| {
375            err.add_context("flags")
376                .set_vuids(&["VUID-VkCommandPoolCreateInfo-flags-parameter"])
377        })?;
378
379        if queue_family_index >= device.physical_device().queue_family_properties().len() as u32 {
380            return Err(Box::new(ValidationError {
381                context: "queue_family_index".into(),
382                problem: "is not less than the number of queue families in the physical device"
383                    .into(),
384                vuids: &["VUID-vkCreateCommandPool-queueFamilyIndex-01937"],
385                ..Default::default()
386            }));
387        }
388
389        Ok(())
390    }
391
392    pub(crate) fn to_vk(&self) -> ash::vk::CommandPoolCreateInfo<'static> {
393        let &Self {
394            flags,
395            queue_family_index,
396            _ne: _,
397        } = self;
398
399        ash::vk::CommandPoolCreateInfo::default()
400            .flags(flags.into())
401            .queue_family_index(queue_family_index)
402    }
403}
404
405vulkan_bitflags! {
406    #[non_exhaustive]
407
408    /// Additional properties of the command pool.
409    CommandPoolCreateFlags = CommandPoolCreateFlags(u32);
410
411    /// A hint to the implementation that the command buffers allocated from this pool will be
412    /// short-lived.
413    TRANSIENT = TRANSIENT,
414
415    /// Command buffers allocated from this pool can be reset individually.
416    RESET_COMMAND_BUFFER = RESET_COMMAND_BUFFER,
417
418    /* TODO: enable
419    // TODO: document
420    PROTECTED = PROTECTED
421    RequiresOneOf([
422        RequiresAllOf([APIVersion(V1_1)])
423    ]), */
424}
425
426vulkan_bitflags! {
427    #[non_exhaustive]
428
429    /// Additional properties of the command pool reset operation.
430    CommandPoolResetFlags = CommandPoolResetFlags(u32);
431
432    /// A hint to the implementation that it should free all the memory internally allocated
433    /// for this pool.
434    RELEASE_RESOURCES = RELEASE_RESOURCES,
435}
436
437/// Parameters to allocate a `CommandPoolAlloc`.
438#[derive(Clone, Debug)]
439pub struct CommandBufferAllocateInfo {
440    /// The level of command buffer to allocate.
441    ///
442    /// The default value is [`CommandBufferLevel::Primary`].
443    pub level: CommandBufferLevel,
444
445    /// The number of command buffers to allocate.
446    ///
447    /// The default value is `1`.
448    pub command_buffer_count: u32,
449
450    pub _ne: crate::NonExhaustive,
451}
452
453impl CommandBufferAllocateInfo {
454    pub(crate) fn to_vk(
455        &self,
456        command_pool_vk: ash::vk::CommandPool,
457    ) -> ash::vk::CommandBufferAllocateInfo<'static> {
458        let &Self {
459            level,
460            command_buffer_count,
461            _ne: _,
462        } = self;
463
464        ash::vk::CommandBufferAllocateInfo::default()
465            .command_pool(command_pool_vk)
466            .level(level.into())
467            .command_buffer_count(command_buffer_count)
468    }
469}
470
471impl Default for CommandBufferAllocateInfo {
472    #[inline]
473    fn default() -> Self {
474        Self {
475            level: CommandBufferLevel::Primary,
476            command_buffer_count: 1,
477            _ne: crate::NonExhaustive(()),
478        }
479    }
480}
481
482/// Opaque type that represents a command buffer allocated from a pool.
483#[derive(Debug)]
484pub struct CommandPoolAlloc {
485    handle: ash::vk::CommandBuffer,
486    device: InstanceOwnedDebugWrapper<Arc<Device>>,
487    id: NonZeroU64,
488    level: CommandBufferLevel,
489}
490
491impl CommandPoolAlloc {
492    /// Returns the level of the command buffer.
493    #[inline]
494    pub fn level(&self) -> CommandBufferLevel {
495        self.level
496    }
497}
498
499unsafe impl VulkanObject for CommandPoolAlloc {
500    type Handle = ash::vk::CommandBuffer;
501
502    #[inline]
503    fn handle(&self) -> Self::Handle {
504        self.handle
505    }
506}
507
508unsafe impl DeviceOwned for CommandPoolAlloc {
509    #[inline]
510    fn device(&self) -> &Arc<Device> {
511        &self.device
512    }
513}
514
515impl_id_counter!(CommandPoolAlloc);
516
517#[cfg(test)]
518mod tests {
519    use super::{CommandPool, CommandPoolCreateInfo};
520    use crate::{
521        command_buffer::{pool::CommandBufferAllocateInfo, CommandBufferLevel},
522        Validated,
523    };
524
525    #[test]
526    fn basic_create() {
527        let (device, queue) = gfx_dev_and_queue!();
528        let _ = CommandPool::new(
529            device,
530            CommandPoolCreateInfo {
531                queue_family_index: queue.queue_family_index(),
532                ..Default::default()
533            },
534        )
535        .unwrap();
536    }
537
538    #[test]
539    fn queue_family_getter() {
540        let (device, queue) = gfx_dev_and_queue!();
541        let pool = CommandPool::new(
542            device,
543            CommandPoolCreateInfo {
544                queue_family_index: queue.queue_family_index(),
545                ..Default::default()
546            },
547        )
548        .unwrap();
549        assert_eq!(pool.queue_family_index(), queue.queue_family_index());
550    }
551
552    #[test]
553    fn check_queue_family_too_high() {
554        let (device, _) = gfx_dev_and_queue!();
555
556        match CommandPool::new(
557            device,
558            CommandPoolCreateInfo {
559                ..Default::default()
560            },
561        ) {
562            Err(Validated::ValidationError(_)) => (),
563            _ => panic!(),
564        }
565    }
566
567    // TODO: test that trim works if VK_KHR_maintenance1 if enabled ; the test macro doesn't
568    //       support enabling extensions yet
569
570    #[test]
571    fn basic_alloc() {
572        let (device, queue) = gfx_dev_and_queue!();
573        let pool = CommandPool::new(
574            device,
575            CommandPoolCreateInfo {
576                queue_family_index: queue.queue_family_index(),
577                ..Default::default()
578            },
579        )
580        .unwrap();
581        let iter = pool
582            .allocate_command_buffers(CommandBufferAllocateInfo {
583                level: CommandBufferLevel::Primary,
584                command_buffer_count: 12,
585                ..Default::default()
586            })
587            .unwrap();
588        assert_eq!(iter.count(), 12);
589    }
590}