summaryrefslogtreecommitdiffstats
path: root/third_party/rust/gpu-descriptor
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/gpu-descriptor')
-rw-r--r--third_party/rust/gpu-descriptor/.cargo-checksum.json1
-rw-r--r--third_party/rust/gpu-descriptor/Cargo.toml22
-rw-r--r--third_party/rust/gpu-descriptor/src/allocator.rs574
-rw-r--r--third_party/rust/gpu-descriptor/src/lib.rs19
4 files changed, 616 insertions, 0 deletions
diff --git a/third_party/rust/gpu-descriptor/.cargo-checksum.json b/third_party/rust/gpu-descriptor/.cargo-checksum.json
new file mode 100644
index 0000000000..3d865846ea
--- /dev/null
+++ b/third_party/rust/gpu-descriptor/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"6d324a3812c6a537665208271ff80b09cc87ea48ab8dc2e440f18a9b8e23e2f9","src/allocator.rs":"810857111189458f798b4366df76e36a16779b112290fa133cfbd1edc3aa2d75","src/lib.rs":"fb014819810bf90f8ef006f493c6f97f8fc43b01afa5e43faebee7b2b311c1b9"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/gpu-descriptor/Cargo.toml b/third_party/rust/gpu-descriptor/Cargo.toml
new file mode 100644
index 0000000000..d057e0de67
--- /dev/null
+++ b/third_party/rust/gpu-descriptor/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "gpu-descriptor"
+version = "0.1.0"
+authors = ["Zakarum <zakarumych@ya.ru>"]
+edition = "2018"
+description = "Implementation agnostic descriptor allocator for Vulkan like APIs"
+keywords = ["gpu", "vulkan", "no-std"]
+license = "MIT OR Apache-2.0"
+documentation = "https://docs.rs/gpu-descriptor-types/0.1.0"
+homepage = "https://github.com/zakarumych/gpu-descriptor"
+repository = "https://github.com/zakarumych/gpu-descriptor"
+
+[features]
+std = []
+default = ["std"]
+
+[dependencies]
+gpu-descriptor-types = { path = "../types", version = "0.1" }
+tracing = { version = "0.1", optional = true, default-features = false }
+bitflags = { version = "1.2", default-features = false }
+serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] }
+hashbrown = { version = "0.9" }
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..50d38afa6c
--- /dev/null
+++ b/third_party/rust/gpu-descriptor/src/allocator.rs
@@ -0,0 +1,574 @@
+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: u8 {
+ /// 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 = 0x1;
+ }
+}
+
+/// 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> {
+ fn drop(&mut self) {
+ assert_eq!(
+ self.total, 0,
+ "Allocator dropped before all sets were deallocated"
+ );
+
+ assert!(
+ self.pools.is_empty(),
+ "All sets deallocated but pools were not. Make sure to call `Allocator::cleanup`"
+ );
+ }
+}
+
+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 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,
+ };
+
+ (size, max_sets)
+ }
+
+ 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);
+
+ match unsafe {
+ device.alloc_descriptor_sets(
+ &mut pool.raw,
+ core::iter::repeat(layout).take(allocate as usize),
+ &mut Allocation {
+ size: self.size,
+ update_after_bind: self.update_after_bind,
+ pool_id: index as u64 + self.offset,
+ sets: allocated_sets,
+ },
+ )
+ } {
+ 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 = unsafe {
+ 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 = unsafe {
+ device.alloc_descriptor_sets(
+ &mut raw,
+ core::iter::repeat(layout).take(allocate as usize),
+ &mut Allocation {
+ pool_id,
+ size: self.size,
+ update_after_bind: self.update_after_bind,
+ sets: allocated_sets,
+ },
+ )
+ };
+
+ match result {
+ Ok(()) => {}
+ Err(err) => {
+ unsafe { 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(())
+ }
+
+ 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;
+ unsafe {
+ 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");
+
+ unsafe { device.destroy_descriptor_pool(pool.raw) };
+ self.offset += 1;
+ }
+ }
+
+ 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");
+
+ unsafe { 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.is_empty() {
+ #[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.
+ ///
+ /// # Safety
+ /// All later operations assume the device is not lost.
+ pub unsafe 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.
+ /// `DescriptorTotalCount` must match descriptor numbers of the layout.
+ /// `DescriptorTotalCount` can be constructed [from bindings] that were used
+ /// to create layout instance.
+ ///
+ /// [from bindings]: .
+ pub fn allocate<L: Debug>(
+ &mut self,
+ device: &impl DescriptorDevice<L, P, S>,
+ layout: &L,
+ flags: DescriptorSetLayoutCreateFlags,
+ layout_descriptor_count: &DescriptorTotalCount,
+ count: u32,
+ ) -> Result<Vec<DescriptorSet<S>>, AllocationError>
+ where
+ S: Debug,
+ {
+ 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.clone(), 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
+ ///
+ /// 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>(
+ &mut self,
+ device: &impl DescriptorDevice<L, P, S>,
+ sets: impl 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.
+ pub fn cleanup<L>(&mut self, device: &impl DescriptorDevice<L, P, S>) {
+ for bucket in self.buckets.values_mut() {
+ bucket.cleanup(device)
+ }
+ }
+}
+
+/// 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..76ec478298
--- /dev/null
+++ b/third_party/rust/gpu-descriptor/src/lib.rs
@@ -0,0 +1,19 @@
+//!
+//! gpu-descriptor crate.
+//!
+
+#![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::*};