blob: b93e6458c666c4234cbc9f0c5ebadff4b42fb409 [file] [log] [blame]
// 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);
}
}