diff options
Diffstat (limited to 'src/command_buffer/pool.rs')
-rw-r--r-- | src/command_buffer/pool.rs | 645 |
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); + } +} |