use super::{
pool::{
CommandBufferAllocateInfo, CommandPool, CommandPoolAlloc, CommandPoolCreateInfo,
CommandPoolResetFlags,
},
CommandBufferLevel,
};
use crate::{
device::{Device, DeviceOwned},
instance::InstanceOwnedDebugWrapper,
Validated, ValidationError, VulkanError,
};
use crossbeam_queue::ArrayQueue;
use smallvec::SmallVec;
use std::{
cell::UnsafeCell,
error::Error,
fmt::{Debug, Display, Error as FmtError, Formatter},
mem, ptr,
sync::{Arc, Weak},
};
use thread_local::ThreadLocal;
const MAX_POOLS: usize = 32;
pub unsafe trait CommandBufferAllocator: DeviceOwned + Send + Sync + 'static {
fn allocate(
&self,
queue_family_index: u32,
level: CommandBufferLevel,
) -> Result<CommandBufferAlloc, Validated<VulkanError>>;
unsafe fn deallocate(&self, allocation: CommandBufferAlloc);
}
impl Debug for dyn CommandBufferAllocator {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
f.debug_struct("CommandBufferAllocator")
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct CommandBufferAlloc {
pub inner: CommandPoolAlloc,
pub pool: Arc<CommandPool>,
pub handle: AllocationHandle,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(not(doc), repr(transparent))]
pub struct AllocationHandle(*mut ());
unsafe impl Send for AllocationHandle {}
unsafe impl Sync for AllocationHandle {}
impl AllocationHandle {
#[inline]
pub const fn null() -> Self {
AllocationHandle(ptr::null_mut())
}
#[inline]
pub const fn from_ptr(ptr: *mut ()) -> Self {
AllocationHandle(ptr)
}
#[allow(clippy::useless_transmute)]
#[inline]
pub const fn from_index(index: usize) -> Self {
AllocationHandle(unsafe { mem::transmute::<usize, *mut ()>(index) })
}
#[inline]
pub const fn as_ptr(self) -> *mut () {
self.0
}
#[allow(clippy::transmutes_expressible_as_ptr_casts)]
#[inline]
pub fn as_index(self) -> usize {
unsafe { mem::transmute::<*mut (), usize>(self.0) }
}
}
#[derive(Debug)]
pub struct StandardCommandBufferAllocator {
device: InstanceOwnedDebugWrapper<Arc<Device>>,
pools: ThreadLocal<SmallVec<[UnsafeCell<Option<Entry>>; 8]>>,
buffer_count: [usize; 2],
}
impl StandardCommandBufferAllocator {
#[inline]
pub fn new(device: Arc<Device>, create_info: StandardCommandBufferAllocatorCreateInfo) -> Self {
let mut buffer_count = [0, 0];
buffer_count[CommandBufferLevel::Primary as usize] = create_info.primary_buffer_count;
buffer_count[CommandBufferLevel::Secondary as usize] = create_info.secondary_buffer_count;
StandardCommandBufferAllocator {
device: InstanceOwnedDebugWrapper(device),
pools: ThreadLocal::new(),
buffer_count,
}
}
#[inline]
pub fn try_reset_pool(
&self,
queue_family_index: u32,
flags: CommandPoolResetFlags,
) -> Result<(), Validated<ResetCommandPoolError>> {
let entry_ptr = self.entry(queue_family_index);
if let Some(entry) = unsafe { &mut *entry_ptr }.as_mut() {
entry.try_reset_pool(flags)
} else {
Ok(())
}
}
#[inline]
pub fn clear(&self, queue_family_index: u32) {
let entry_ptr = self.entry(queue_family_index);
unsafe { *entry_ptr = None };
}
fn entry(&self, queue_family_index: u32) -> *mut Option<Entry> {
let pools = self.pools.get_or(|| {
self.device
.physical_device()
.queue_family_properties()
.iter()
.map(|_| UnsafeCell::new(None))
.collect()
});
pools[queue_family_index as usize].get()
}
}
unsafe impl CommandBufferAllocator for StandardCommandBufferAllocator {
#[inline]
fn allocate(
&self,
queue_family_index: u32,
level: CommandBufferLevel,
) -> Result<CommandBufferAlloc, Validated<VulkanError>> {
if !self
.device
.active_queue_family_indices()
.contains(&queue_family_index)
{
Err(Box::new(ValidationError {
context: "queue_family_index".into(),
problem: "is not active on the device".into(),
vuids: &["VUID-vkCreateCommandPool-queueFamilyIndex-01937"],
..Default::default()
}))?;
}
let entry_ptr = self.entry(queue_family_index);
let entry = unsafe { &mut *entry_ptr };
if entry.is_none() {
*entry = Some(Entry::new(
self.device.clone(),
queue_family_index,
&self.buffer_count,
Arc::new(ArrayQueue::new(MAX_POOLS)),
)?);
}
let entry = entry.as_mut().unwrap();
Ok(entry.allocate(queue_family_index, level, &self.buffer_count)?)
}
#[inline]
unsafe fn deallocate(&self, allocation: CommandBufferAlloc) {
let ptr = allocation.handle.as_ptr().cast::<Pool>();
let pool = unsafe { Arc::from_raw(ptr) };
let level = allocation.inner.level();
let buffer_reserve = pool.buffer_reserve[level as usize].as_ref().unwrap();
let res = buffer_reserve.push(allocation.inner);
debug_assert!(res.is_ok());
if Arc::strong_count(&pool) == 1 {
if let Some(reserve) = pool.pool_reserve.upgrade() {
let _ = reserve.push(pool);
}
}
}
}
unsafe impl<T: CommandBufferAllocator> CommandBufferAllocator for Arc<T> {
#[inline]
fn allocate(
&self,
queue_family_index: u32,
level: CommandBufferLevel,
) -> Result<CommandBufferAlloc, Validated<VulkanError>> {
(**self).allocate(queue_family_index, level)
}
#[inline]
unsafe fn deallocate(&self, allocation: CommandBufferAlloc) {
unsafe { (**self).deallocate(allocation) }
}
}
unsafe impl DeviceOwned for StandardCommandBufferAllocator {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
#[derive(Debug)]
struct Entry {
pool: Arc<Pool>,
allocations: [usize; 2],
pool_reserve: Arc<ArrayQueue<Arc<Pool>>>,
}
unsafe impl Send for Entry {}
impl Entry {
fn new(
device: Arc<Device>,
queue_family_index: u32,
buffer_count: &[usize; 2],
pool_reserve: Arc<ArrayQueue<Arc<Pool>>>,
) -> Result<Self, VulkanError> {
Ok(Entry {
pool: Pool::new(device, queue_family_index, buffer_count, &pool_reserve)?,
allocations: [0; 2],
pool_reserve,
})
}
fn allocate(
&mut self,
queue_family_index: u32,
level: CommandBufferLevel,
buffer_count: &[usize; 2],
) -> Result<CommandBufferAlloc, VulkanError> {
if self.allocations[level as usize] >= buffer_count[level as usize] {
if Arc::strong_count(&self.pool) == 1 {
unsafe {
self.pool
.inner
.reset_unchecked(CommandPoolResetFlags::empty())
}?;
self.allocations = [0; 2];
} else {
if let Some(pool) = self.pool_reserve.pop() {
unsafe { pool.inner.reset_unchecked(CommandPoolResetFlags::empty()) }?;
self.pool = pool;
self.allocations = [0; 2];
} else {
*self = Entry::new(
self.pool.inner.device().clone(),
queue_family_index,
buffer_count,
self.pool_reserve.clone(),
)?;
}
}
}
let buffer_reserve = self.pool.buffer_reserve[level as usize]
.as_ref()
.unwrap_or_else(|| {
panic!(
"attempted to allocate a command buffer with level `{level:?}`, but the \
command buffer pool for that level was configured to be empty",
)
});
self.allocations[level as usize] += 1;
Ok(CommandBufferAlloc {
inner: buffer_reserve.pop().unwrap(),
pool: self.pool.inner.clone(),
handle: AllocationHandle::from_ptr(Arc::into_raw(self.pool.clone()) as _),
})
}
fn try_reset_pool(
&mut self,
flags: CommandPoolResetFlags,
) -> Result<(), Validated<ResetCommandPoolError>> {
if let Some(pool) = Arc::get_mut(&mut self.pool) {
unsafe { pool.inner.reset(flags) }.map_err(|err| match err {
Validated::Error(err) => Validated::Error(ResetCommandPoolError::VulkanError(err)),
Validated::ValidationError(err) => err.into(),
})?;
self.allocations = [0; 2];
Ok(())
} else {
Err(ResetCommandPoolError::InUse.into())
}
}
}
#[derive(Debug)]
struct Pool {
inner: Arc<CommandPool>,
buffer_reserve: [Option<ArrayQueue<CommandPoolAlloc>>; 2],
pool_reserve: Weak<ArrayQueue<Arc<Self>>>,
}
impl Pool {
fn new(
device: Arc<Device>,
queue_family_index: u32,
buffer_counts: &[usize; 2],
pool_reserve: &Arc<ArrayQueue<Arc<Self>>>,
) -> Result<Arc<Self>, VulkanError> {
let inner = CommandPool::new(
device,
CommandPoolCreateInfo {
queue_family_index,
..Default::default()
},
)
.map_err(Validated::unwrap)?;
let levels = [CommandBufferLevel::Primary, CommandBufferLevel::Secondary];
let mut buffer_reserve = [None, None];
for (level, &buffer_count) in levels.into_iter().zip(buffer_counts) {
if buffer_count == 0 {
continue;
}
let pool = ArrayQueue::new(buffer_count);
for allocation in inner.allocate_command_buffers(CommandBufferAllocateInfo {
level,
command_buffer_count: buffer_count.try_into().unwrap(),
..Default::default()
})? {
let _ = pool.push(allocation);
}
buffer_reserve[level as usize] = Some(pool);
}
Ok(Arc::new(Pool {
inner: Arc::new(inner),
buffer_reserve,
pool_reserve: Arc::downgrade(pool_reserve),
}))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StandardCommandBufferAllocatorCreateInfo {
pub primary_buffer_count: usize,
pub secondary_buffer_count: usize,
pub _ne: crate::NonExhaustive,
}
impl Default for StandardCommandBufferAllocatorCreateInfo {
#[inline]
fn default() -> Self {
StandardCommandBufferAllocatorCreateInfo {
primary_buffer_count: 32,
secondary_buffer_count: 0,
_ne: crate::NonExhaustive(()),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResetCommandPoolError {
VulkanError(VulkanError),
InUse,
}
impl Error for ResetCommandPoolError {}
impl Display for ResetCommandPoolError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::VulkanError(_) => write!(f, "a runtime error occurred"),
Self::InUse => write!(f, "the command pool is still in use"),
}
}
}
impl From<VulkanError> for ResetCommandPoolError {
fn from(err: VulkanError) -> Self {
Self::VulkanError(err)
}
}
impl From<ResetCommandPoolError> for Validated<ResetCommandPoolError> {
fn from(err: ResetCommandPoolError) -> Self {
Self::Error(err)
}
}