aboutsummaryrefslogtreecommitdiff
path: root/src/command_buffer/pool.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/command_buffer/pool.rs')
-rw-r--r--src/command_buffer/pool.rs645
1 files changed, 645 insertions, 0 deletions
diff --git a/src/command_buffer/pool.rs b/src/command_buffer/pool.rs
new file mode 100644
index 0000000..b93e645
--- /dev/null
+++ b/src/command_buffer/pool.rs
@@ -0,0 +1,645 @@
+// Copyright (c) 2016 The vulkano developers
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
+// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
+// at your option. All files in the project carrying such
+// notice may not be copied, modified, or distributed except
+// according to those terms.
+
+//! Memory and resource pool for recording command buffers.
+//!
+//! A command pool holds and manages the memory of one or more command buffers. If you destroy a
+//! command pool, all command buffers recorded from it become invalid. This could lead to invalid
+//! usage and unsoundness, so to ensure safety you must use a [command buffer allocator].
+//!
+//! [command buffer allocator]: crate::command_buffer::allocator
+
+use crate::{
+ command_buffer::CommandBufferLevel,
+ device::{Device, DeviceOwned},
+ macros::impl_id_counter,
+ OomError, RequiresOneOf, Version, VulkanError, VulkanObject,
+};
+use smallvec::SmallVec;
+use std::{
+ cell::Cell,
+ error::Error,
+ fmt::{Display, Error as FmtError, Formatter},
+ marker::PhantomData,
+ mem::MaybeUninit,
+ num::NonZeroU64,
+ ptr,
+ sync::Arc,
+};
+
+/// Represents a Vulkan command pool.
+///
+/// A command pool is always tied to a specific queue family. Command buffers allocated from a pool
+/// can only be executed on the corresponding queue family.
+///
+/// This struct doesn't implement the `Sync` trait because Vulkan command pools are not thread
+/// safe. In other words, you can only use a pool from one thread at a time.
+#[derive(Debug)]
+pub struct CommandPool {
+ handle: ash::vk::CommandPool,
+ device: Arc<Device>,
+ id: NonZeroU64,
+
+ queue_family_index: u32,
+ _transient: bool,
+ _reset_command_buffer: bool,
+ // Unimplement `Sync`, as Vulkan command pools are not thread-safe.
+ _marker: PhantomData<Cell<ash::vk::CommandPool>>,
+}
+
+impl CommandPool {
+ /// Creates a new `CommandPool`.
+ pub fn new(
+ device: Arc<Device>,
+ mut create_info: CommandPoolCreateInfo,
+ ) -> Result<CommandPool, CommandPoolCreationError> {
+ Self::validate(&device, &mut create_info)?;
+ let handle = unsafe { Self::create(&device, &create_info)? };
+
+ let CommandPoolCreateInfo {
+ queue_family_index,
+ transient,
+ reset_command_buffer,
+ _ne: _,
+ } = create_info;
+
+ Ok(CommandPool {
+ handle,
+ device,
+ id: Self::next_id(),
+ queue_family_index,
+ _transient: transient,
+ _reset_command_buffer: reset_command_buffer,
+ _marker: PhantomData,
+ })
+ }
+
+ /// Creates a new `UnsafeCommandPool` from a raw object handle.
+ ///
+ /// # Safety
+ ///
+ /// - `handle` must be a valid Vulkan object handle created from `device`.
+ /// - `create_info` must match the info used to create the object.
+ #[inline]
+ pub unsafe fn from_handle(
+ device: Arc<Device>,
+ handle: ash::vk::CommandPool,
+ create_info: CommandPoolCreateInfo,
+ ) -> CommandPool {
+ let CommandPoolCreateInfo {
+ queue_family_index,
+ transient,
+ reset_command_buffer,
+ _ne: _,
+ } = create_info;
+
+ CommandPool {
+ handle,
+ device,
+ id: Self::next_id(),
+ queue_family_index,
+ _transient: transient,
+ _reset_command_buffer: reset_command_buffer,
+ _marker: PhantomData,
+ }
+ }
+
+ fn validate(
+ device: &Device,
+ create_info: &mut CommandPoolCreateInfo,
+ ) -> Result<(), CommandPoolCreationError> {
+ let &mut CommandPoolCreateInfo {
+ queue_family_index,
+ transient: _,
+ reset_command_buffer: _,
+ _ne: _,
+ } = create_info;
+
+ // VUID-vkCreateCommandPool-queueFamilyIndex-01937
+ if queue_family_index >= device.physical_device().queue_family_properties().len() as u32 {
+ return Err(CommandPoolCreationError::QueueFamilyIndexOutOfRange {
+ queue_family_index,
+ queue_family_count: device.physical_device().queue_family_properties().len() as u32,
+ });
+ }
+
+ Ok(())
+ }
+
+ unsafe fn create(
+ device: &Device,
+ create_info: &CommandPoolCreateInfo,
+ ) -> Result<ash::vk::CommandPool, CommandPoolCreationError> {
+ let &CommandPoolCreateInfo {
+ queue_family_index,
+ transient,
+ reset_command_buffer,
+ _ne: _,
+ } = create_info;
+
+ let mut flags = ash::vk::CommandPoolCreateFlags::empty();
+
+ if transient {
+ flags |= ash::vk::CommandPoolCreateFlags::TRANSIENT;
+ }
+
+ if reset_command_buffer {
+ flags |= ash::vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER;
+ }
+
+ let create_info = ash::vk::CommandPoolCreateInfo {
+ flags,
+ queue_family_index,
+ ..Default::default()
+ };
+
+ let handle = {
+ let fns = device.fns();
+ let mut output = MaybeUninit::uninit();
+ (fns.v1_0.create_command_pool)(
+ device.handle(),
+ &create_info,
+ ptr::null(),
+ output.as_mut_ptr(),
+ )
+ .result()
+ .map_err(VulkanError::from)?;
+ output.assume_init()
+ };
+
+ Ok(handle)
+ }
+
+ /// Resets the pool, which resets all the command buffers that were allocated from it.
+ ///
+ /// If `release_resources` is true, it is a hint to the implementation that it should free all
+ /// the memory internally allocated for this pool.
+ ///
+ /// # Safety
+ ///
+ /// - The command buffers allocated from this pool jump to the initial state.
+ #[inline]
+ pub unsafe fn reset(&self, release_resources: bool) -> Result<(), OomError> {
+ let flags = if release_resources {
+ ash::vk::CommandPoolResetFlags::RELEASE_RESOURCES
+ } else {
+ ash::vk::CommandPoolResetFlags::empty()
+ };
+
+ let fns = self.device.fns();
+ (fns.v1_0.reset_command_pool)(self.device.handle(), self.handle, flags)
+ .result()
+ .map_err(VulkanError::from)?;
+
+ Ok(())
+ }
+
+ /// Allocates command buffers.
+ #[inline]
+ pub fn allocate_command_buffers(
+ &self,
+ allocate_info: CommandBufferAllocateInfo,
+ ) -> Result<impl ExactSizeIterator<Item = CommandPoolAlloc>, OomError> {
+ let CommandBufferAllocateInfo {
+ level,
+ command_buffer_count,
+ _ne: _,
+ } = allocate_info;
+
+ // VUID-vkAllocateCommandBuffers-pAllocateInfo::commandBufferCount-arraylength
+ let out = if command_buffer_count == 0 {
+ vec![]
+ } else {
+ let allocate_info = ash::vk::CommandBufferAllocateInfo {
+ command_pool: self.handle,
+ level: level.into(),
+ command_buffer_count,
+ ..Default::default()
+ };
+
+ unsafe {
+ let fns = self.device.fns();
+ let mut out = Vec::with_capacity(command_buffer_count as usize);
+ (fns.v1_0.allocate_command_buffers)(
+ self.device.handle(),
+ &allocate_info,
+ out.as_mut_ptr(),
+ )
+ .result()
+ .map_err(VulkanError::from)?;
+ out.set_len(command_buffer_count as usize);
+ out
+ }
+ };
+
+ let device = self.device.clone();
+
+ Ok(out.into_iter().map(move |command_buffer| CommandPoolAlloc {
+ handle: command_buffer,
+ device: device.clone(),
+ id: CommandPoolAlloc::next_id(),
+ level,
+ }))
+ }
+
+ /// Frees individual command buffers.
+ ///
+ /// # Safety
+ ///
+ /// - The `command_buffers` must have been allocated from this pool.
+ /// - The `command_buffers` must not be in the pending state.
+ pub unsafe fn free_command_buffers(
+ &self,
+ command_buffers: impl IntoIterator<Item = CommandPoolAlloc>,
+ ) {
+ let command_buffers: SmallVec<[_; 4]> =
+ command_buffers.into_iter().map(|cb| cb.handle).collect();
+ let fns = self.device.fns();
+ (fns.v1_0.free_command_buffers)(
+ self.device.handle(),
+ self.handle,
+ command_buffers.len() as u32,
+ command_buffers.as_ptr(),
+ )
+ }
+
+ /// Trims a command pool, which recycles unused internal memory from the command pool back to
+ /// the system.
+ ///
+ /// Command buffers allocated from the pool are not affected by trimming.
+ ///
+ /// This function is supported only if the
+ /// [`khr_maintenance1`](crate::device::DeviceExtensions::khr_maintenance1) extension is
+ /// enabled on the device. Otherwise an error is returned.
+ /// Since this operation is purely an optimization it is legitimate to call this function and
+ /// simply ignore any possible error.
+ #[inline]
+ pub fn trim(&self) -> Result<(), CommandPoolTrimError> {
+ if !(self.device.api_version() >= Version::V1_1
+ || self.device.enabled_extensions().khr_maintenance1)
+ {
+ return Err(CommandPoolTrimError::RequirementNotMet {
+ required_for: "`CommandPool::trim`",
+ requires_one_of: RequiresOneOf {
+ api_version: Some(Version::V1_1),
+ device_extensions: &["khr_maintenance1"],
+ ..Default::default()
+ },
+ });
+ }
+
+ unsafe {
+ let fns = self.device.fns();
+
+ if self.device.api_version() >= Version::V1_1 {
+ (fns.v1_1.trim_command_pool)(
+ self.device.handle(),
+ self.handle,
+ ash::vk::CommandPoolTrimFlags::empty(),
+ );
+ } else {
+ (fns.khr_maintenance1.trim_command_pool_khr)(
+ self.device.handle(),
+ self.handle,
+ ash::vk::CommandPoolTrimFlagsKHR::empty(),
+ );
+ }
+
+ Ok(())
+ }
+ }
+
+ /// Returns the queue family on which command buffers of this pool can be executed.
+ #[inline]
+ pub fn queue_family_index(&self) -> u32 {
+ self.queue_family_index
+ }
+}
+
+impl Drop for CommandPool {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe {
+ let fns = self.device.fns();
+ (fns.v1_0.destroy_command_pool)(self.device.handle(), self.handle, ptr::null());
+ }
+ }
+}
+
+unsafe impl VulkanObject for CommandPool {
+ type Handle = ash::vk::CommandPool;
+
+ #[inline]
+ fn handle(&self) -> Self::Handle {
+ self.handle
+ }
+}
+
+unsafe impl DeviceOwned for CommandPool {
+ #[inline]
+ fn device(&self) -> &Arc<Device> {
+ &self.device
+ }
+}
+
+impl_id_counter!(CommandPool);
+
+/// Error that can happen when creating a `CommandPool`.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum CommandPoolCreationError {
+ /// Not enough memory.
+ OomError(OomError),
+
+ /// The provided `queue_family_index` was not less than the number of queue families in the
+ /// physical device.
+ QueueFamilyIndexOutOfRange {
+ queue_family_index: u32,
+ queue_family_count: u32,
+ },
+}
+
+impl Error for CommandPoolCreationError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ Self::OomError(err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+impl Display for CommandPoolCreationError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
+ match self {
+ Self::OomError(_) => write!(f, "not enough memory",),
+ Self::QueueFamilyIndexOutOfRange {
+ queue_family_index,
+ queue_family_count,
+ } => write!(
+ f,
+ "the provided `queue_family_index` ({}) was not less than the number of queue \
+ families in the physical device ({})",
+ queue_family_index, queue_family_count,
+ ),
+ }
+ }
+}
+
+impl From<VulkanError> for CommandPoolCreationError {
+ fn from(err: VulkanError) -> Self {
+ match err {
+ err @ VulkanError::OutOfHostMemory => Self::OomError(OomError::from(err)),
+ _ => panic!("unexpected error: {:?}", err),
+ }
+ }
+}
+
+/// Parameters to create an `CommandPool`.
+#[derive(Clone, Debug)]
+pub struct CommandPoolCreateInfo {
+ /// The index of the queue family that this pool is created for. All command buffers allocated
+ /// from this pool must be submitted on a queue belonging to that family.
+ ///
+ /// The default value is `u32::MAX`, which must be overridden.
+ pub queue_family_index: u32,
+
+ /// A hint to the implementation that the command buffers allocated from this pool will be
+ /// short-lived.
+ ///
+ /// The default value is `false`.
+ pub transient: bool,
+
+ /// Whether the command buffers allocated from this pool can be reset individually.
+ ///
+ /// The default value is `false`.
+ pub reset_command_buffer: bool,
+
+ pub _ne: crate::NonExhaustive,
+}
+
+impl Default for CommandPoolCreateInfo {
+ #[inline]
+ fn default() -> Self {
+ Self {
+ queue_family_index: u32::MAX,
+ transient: false,
+ reset_command_buffer: false,
+ _ne: crate::NonExhaustive(()),
+ }
+ }
+}
+
+/// Parameters to allocate an `UnsafeCommandPoolAlloc`.
+#[derive(Clone, Debug)]
+pub struct CommandBufferAllocateInfo {
+ /// The level of command buffer to allocate.
+ ///
+ /// The default value is [`CommandBufferLevel::Primary`].
+ pub level: CommandBufferLevel,
+
+ /// The number of command buffers to allocate.
+ ///
+ /// The default value is `1`.
+ pub command_buffer_count: u32,
+
+ pub _ne: crate::NonExhaustive,
+}
+
+impl Default for CommandBufferAllocateInfo {
+ #[inline]
+ fn default() -> Self {
+ Self {
+ level: CommandBufferLevel::Primary,
+ command_buffer_count: 1,
+ _ne: crate::NonExhaustive(()),
+ }
+ }
+}
+
+/// Opaque type that represents a command buffer allocated from a pool.
+#[derive(Debug)]
+pub struct CommandPoolAlloc {
+ handle: ash::vk::CommandBuffer,
+ device: Arc<Device>,
+ id: NonZeroU64,
+ level: CommandBufferLevel,
+}
+
+impl CommandPoolAlloc {
+ /// Returns the level of the command buffer.
+ #[inline]
+ pub fn level(&self) -> CommandBufferLevel {
+ self.level
+ }
+}
+
+unsafe impl VulkanObject for CommandPoolAlloc {
+ type Handle = ash::vk::CommandBuffer;
+
+ #[inline]
+ fn handle(&self) -> Self::Handle {
+ self.handle
+ }
+}
+
+unsafe impl DeviceOwned for CommandPoolAlloc {
+ #[inline]
+ fn device(&self) -> &Arc<Device> {
+ &self.device
+ }
+}
+
+impl_id_counter!(CommandPoolAlloc);
+
+/// Error that can happen when trimming command pools.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum CommandPoolTrimError {
+ RequirementNotMet {
+ required_for: &'static str,
+ requires_one_of: RequiresOneOf,
+ },
+}
+
+impl Error for CommandPoolTrimError {}
+
+impl Display for CommandPoolTrimError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
+ match self {
+ Self::RequirementNotMet {
+ required_for,
+ requires_one_of,
+ } => write!(
+ f,
+ "a requirement was not met for: {}; requires one of: {}",
+ required_for, requires_one_of,
+ ),
+ }
+ }
+}
+
+impl From<VulkanError> for CommandPoolTrimError {
+ fn from(err: VulkanError) -> CommandPoolTrimError {
+ panic!("unexpected error: {:?}", err)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ CommandPool, CommandPoolCreateInfo, CommandPoolCreationError, CommandPoolTrimError,
+ };
+ use crate::{
+ command_buffer::{pool::CommandBufferAllocateInfo, CommandBufferLevel},
+ RequiresOneOf, Version,
+ };
+
+ #[test]
+ fn basic_create() {
+ let (device, queue) = gfx_dev_and_queue!();
+ let _ = CommandPool::new(
+ device,
+ CommandPoolCreateInfo {
+ queue_family_index: queue.queue_family_index(),
+ ..Default::default()
+ },
+ )
+ .unwrap();
+ }
+
+ #[test]
+ fn queue_family_getter() {
+ let (device, queue) = gfx_dev_and_queue!();
+ let pool = CommandPool::new(
+ device,
+ CommandPoolCreateInfo {
+ queue_family_index: queue.queue_family_index(),
+ ..Default::default()
+ },
+ )
+ .unwrap();
+ assert_eq!(pool.queue_family_index(), queue.queue_family_index());
+ }
+
+ #[test]
+ fn check_queue_family_too_high() {
+ let (device, _) = gfx_dev_and_queue!();
+
+ match CommandPool::new(
+ device,
+ CommandPoolCreateInfo {
+ ..Default::default()
+ },
+ ) {
+ Err(CommandPoolCreationError::QueueFamilyIndexOutOfRange { .. }) => (),
+ _ => panic!(),
+ }
+ }
+
+ #[test]
+ fn check_maintenance_when_trim() {
+ let (device, queue) = gfx_dev_and_queue!();
+ let pool = CommandPool::new(
+ device.clone(),
+ CommandPoolCreateInfo {
+ queue_family_index: queue.queue_family_index(),
+ ..Default::default()
+ },
+ )
+ .unwrap();
+
+ if device.api_version() >= Version::V1_1 {
+ if matches!(
+ pool.trim(),
+ Err(CommandPoolTrimError::RequirementNotMet {
+ requires_one_of: RequiresOneOf {
+ device_extensions,
+ ..
+ }, ..
+ }) if device_extensions.contains(&"khr_maintenance1")
+ ) {
+ panic!()
+ }
+ } else {
+ if !matches!(
+ pool.trim(),
+ Err(CommandPoolTrimError::RequirementNotMet {
+ requires_one_of: RequiresOneOf {
+ device_extensions,
+ ..
+ }, ..
+ }) if device_extensions.contains(&"khr_maintenance1")
+ ) {
+ panic!()
+ }
+ }
+ }
+
+ // TODO: test that trim works if VK_KHR_maintenance1 if enabled ; the test macro doesn't
+ // support enabling extensions yet
+
+ #[test]
+ fn basic_alloc() {
+ let (device, queue) = gfx_dev_and_queue!();
+ let pool = CommandPool::new(
+ device,
+ CommandPoolCreateInfo {
+ queue_family_index: queue.queue_family_index(),
+ ..Default::default()
+ },
+ )
+ .unwrap();
+ let iter = pool
+ .allocate_command_buffers(CommandBufferAllocateInfo {
+ level: CommandBufferLevel::Primary,
+ command_buffer_count: 12,
+ ..Default::default()
+ })
+ .unwrap();
+ assert_eq!(iter.count(), 12);
+ }
+}