summaryrefslogtreecommitdiffstats
path: root/third_party/rust/gpu-descriptor/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/gpu-descriptor/src')
-rw-r--r--third_party/rust/gpu-descriptor/src/allocator.rs598
-rw-r--r--third_party/rust/gpu-descriptor/src/lib.rs35
2 files changed, 633 insertions, 0 deletions
diff --git a/third_party/rust/gpu-descriptor/src/allocator.rs b/third_party/rust/gpu-descriptor/src/allocator.rs
new file mode 100644
index 0000000000..077a8860ce
--- /dev/null
+++ b/third_party/rust/gpu-descriptor/src/allocator.rs
@@ -0,0 +1,598 @@
+use {
+ alloc::{collections::VecDeque, vec::Vec},
+ core::{
+ convert::TryFrom as _,
+ fmt::{self, Debug, Display},
+ },
+ gpu_descriptor_types::{
+ CreatePoolError, DescriptorDevice, DescriptorPoolCreateFlags, DescriptorTotalCount,
+ DeviceAllocationError,
+ },
+ hashbrown::HashMap,
+};
+
+bitflags::bitflags! {
+ /// Flags to augment descriptor set allocation.
+ pub struct DescriptorSetLayoutCreateFlags: u32 {
+ /// Specified that descriptor set must be allocated from\
+ /// pool with `DescriptorPoolCreateFlags::UPDATE_AFTER_BIND`.
+ ///
+ /// This flag must be specified when and only when layout was created with matching backend-specific flag,
+ /// that allows layout to have UpdateAfterBind bindings.
+ const UPDATE_AFTER_BIND = 0x2;
+ }
+}
+
+/// Descriptor set from allocator.
+#[derive(Debug)]
+pub struct DescriptorSet<S> {
+ raw: S,
+ pool_id: u64,
+ size: DescriptorTotalCount,
+ update_after_bind: bool,
+}
+
+impl<S> DescriptorSet<S> {
+ /// Returns reference to raw descriptor set.
+ pub fn raw(&self) -> &S {
+ &self.raw
+ }
+
+ /// Returns mutable reference to raw descriptor set.
+ ///
+ /// # Safety
+ ///
+ /// Object must not be replaced.
+ pub unsafe fn raw_mut(&mut self) -> &mut S {
+ &mut self.raw
+ }
+}
+
+/// AllocationError that may occur during descriptor sets allocation.
+#[derive(Debug)]
+pub enum AllocationError {
+ /// Backend reported that device memory has been exhausted.\
+ /// Deallocating device memory or other resources may increase chance
+ /// that another allocation would succeed.
+ OutOfDeviceMemory,
+
+ /// Backend reported that host memory has been exhausted.\
+ /// Deallocating host memory may increase chance that another allocation would succeed.
+ OutOfHostMemory,
+
+ /// The total number of descriptors across all pools created\
+ /// with flag `CREATE_UPDATE_AFTER_BIND_BIT` set exceeds `max_update_after_bind_descriptors_in_all_pools`
+ /// Or fragmentation of the underlying hardware resources occurs.
+ Fragmentation,
+}
+
+impl Display for AllocationError {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ AllocationError::OutOfDeviceMemory => fmt.write_str("Device memory exhausted"),
+ AllocationError::OutOfHostMemory => fmt.write_str("Host memory exhausted"),
+ AllocationError::Fragmentation => fmt.write_str("Fragmentation"),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for AllocationError {}
+
+impl From<CreatePoolError> for AllocationError {
+ fn from(err: CreatePoolError) -> Self {
+ match err {
+ CreatePoolError::OutOfDeviceMemory => AllocationError::OutOfDeviceMemory,
+ CreatePoolError::OutOfHostMemory => AllocationError::OutOfHostMemory,
+ CreatePoolError::Fragmentation => AllocationError::Fragmentation,
+ }
+ }
+}
+
+const MIN_SETS: u32 = 64;
+const MAX_SETS: u32 = 512;
+
+#[derive(Debug)]
+struct DescriptorPool<P> {
+ raw: P,
+
+ /// Number of sets allocated from pool.
+ allocated: u32,
+
+ /// Expected number of sets available.
+ available: u32,
+}
+
+#[derive(Debug)]
+struct DescriptorBucket<P> {
+ offset: u64,
+ pools: VecDeque<DescriptorPool<P>>,
+ total: u64,
+ update_after_bind: bool,
+ size: DescriptorTotalCount,
+}
+
+impl<P> Drop for DescriptorBucket<P> {
+ #[cfg(feature = "tracing")]
+ fn drop(&mut self) {
+ #[cfg(feature = "std")]
+ {
+ if std::thread::panicking() {
+ return;
+ }
+ }
+ if self.total > 0 {
+ tracing::error!("Descriptor sets were not deallocated");
+ }
+ }
+
+ #[cfg(all(not(feature = "tracing"), feature = "std"))]
+ fn drop(&mut self) {
+ if std::thread::panicking() {
+ return;
+ }
+ if self.total > 0 {
+ eprintln!("Descriptor sets were not deallocated")
+ }
+ }
+
+ #[cfg(all(not(feature = "tracing"), not(feature = "std")))]
+ fn drop(&mut self) {
+ if self.total > 0 {
+ panic!("Descriptor sets were not deallocated")
+ }
+ }
+}
+
+impl<P> DescriptorBucket<P> {
+ fn new(update_after_bind: bool, size: DescriptorTotalCount) -> Self {
+ DescriptorBucket {
+ offset: 0,
+ pools: VecDeque::new(),
+ total: 0,
+ update_after_bind,
+ size,
+ }
+ }
+
+ fn new_pool_size(&self, minimal_set_count: u32) -> (DescriptorTotalCount, u32) {
+ let mut max_sets = MIN_SETS // at least MIN_SETS
+ .max(minimal_set_count) // at least enough for allocation
+ .max(self.total.min(MAX_SETS as u64) as u32) // at least as much as was allocated so far capped to MAX_SETS
+ .checked_next_power_of_two() // rounded up to nearest 2^N
+ .unwrap_or(i32::MAX as u32);
+
+ max_sets = (u32::MAX / self.size.sampler.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.combined_image_sampler.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.sampled_image.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.storage_image.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.uniform_texel_buffer.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.storage_texel_buffer.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.uniform_buffer.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.storage_buffer.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.uniform_buffer_dynamic.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.storage_buffer_dynamic.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.input_attachment.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.acceleration_structure.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.inline_uniform_block_bytes.max(1)).min(max_sets);
+ max_sets = (u32::MAX / self.size.inline_uniform_block_bindings.max(1)).min(max_sets);
+
+ let mut pool_size = DescriptorTotalCount {
+ sampler: self.size.sampler * max_sets,
+ combined_image_sampler: self.size.combined_image_sampler * max_sets,
+ sampled_image: self.size.sampled_image * max_sets,
+ storage_image: self.size.storage_image * max_sets,
+ uniform_texel_buffer: self.size.uniform_texel_buffer * max_sets,
+ storage_texel_buffer: self.size.storage_texel_buffer * max_sets,
+ uniform_buffer: self.size.uniform_buffer * max_sets,
+ storage_buffer: self.size.storage_buffer * max_sets,
+ uniform_buffer_dynamic: self.size.uniform_buffer_dynamic * max_sets,
+ storage_buffer_dynamic: self.size.storage_buffer_dynamic * max_sets,
+ input_attachment: self.size.input_attachment * max_sets,
+ acceleration_structure: self.size.acceleration_structure * max_sets,
+ inline_uniform_block_bytes: self.size.inline_uniform_block_bytes * max_sets,
+ inline_uniform_block_bindings: self.size.inline_uniform_block_bindings * max_sets,
+ };
+
+ if pool_size == Default::default() {
+ pool_size.sampler = 1;
+ }
+
+ (pool_size, max_sets)
+ }
+
+ unsafe fn allocate<L, S>(
+ &mut self,
+ device: &impl DescriptorDevice<L, P, S>,
+ layout: &L,
+ mut count: u32,
+ allocated_sets: &mut Vec<DescriptorSet<S>>,
+ ) -> Result<(), AllocationError> {
+ debug_assert!(usize::try_from(count).is_ok(), "Must be ensured by caller");
+
+ if count == 0 {
+ return Ok(());
+ }
+
+ for (index, pool) in self.pools.iter_mut().enumerate().rev() {
+ if pool.available == 0 {
+ continue;
+ }
+
+ let allocate = pool.available.min(count);
+
+ #[cfg(feature = "tracing")]
+ tracing::trace!("Allocate `{}` sets from exising pool", allocate);
+
+ let result = device.alloc_descriptor_sets(
+ &mut pool.raw,
+ (0..allocate).map(|_| layout),
+ &mut Allocation {
+ size: self.size,
+ update_after_bind: self.update_after_bind,
+ pool_id: index as u64 + self.offset,
+ sets: allocated_sets,
+ },
+ );
+
+ match result {
+ Ok(()) => {}
+ Err(DeviceAllocationError::OutOfDeviceMemory) => {
+ return Err(AllocationError::OutOfDeviceMemory)
+ }
+ Err(DeviceAllocationError::OutOfHostMemory) => {
+ return Err(AllocationError::OutOfHostMemory)
+ }
+ Err(DeviceAllocationError::FragmentedPool) => {
+ // Should not happen, but better this than panicing.
+ #[cfg(feature = "tracing")]
+ tracing::error!("Unexpectedly failed to allocated descriptor sets due to pool fragmentation");
+ pool.available = 0;
+ continue;
+ }
+ Err(DeviceAllocationError::OutOfPoolMemory) => {
+ pool.available = 0;
+ continue;
+ }
+ }
+
+ count -= allocate;
+ pool.available -= allocate;
+ pool.allocated += allocate;
+ self.total += u64::from(allocate);
+
+ if count == 0 {
+ return Ok(());
+ }
+ }
+
+ while count > 0 {
+ let (pool_size, max_sets) = self.new_pool_size(count);
+ #[cfg(feature = "tracing")]
+ tracing::trace!(
+ "Create new pool with {} sets and {:?} descriptors",
+ max_sets,
+ pool_size,
+ );
+
+ let mut raw = device.create_descriptor_pool(
+ &pool_size,
+ max_sets,
+ if self.update_after_bind {
+ DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET
+ | DescriptorPoolCreateFlags::UPDATE_AFTER_BIND
+ } else {
+ DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET
+ },
+ )?;
+
+ let pool_id = self.pools.len() as u64 + self.offset;
+
+ let allocate = max_sets.min(count);
+ let result = device.alloc_descriptor_sets(
+ &mut raw,
+ (0..allocate).map(|_| layout),
+ &mut Allocation {
+ pool_id,
+ size: self.size,
+ update_after_bind: self.update_after_bind,
+ sets: allocated_sets,
+ },
+ );
+
+ match result {
+ Ok(()) => {}
+ Err(err) => {
+ device.destroy_descriptor_pool(raw);
+ match err {
+ DeviceAllocationError::OutOfDeviceMemory => {
+ return Err(AllocationError::OutOfDeviceMemory)
+ }
+ DeviceAllocationError::OutOfHostMemory => {
+ return Err(AllocationError::OutOfHostMemory)
+ }
+ DeviceAllocationError::FragmentedPool => {
+ // Should not happen, but better this than panicing.
+ #[cfg(feature = "trace")]
+ trace::error!("Unexpectedly failed to allocated descriptor sets due to pool fragmentation");
+ }
+ DeviceAllocationError::OutOfPoolMemory => {}
+ }
+ panic!("Failed to allocate descriptor sets from fresh pool");
+ }
+ }
+
+ count -= allocate;
+ self.pools.push_back(DescriptorPool {
+ raw,
+ allocated: allocate,
+ available: max_sets - allocate,
+ });
+ self.total += allocate as u64;
+ }
+
+ Ok(())
+ }
+
+ unsafe fn free<L, S>(
+ &mut self,
+ device: &impl DescriptorDevice<L, P, S>,
+ raw_sets: impl IntoIterator<Item = S>,
+ pool_id: u64,
+ ) {
+ let pool = usize::try_from(pool_id - self.offset)
+ .ok()
+ .and_then(|index| self.pools.get_mut(index))
+ .expect("Invalid pool id");
+
+ let mut raw_sets = raw_sets.into_iter();
+ let mut count = 0;
+ device.dealloc_descriptor_sets(&mut pool.raw, raw_sets.by_ref().inspect(|_| count += 1));
+
+ debug_assert!(
+ raw_sets.next().is_none(),
+ "Device must deallocated all sets from iterator"
+ );
+
+ pool.available += count;
+ pool.allocated -= count;
+ self.total -= u64::from(count);
+ #[cfg(feature = "tracing")]
+ tracing::trace!("Freed {} from descriptor bucket", count);
+
+ while let Some(pool) = self.pools.pop_front() {
+ if self.pools.is_empty() || pool.allocated != 0 {
+ self.pools.push_front(pool);
+ break;
+ }
+
+ #[cfg(feature = "tracing")]
+ tracing::trace!("Destroying old descriptor pool");
+
+ device.destroy_descriptor_pool(pool.raw);
+ self.offset += 1;
+ }
+ }
+
+ unsafe fn cleanup<L, S>(&mut self, device: &impl DescriptorDevice<L, P, S>) {
+ while let Some(pool) = self.pools.pop_front() {
+ if pool.allocated != 0 {
+ self.pools.push_front(pool);
+ break;
+ }
+
+ #[cfg(feature = "tracing")]
+ tracing::trace!("Destroying old descriptor pool");
+
+ device.destroy_descriptor_pool(pool.raw);
+ self.offset += 1;
+ }
+ }
+}
+
+/// Descriptor allocator.
+/// Can be used to allocate descriptor sets for any layout.
+#[derive(Debug)]
+pub struct DescriptorAllocator<P, S> {
+ buckets: HashMap<(DescriptorTotalCount, bool), DescriptorBucket<P>>,
+ total: u64,
+ sets_cache: Vec<DescriptorSet<S>>,
+ raw_sets_cache: Vec<S>,
+ max_update_after_bind_descriptors_in_all_pools: u32,
+}
+
+impl<P, S> Drop for DescriptorAllocator<P, S> {
+ fn drop(&mut self) {
+ if self.buckets.drain().any(|(_, bucket)| bucket.total != 0) {
+ #[cfg(feature = "tracing")]
+ tracing::error!(
+ "`DescriptorAllocator` is dropped while some descriptor sets were not deallocated"
+ );
+ }
+ }
+}
+
+impl<P, S> DescriptorAllocator<P, S> {
+ /// Create new allocator instance.
+ pub fn new(max_update_after_bind_descriptors_in_all_pools: u32) -> Self {
+ DescriptorAllocator {
+ buckets: HashMap::default(),
+ total: 0,
+ sets_cache: Vec::new(),
+ raw_sets_cache: Vec::new(),
+ max_update_after_bind_descriptors_in_all_pools,
+ }
+ }
+
+ /// Allocate descriptor set with specified layout.
+ ///
+ /// # Safety
+ ///
+ /// * Same `device` instance must be passed to all method calls of
+ /// one `DescriptorAllocator` instance.
+ /// * `flags` must match flags that were used to create the layout.
+ /// * `layout_descriptor_count` must match descriptor numbers in the layout.
+ pub unsafe fn allocate<L, D>(
+ &mut self,
+ device: &D,
+ layout: &L,
+ flags: DescriptorSetLayoutCreateFlags,
+ layout_descriptor_count: &DescriptorTotalCount,
+ count: u32,
+ ) -> Result<Vec<DescriptorSet<S>>, AllocationError>
+ where
+ S: Debug,
+ L: Debug,
+ D: DescriptorDevice<L, P, S>,
+ {
+ if count == 0 {
+ return Ok(Vec::new());
+ }
+
+ let update_after_bind = flags.contains(DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND);
+
+ #[cfg(feature = "tracing")]
+ tracing::trace!(
+ "Allocating {} sets with layout {:?} @ {:?}",
+ count,
+ layout,
+ layout_descriptor_count
+ );
+
+ let bucket = self
+ .buckets
+ .entry((*layout_descriptor_count, update_after_bind))
+ .or_insert_with(|| DescriptorBucket::new(update_after_bind, *layout_descriptor_count));
+ match bucket.allocate(device, layout, count, &mut self.sets_cache) {
+ Ok(()) => Ok(core::mem::replace(&mut self.sets_cache, Vec::new())),
+ Err(err) => {
+ debug_assert!(self.raw_sets_cache.is_empty());
+
+ // Free sets allocated so far.
+ let mut last = None;
+
+ for set in self.sets_cache.drain(..) {
+ if Some(set.pool_id) != last {
+ if let Some(last_id) = last {
+ // Free contiguous range of sets from one pool in one go.
+ bucket.free(device, self.raw_sets_cache.drain(..), last_id);
+ }
+ }
+ last = Some(set.pool_id);
+ self.raw_sets_cache.push(set.raw);
+ }
+
+ if let Some(last_id) = last {
+ bucket.free(device, self.raw_sets_cache.drain(..), last_id);
+ }
+
+ Err(err)
+ }
+ }
+ }
+
+ /// Free descriptor sets.
+ ///
+ /// # Safety
+ ///
+ /// * Same `device` instance must be passed to all method calls of
+ /// one `DescriptorAllocator` instance.
+ /// * None of descriptor sets can be referenced in any pending command buffers.
+ /// * All command buffers where at least one of descriptor sets referenced
+ /// move to invalid state.
+ pub unsafe fn free<L, D, I>(&mut self, device: &D, sets: I)
+ where
+ D: DescriptorDevice<L, P, S>,
+ I: IntoIterator<Item = DescriptorSet<S>>,
+ {
+ debug_assert!(self.raw_sets_cache.is_empty());
+
+ let mut last_key = (EMPTY_COUNT, false);
+ let mut last_pool_id = None;
+
+ for set in sets {
+ if last_key != (set.size, set.update_after_bind) || last_pool_id != Some(set.pool_id) {
+ if let Some(pool_id) = last_pool_id {
+ let bucket = self
+ .buckets
+ .get_mut(&last_key)
+ .expect("Set must be allocated from this allocator");
+
+ debug_assert!(u64::try_from(self.raw_sets_cache.len())
+ .ok()
+ .map_or(false, |count| count <= bucket.total));
+
+ bucket.free(device, self.raw_sets_cache.drain(..), pool_id);
+ }
+ last_key = (set.size, set.update_after_bind);
+ last_pool_id = Some(set.pool_id);
+ }
+ self.raw_sets_cache.push(set.raw);
+ }
+
+ if let Some(pool_id) = last_pool_id {
+ let bucket = self
+ .buckets
+ .get_mut(&last_key)
+ .expect("Set must be allocated from this allocator");
+
+ debug_assert!(u64::try_from(self.raw_sets_cache.len())
+ .ok()
+ .map_or(false, |count| count <= bucket.total));
+
+ bucket.free(device, self.raw_sets_cache.drain(..), pool_id);
+ }
+ }
+
+ /// Perform cleanup to allow resources reuse.
+ ///
+ /// # Safety
+ ///
+ /// * Same `device` instance must be passed to all method calls of
+ /// one `DescriptorAllocator` instance.
+ pub unsafe fn cleanup<L>(&mut self, device: &impl DescriptorDevice<L, P, S>) {
+ for bucket in self.buckets.values_mut() {
+ bucket.cleanup(device)
+ }
+ self.buckets.retain(|_, bucket| !bucket.pools.is_empty());
+ }
+}
+
+/// Empty descriptor per_type.
+const EMPTY_COUNT: DescriptorTotalCount = DescriptorTotalCount {
+ sampler: 0,
+ combined_image_sampler: 0,
+ sampled_image: 0,
+ storage_image: 0,
+ uniform_texel_buffer: 0,
+ storage_texel_buffer: 0,
+ uniform_buffer: 0,
+ storage_buffer: 0,
+ uniform_buffer_dynamic: 0,
+ storage_buffer_dynamic: 0,
+ input_attachment: 0,
+ acceleration_structure: 0,
+ inline_uniform_block_bytes: 0,
+ inline_uniform_block_bindings: 0,
+};
+
+struct Allocation<'a, S> {
+ update_after_bind: bool,
+ size: DescriptorTotalCount,
+ pool_id: u64,
+ sets: &'a mut Vec<DescriptorSet<S>>,
+}
+
+impl<S> Extend<S> for Allocation<'_, S> {
+ fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {
+ let update_after_bind = self.update_after_bind;
+ let size = self.size;
+ let pool_id = self.pool_id;
+ self.sets.extend(iter.into_iter().map(|raw| DescriptorSet {
+ raw,
+ pool_id,
+ update_after_bind,
+ size,
+ }))
+ }
+}
diff --git a/third_party/rust/gpu-descriptor/src/lib.rs b/third_party/rust/gpu-descriptor/src/lib.rs
new file mode 100644
index 0000000000..08217454e3
--- /dev/null
+++ b/third_party/rust/gpu-descriptor/src/lib.rs
@@ -0,0 +1,35 @@
+//!
+//! Library for Vulkan-like APIs to allocated descriptor sets
+//! from descriptor pools fast, with least overhead and zero fragmentation.
+//!
+//! Straightforward usage:
+//! ```ignore
+//! use gpu_descriptor::DescriptorAllocator;
+//!
+//! let mut allocator = DescriptorAllocator::new(max_update_after_bind_descriptors_in_all_pools); // Limit as dictated by API for selected hardware
+//!
+//! let result = allocator.allocate(
+//! device, // Implementation of `gpu_descriptor::DescriptorDevice`. Comes from plugins.
+//! layout, // Descriptor set layout recognized by device's type.
+//! flags, // Flags specified when layout was created.
+//! layout_descriptor_count, // Descriptors count in the layout.
+//! count, // count of sets to allocated.
+//! );
+//! ```
+//!
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![warn(
+ missing_docs,
+ trivial_casts,
+ trivial_numeric_casts,
+ unused_extern_crates,
+ unused_import_braces,
+ unused_qualifications
+)]
+
+extern crate alloc;
+
+mod allocator;
+
+pub use {crate::allocator::*, gpu_descriptor_types::*};