use self::sorted_map::SortedMap;
use super::{
layout::DescriptorSetLayout,
pool::{
DescriptorPool, DescriptorPoolAllocError, DescriptorPoolCreateInfo,
DescriptorSetAllocateInfo,
},
sys::UnsafeDescriptorSet,
};
use crate::{
device::{Device, DeviceOwned},
OomError,
};
use crossbeam_queue::ArrayQueue;
use std::{cell::UnsafeCell, mem::ManuallyDrop, num::NonZeroU64, sync::Arc};
use thread_local::ThreadLocal;
const MAX_POOLS: usize = 32;
const MAX_SETS: usize = 256;
pub unsafe trait DescriptorSetAllocator: DeviceOwned {
type Alloc: DescriptorSetAlloc;
fn allocate(
&self,
layout: &Arc<DescriptorSetLayout>,
variable_descriptor_count: u32,
) -> Result<Self::Alloc, OomError>;
}
pub trait DescriptorSetAlloc: Send + Sync {
fn inner(&self) -> &UnsafeDescriptorSet;
fn inner_mut(&mut self) -> &mut UnsafeDescriptorSet;
}
#[derive(Debug)]
pub struct StandardDescriptorSetAllocator {
device: Arc<Device>,
pools: ThreadLocal<UnsafeCell<SortedMap<NonZeroU64, Entry>>>,
}
#[derive(Debug)]
enum Entry {
Fixed(FixedEntry),
Variable(VariableEntry),
}
unsafe impl Send for Entry {}
impl StandardDescriptorSetAllocator {
#[inline]
pub fn new(device: Arc<Device>) -> StandardDescriptorSetAllocator {
StandardDescriptorSetAllocator {
device,
pools: ThreadLocal::new(),
}
}
#[inline]
pub fn clear(&self, layout: &Arc<DescriptorSetLayout>) {
unsafe { &mut *self.pools.get_or(Default::default).get() }.remove(layout.id())
}
#[inline]
pub fn clear_all(&self) {
unsafe { *self.pools.get_or(Default::default).get() = SortedMap::default() };
}
}
unsafe impl DescriptorSetAllocator for StandardDescriptorSetAllocator {
type Alloc = StandardDescriptorSetAlloc;
#[inline]
fn allocate(
&self,
layout: &Arc<DescriptorSetLayout>,
variable_descriptor_count: u32,
) -> Result<StandardDescriptorSetAlloc, OomError> {
assert!(
!layout.push_descriptor(),
"the provided descriptor set layout is for push descriptors, and cannot be used to \
build a descriptor set object",
);
let max_count = layout.variable_descriptor_count();
assert!(
variable_descriptor_count <= max_count,
"the provided variable_descriptor_count ({}) is greater than the maximum number of \
variable count descriptors in the set ({})",
variable_descriptor_count,
max_count,
);
let pools = self.pools.get_or(Default::default);
let entry = unsafe { &mut *pools.get() }.get_or_try_insert(layout.id(), || {
if max_count == 0 {
FixedEntry::new(layout.clone()).map(Entry::Fixed)
} else {
VariableEntry::new(layout.clone()).map(Entry::Variable)
}
})?;
match entry {
Entry::Fixed(entry) => entry.allocate(),
Entry::Variable(entry) => entry.allocate(variable_descriptor_count),
}
}
}
unsafe impl DescriptorSetAllocator for Arc<StandardDescriptorSetAllocator> {
type Alloc = StandardDescriptorSetAlloc;
#[inline]
fn allocate(
&self,
layout: &Arc<DescriptorSetLayout>,
variable_descriptor_count: u32,
) -> Result<Self::Alloc, OomError> {
(**self).allocate(layout, variable_descriptor_count)
}
}
unsafe impl DeviceOwned for StandardDescriptorSetAllocator {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
#[derive(Debug)]
struct FixedEntry {
pool: Arc<FixedPool>,
set_count: usize,
layout: Arc<DescriptorSetLayout>,
}
impl FixedEntry {
fn new(layout: Arc<DescriptorSetLayout>) -> Result<Self, OomError> {
Ok(FixedEntry {
pool: FixedPool::new(&layout, MAX_SETS)?,
set_count: MAX_SETS,
layout,
})
}
fn allocate(&mut self) -> Result<StandardDescriptorSetAlloc, OomError> {
let inner = if let Some(inner) = self.pool.reserve.pop() {
inner
} else {
self.set_count *= 2;
self.pool = FixedPool::new(&self.layout, self.set_count)?;
self.pool.reserve.pop().unwrap()
};
Ok(StandardDescriptorSetAlloc {
inner: ManuallyDrop::new(inner),
parent: AllocParent::Fixed(self.pool.clone()),
})
}
}
#[derive(Debug)]
struct FixedPool {
_inner: DescriptorPool,
reserve: ArrayQueue<UnsafeDescriptorSet>,
}
impl FixedPool {
fn new(layout: &Arc<DescriptorSetLayout>, set_count: usize) -> Result<Arc<Self>, OomError> {
let inner = DescriptorPool::new(
layout.device().clone(),
DescriptorPoolCreateInfo {
max_sets: set_count as u32,
pool_sizes: layout
.descriptor_counts()
.iter()
.map(|(&ty, &count)| (ty, count * set_count as u32))
.collect(),
..Default::default()
},
)?;
let allocate_infos = (0..set_count).map(|_| DescriptorSetAllocateInfo {
layout,
variable_descriptor_count: 0,
});
let reserve = match unsafe { inner.allocate_descriptor_sets(allocate_infos) } {
Ok(allocs) => {
let reserve = ArrayQueue::new(set_count);
for alloc in allocs {
let _ = reserve.push(alloc);
}
reserve
}
Err(DescriptorPoolAllocError::OutOfHostMemory) => {
return Err(OomError::OutOfHostMemory);
}
Err(DescriptorPoolAllocError::OutOfDeviceMemory) => {
return Err(OomError::OutOfDeviceMemory);
}
Err(DescriptorPoolAllocError::FragmentedPool) => {
unreachable!();
}
Err(DescriptorPoolAllocError::OutOfPoolMemory) => {
unreachable!();
}
};
Ok(Arc::new(FixedPool {
_inner: inner,
reserve,
}))
}
}
#[derive(Debug)]
struct VariableEntry {
pool: Arc<VariablePool>,
reserve: Arc<ArrayQueue<DescriptorPool>>,
layout: Arc<DescriptorSetLayout>,
allocations: usize,
}
impl VariableEntry {
fn new(layout: Arc<DescriptorSetLayout>) -> Result<Self, OomError> {
let reserve = Arc::new(ArrayQueue::new(MAX_POOLS));
Ok(VariableEntry {
pool: VariablePool::new(&layout, reserve.clone())?,
reserve,
layout,
allocations: 0,
})
}
fn allocate(
&mut self,
variable_descriptor_count: u32,
) -> Result<StandardDescriptorSetAlloc, OomError> {
if self.allocations >= MAX_SETS {
self.pool = if let Some(inner) = self.reserve.pop() {
Arc::new(VariablePool {
inner: ManuallyDrop::new(inner),
reserve: self.reserve.clone(),
})
} else {
VariablePool::new(&self.layout, self.reserve.clone())?
};
self.allocations = 0;
}
let allocate_info = DescriptorSetAllocateInfo {
layout: &self.layout,
variable_descriptor_count,
};
let inner = match unsafe { self.pool.inner.allocate_descriptor_sets([allocate_info]) } {
Ok(mut sets) => sets.next().unwrap(),
Err(DescriptorPoolAllocError::OutOfHostMemory) => {
return Err(OomError::OutOfHostMemory);
}
Err(DescriptorPoolAllocError::OutOfDeviceMemory) => {
return Err(OomError::OutOfDeviceMemory);
}
Err(DescriptorPoolAllocError::FragmentedPool) => {
unreachable!();
}
Err(DescriptorPoolAllocError::OutOfPoolMemory) => {
unreachable!();
}
};
self.allocations += 1;
Ok(StandardDescriptorSetAlloc {
inner: ManuallyDrop::new(inner),
parent: AllocParent::Variable(self.pool.clone()),
})
}
}
#[derive(Debug)]
struct VariablePool {
inner: ManuallyDrop<DescriptorPool>,
reserve: Arc<ArrayQueue<DescriptorPool>>,
}
impl VariablePool {
fn new(
layout: &Arc<DescriptorSetLayout>,
reserve: Arc<ArrayQueue<DescriptorPool>>,
) -> Result<Arc<Self>, OomError> {
DescriptorPool::new(
layout.device().clone(),
DescriptorPoolCreateInfo {
max_sets: MAX_SETS as u32,
pool_sizes: layout
.descriptor_counts()
.iter()
.map(|(&ty, &count)| (ty, count * MAX_SETS as u32))
.collect(),
..Default::default()
},
)
.map(|inner| {
Arc::new(Self {
inner: ManuallyDrop::new(inner),
reserve,
})
})
}
}
impl Drop for VariablePool {
fn drop(&mut self) {
let inner = unsafe { ManuallyDrop::take(&mut self.inner) };
unsafe { inner.reset() }.unwrap();
let _ = self.reserve.push(inner);
}
}
#[derive(Debug)]
pub struct StandardDescriptorSetAlloc {
inner: ManuallyDrop<UnsafeDescriptorSet>,
parent: AllocParent,
}
#[derive(Debug)]
enum AllocParent {
Fixed(Arc<FixedPool>),
Variable(Arc<VariablePool>),
}
unsafe impl Send for StandardDescriptorSetAlloc {}
unsafe impl Sync for StandardDescriptorSetAlloc {}
impl DescriptorSetAlloc for StandardDescriptorSetAlloc {
#[inline]
fn inner(&self) -> &UnsafeDescriptorSet {
&self.inner
}
#[inline]
fn inner_mut(&mut self) -> &mut UnsafeDescriptorSet {
&mut self.inner
}
}
impl Drop for StandardDescriptorSetAlloc {
#[inline]
fn drop(&mut self) {
let inner = unsafe { ManuallyDrop::take(&mut self.inner) };
match &self.parent {
AllocParent::Fixed(pool) => {
let _ = pool.reserve.push(inner);
}
AllocParent::Variable(_) => {}
}
}
}
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);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
descriptor_set::layout::{
DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType,
},
shader::ShaderStages,
VulkanObject,
};
use std::thread;
#[test]
fn threads_use_different_pools() {
let (device, _) = gfx_dev_and_queue!();
let layout = DescriptorSetLayout::new(
device.clone(),
DescriptorSetLayoutCreateInfo {
bindings: [(
0,
DescriptorSetLayoutBinding {
stages: ShaderStages::all_graphics(),
..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
},
)]
.into(),
..Default::default()
},
)
.unwrap();
let allocator = StandardDescriptorSetAllocator::new(device);
let pool1 =
if let AllocParent::Fixed(pool) = &allocator.allocate(&layout, 0).unwrap().parent {
pool._inner.handle()
} else {
unreachable!()
};
thread::spawn(move || {
let pool2 =
if let AllocParent::Fixed(pool) = &allocator.allocate(&layout, 0).unwrap().parent {
pool._inner.handle()
} else {
unreachable!()
};
assert_ne!(pool1, pool2);
})
.join()
.unwrap();
}
}