use self::sorted_map::SortedMap;
use super::{
layout::DescriptorSetLayout,
pool::{
DescriptorPool, DescriptorPoolAlloc, DescriptorPoolCreateFlags, DescriptorPoolCreateInfo,
DescriptorSetAllocateInfo,
},
};
use crate::{
descriptor_set::layout::DescriptorType,
device::{Device, DeviceOwned},
instance::InstanceOwnedDebugWrapper,
Validated, VulkanError,
};
use crossbeam_queue::ArrayQueue;
use std::{
cell::UnsafeCell,
fmt::{Debug, Error as FmtError, Formatter},
mem,
num::NonZeroU64,
ptr,
sync::Arc,
};
use thread_local::ThreadLocal;
const MAX_POOLS: usize = 32;
pub unsafe trait DescriptorSetAllocator: DeviceOwned + Send + Sync + 'static {
fn allocate(
&self,
layout: &Arc<DescriptorSetLayout>,
variable_descriptor_count: u32,
) -> Result<DescriptorSetAlloc, Validated<VulkanError>>;
unsafe fn deallocate(&self, allocation: DescriptorSetAlloc);
}
impl Debug for dyn DescriptorSetAllocator {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
f.debug_struct("DescriptorSetAllocator")
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct DescriptorSetAlloc {
pub inner: DescriptorPoolAlloc,
pub pool: Arc<DescriptorPool>,
pub handle: AllocationHandle,
}
unsafe impl Send for DescriptorSetAlloc {}
unsafe impl Sync for DescriptorSetAlloc {}
#[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 StandardDescriptorSetAllocator {
device: InstanceOwnedDebugWrapper<Arc<Device>>,
pools: ThreadLocal<UnsafeCell<SortedMap<NonZeroU64, Entry>>>,
create_info: StandardDescriptorSetAllocatorCreateInfo,
}
#[derive(Debug)]
enum Entry {
Fixed(FixedEntry),
Variable(VariableEntry),
}
unsafe impl Send for Entry {}
impl StandardDescriptorSetAllocator {
#[inline]
pub fn new(
device: Arc<Device>,
create_info: StandardDescriptorSetAllocatorCreateInfo,
) -> StandardDescriptorSetAllocator {
StandardDescriptorSetAllocator {
device: InstanceOwnedDebugWrapper(device),
pools: ThreadLocal::new(),
create_info,
}
}
#[inline]
pub fn clear(&self, layout: &Arc<DescriptorSetLayout>) {
let entry_ptr = self.pools.get_or(Default::default).get();
unsafe { &mut *entry_ptr }.remove(layout.id())
}
#[inline]
pub fn clear_all(&self) {
let entry_ptr = self.pools.get_or(Default::default).get();
unsafe { *entry_ptr = SortedMap::default() };
}
}
unsafe impl DescriptorSetAllocator for StandardDescriptorSetAllocator {
#[inline]
fn allocate(
&self,
layout: &Arc<DescriptorSetLayout>,
variable_descriptor_count: u32,
) -> Result<DescriptorSetAlloc, Validated<VulkanError>> {
let is_fixed = layout.variable_descriptor_count() == 0;
let pools = self.pools.get_or_default();
let entry_ptr = pools.get();
let entry = unsafe { &mut *entry_ptr }.get_or_try_insert(layout.id(), || {
if is_fixed {
FixedEntry::new(layout, &self.create_info).map(Entry::Fixed)
} else {
VariableEntry::new(
layout,
&self.create_info,
Arc::new(ArrayQueue::new(MAX_POOLS)),
)
.map(Entry::Variable)
}
})?;
match entry {
Entry::Fixed(entry) => entry.allocate(layout, &self.create_info),
Entry::Variable(entry) => {
entry.allocate(layout, variable_descriptor_count, &self.create_info)
}
}
}
#[inline]
unsafe fn deallocate(&self, allocation: DescriptorSetAlloc) {
let is_fixed = allocation.inner.variable_descriptor_count() == 0;
let ptr = allocation.handle.as_ptr();
if is_fixed {
let reserve = unsafe { Arc::from_raw(ptr.cast::<ArrayQueue<DescriptorPoolAlloc>>()) };
debug_assert!(reserve.push(allocation.inner).is_ok());
} else {
let reserve = unsafe { Arc::from_raw(ptr.cast::<ArrayQueue<Arc<DescriptorPool>>>()) };
let pool = allocation.pool;
if Arc::strong_count(&pool) == 1 {
let _ = reserve.push(pool);
}
}
}
}
unsafe impl<T: DescriptorSetAllocator> DescriptorSetAllocator for Arc<T> {
#[inline]
fn allocate(
&self,
layout: &Arc<DescriptorSetLayout>,
variable_descriptor_count: u32,
) -> Result<DescriptorSetAlloc, Validated<VulkanError>> {
(**self).allocate(layout, variable_descriptor_count)
}
#[inline]
unsafe fn deallocate(&self, allocation: DescriptorSetAlloc) {
unsafe { (**self).deallocate(allocation) }
}
}
unsafe impl DeviceOwned for StandardDescriptorSetAllocator {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
#[derive(Debug)]
struct FixedEntry {
pool: Arc<DescriptorPool>,
reserve: Arc<ArrayQueue<DescriptorPoolAlloc>>,
}
impl FixedEntry {
fn new(
layout: &Arc<DescriptorSetLayout>,
create_info: &StandardDescriptorSetAllocatorCreateInfo,
) -> Result<Self, Validated<VulkanError>> {
let pool = DescriptorPool::new(
layout.device().clone(),
DescriptorPoolCreateInfo {
flags: create_info
.update_after_bind
.then_some(DescriptorPoolCreateFlags::UPDATE_AFTER_BIND)
.unwrap_or_default(),
max_sets: create_info.set_count as u32,
pool_sizes: layout
.descriptor_counts()
.iter()
.map(|(&ty, &count)| {
assert_ne!(ty, DescriptorType::InlineUniformBlock);
(ty, count * create_info.set_count as u32)
})
.collect(),
..Default::default()
},
)
.map_err(Validated::unwrap)?;
let allocate_infos =
(0..create_info.set_count).map(|_| DescriptorSetAllocateInfo::new(layout.clone()));
let allocs =
unsafe { pool.allocate_descriptor_sets(allocate_infos) }.map_err(|err| match err {
Validated::ValidationError(_) => err,
Validated::Error(vk_err) => match vk_err {
VulkanError::OutOfHostMemory | VulkanError::OutOfDeviceMemory => err,
VulkanError::FragmentedPool => unreachable!(),
VulkanError::OutOfPoolMemory => unreachable!(),
_ => unreachable!(),
},
})?;
let reserve = ArrayQueue::new(create_info.set_count);
for alloc in allocs {
let _ = reserve.push(alloc);
}
Ok(FixedEntry {
pool: Arc::new(pool),
reserve: Arc::new(reserve),
})
}
fn allocate(
&mut self,
layout: &Arc<DescriptorSetLayout>,
create_info: &StandardDescriptorSetAllocatorCreateInfo,
) -> Result<DescriptorSetAlloc, Validated<VulkanError>> {
let inner = if let Some(inner) = self.reserve.pop() {
inner
} else {
*self = FixedEntry::new(layout, create_info)?;
self.reserve.pop().unwrap()
};
Ok(DescriptorSetAlloc {
inner,
pool: self.pool.clone(),
handle: AllocationHandle::from_ptr(Arc::into_raw(self.reserve.clone()) as _),
})
}
}
#[derive(Debug)]
struct VariableEntry {
pool: Arc<DescriptorPool>,
reserve: Arc<ArrayQueue<Arc<DescriptorPool>>>,
allocations: usize,
}
impl VariableEntry {
fn new(
layout: &DescriptorSetLayout,
create_info: &StandardDescriptorSetAllocatorCreateInfo,
reserve: Arc<ArrayQueue<Arc<DescriptorPool>>>,
) -> Result<Self, Validated<VulkanError>> {
let pool = DescriptorPool::new(
layout.device().clone(),
DescriptorPoolCreateInfo {
flags: create_info
.update_after_bind
.then_some(DescriptorPoolCreateFlags::UPDATE_AFTER_BIND)
.unwrap_or_default(),
max_sets: create_info.set_count as u32,
pool_sizes: layout
.descriptor_counts()
.iter()
.map(|(&ty, &count)| {
assert_ne!(ty, DescriptorType::InlineUniformBlock);
(ty, count * create_info.set_count as u32)
})
.collect(),
..Default::default()
},
)
.map_err(Validated::unwrap)?;
Ok(VariableEntry {
pool: Arc::new(pool),
reserve,
allocations: 0,
})
}
fn allocate(
&mut self,
layout: &Arc<DescriptorSetLayout>,
variable_descriptor_count: u32,
create_info: &StandardDescriptorSetAllocatorCreateInfo,
) -> Result<DescriptorSetAlloc, Validated<VulkanError>> {
if self.allocations >= create_info.set_count {
if Arc::strong_count(&self.pool) == 1 {
unsafe { self.pool.reset() }?;
self.allocations = 0;
} else {
if let Some(pool) = self.reserve.pop() {
unsafe { pool.reset() }?;
self.pool = pool;
self.allocations = 0;
} else {
*self = VariableEntry::new(layout, create_info, self.reserve.clone())?;
}
}
}
let allocate_info = DescriptorSetAllocateInfo {
variable_descriptor_count,
..DescriptorSetAllocateInfo::new(layout.clone())
};
let mut sets = unsafe { self.pool.allocate_descriptor_sets([allocate_info]) }.map_err(
|err| match err {
Validated::ValidationError(_) => err,
Validated::Error(vk_err) => match vk_err {
VulkanError::OutOfHostMemory | VulkanError::OutOfDeviceMemory => err,
VulkanError::FragmentedPool => unreachable!(),
VulkanError::OutOfPoolMemory => unreachable!(),
_ => unreachable!(),
},
},
)?;
self.allocations += 1;
Ok(DescriptorSetAlloc {
inner: sets.next().unwrap(),
pool: self.pool.clone(),
handle: AllocationHandle::from_ptr(Arc::into_raw(self.reserve.clone()) as _),
})
}
}
#[derive(Clone, Debug)]
pub struct StandardDescriptorSetAllocatorCreateInfo {
pub set_count: usize,
pub update_after_bind: bool,
pub _ne: crate::NonExhaustive,
}
impl Default for StandardDescriptorSetAllocatorCreateInfo {
#[inline]
fn default() -> Self {
StandardDescriptorSetAllocatorCreateInfo {
set_count: 32,
update_after_bind: false,
_ne: crate::NonExhaustive(()),
}
}
}
mod sorted_map {
use smallvec::SmallVec;
#[derive(Debug)]
pub(super) struct SortedMap<K, V> {
inner: SmallVec<[(K, V); 8]>,
}
impl<K, V> Default for SortedMap<K, V> {
fn default() -> Self {
Self {
inner: SmallVec::default(),
}
}
}
impl<K: Ord + Copy, V> SortedMap<K, V> {
pub fn get_or_try_insert<E>(
&mut self,
key: K,
f: impl FnOnce() -> Result<V, E>,
) -> Result<&mut V, E> {
match self.inner.binary_search_by_key(&key, |&(k, _)| k) {
Ok(index) => Ok(&mut self.inner[index].1),
Err(index) => {
self.inner.insert(index, (key, f()?));
Ok(&mut self.inner[index].1)
}
}
}
pub fn remove(&mut self, key: K) {
if let Ok(index) = self.inner.binary_search_by_key(&key, |&(k, _)| k) {
self.inner.remove(index);
}
}
}
}