From 28f311b5984439a75b514d4fda24c55016080b4f Mon Sep 17 00:00:00 2001 From: John Wells Date: Sat, 27 Jan 2024 19:29:19 -0500 Subject: [PATCH 1/8] Clean up pool code --- src/pool/hash.rs | 83 +++++++++--------------- src/pool/lazy.rs | 160 ++++++++++++----------------------------------- src/pool/mod.rs | 47 +++++++++++--- 3 files changed, 108 insertions(+), 182 deletions(-) diff --git a/src/pool/hash.rs b/src/pool/hash.rs index 942d3d9..e828017 100644 --- a/src/pool/hash.rs +++ b/src/pool/hash.rs @@ -4,7 +4,7 @@ //! the exact information provided then a new resource is created and returned. use { - super::{Cache, Lease, Pool}, + super::{can_lease_command_buffer, Cache, Lease, Pool}, crate::driver::{ accel_struct::{ AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, @@ -18,7 +18,6 @@ use { parking_lot::Mutex, std::{ collections::{HashMap, VecDeque}, - fmt::Debug, sync::Arc, }, }; @@ -53,49 +52,43 @@ impl HashPool { } } -impl HashPool { - fn can_lease_command_buffer(cmd_buf: &mut CommandBuffer) -> bool { - let can_lease = unsafe { - // Don't lease this command buffer if it is unsignalled; we'll create a new one - // and wait for this, and those behind it, to signal. - cmd_buf - .device - .get_fence_status(cmd_buf.fence) - .unwrap_or_default() - }; - - if can_lease { - // Drop anything we were holding from the last submission - CommandBuffer::drop_fenced(cmd_buf); - } - - can_lease - } -} - impl Pool for HashPool { #[profiling::function] fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { - let command_buffer_cache = self + let cache = self .command_buffer_cache .entry(info.queue_family_index) .or_default(); - let cache_ref = Arc::downgrade(command_buffer_cache); - let mut cache = command_buffer_cache.lock(); + let mut item = cache + .lock() + .pop_front() + .filter(can_lease_command_buffer) + .map(Ok) + .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; - if cache.is_empty() || !Self::can_lease_command_buffer(cache.front_mut().unwrap()) { - let item = CommandBuffer::create(&self.device, info)?; + // Drop anything we were holding from the last submission + CommandBuffer::drop_fenced(&mut item); - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); - } + Ok(Lease::new(Arc::downgrade(cache), item)) + } +} + +impl Pool for HashPool { + #[profiling::function] + fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { + let cache = if let Some(cache) = self.render_pass_cache.get(&info) { + cache + } else { + // We tried to get the cache first in order to avoid this clone + self.render_pass_cache.entry(info.clone()).or_default() + }; + let item = cache + .lock() + .pop_front() + .map(Ok) + .unwrap_or_else(|| RenderPass::create(&self.device, info))?; - Ok(Lease { - cache: Some(cache_ref), - item: cache.pop_front(), - }) + Ok(Lease::new(Arc::downgrade(cache), item)) } } @@ -110,29 +103,15 @@ macro_rules! lease { .or_insert_with(|| { Arc::new(Mutex::new(VecDeque::new())) }); - let cache_ref = Arc::downgrade(cache); - let mut cache = cache.lock(); - - if cache.is_empty() { - let item = $item::create(&self.device, info)?; - - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); - } + let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| $item::create(&self.device, info))?; - Ok(Lease { - cache: Some(cache_ref), - item: cache.pop_front(), - }) + Ok(Lease::new(Arc::downgrade(cache), item)) } } } }; } -lease!(RenderPassInfo => RenderPass); lease!(DescriptorPoolInfo => DescriptorPool); // Enable leasing items as above, but also using their info builder type for convenience diff --git a/src/pool/lazy.rs b/src/pool/lazy.rs index 9b113c9..9700e48 100644 --- a/src/pool/lazy.rs +++ b/src/pool/lazy.rs @@ -9,7 +9,7 @@ //! * Images may have additional usage flags use { - super::{Cache, Lease, Pool}, + super::{can_lease_command_buffer, Cache, Lease, Pool}, crate::driver::{ accel_struct::{ AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, @@ -21,12 +21,7 @@ use { RenderPass, RenderPassInfo, }, ash::vk, - parking_lot::Mutex, - std::{ - collections::{HashMap, VecDeque}, - fmt::Debug, - sync::Arc, - }, + std::{collections::HashMap, sync::Arc}, }; #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] @@ -98,21 +93,12 @@ impl Pool for LazyPool { &mut self, info: AccelerationStructureInfo, ) -> Result, DriverError> { - let acceleration_structure_cache = self + let cache = self .acceleration_structure_cache .entry(info.ty) .or_default(); - let cache_ref = Arc::downgrade(acceleration_structure_cache); - let mut cache = acceleration_structure_cache.lock(); - - if cache.is_empty() { - let item = AccelerationStructure::create(&self.device, info)?; - - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); - } + let cache_ref = Arc::downgrade(cache); + let mut cache = cache.lock(); { profiling::scope!("Check cache"); @@ -123,20 +109,14 @@ impl Pool for LazyPool { if item.info.size >= info.size { let item = cache.remove(idx).unwrap(); - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); + return Ok(Lease::new(cache_ref, item)); } } } let item = AccelerationStructure::create(&self.device, info)?; - Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }) + Ok(Lease::new(cache_ref, item)) } } @@ -152,18 +132,9 @@ impl Pool for LazyPool impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: BufferInfo) -> Result, DriverError> { - let buffer_cache = self.buffer_cache.entry(info.can_map).or_default(); - let cache_ref = Arc::downgrade(buffer_cache); - let mut cache = buffer_cache.lock(); - - if cache.is_empty() { - let item = Buffer::create(&self.device, info)?; - - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); - } + let cache = self.buffer_cache.entry(info.can_map).or_default(); + let cache_ref = Arc::downgrade(cache); + let mut cache = cache.lock(); { profiling::scope!("Check cache"); @@ -177,20 +148,14 @@ impl Pool for LazyPool { { let item = cache.remove(idx).unwrap(); - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); + return Ok(Lease::new(cache_ref, item)); } } } let item = Buffer::create(&self.device, info)?; - Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }) + Ok(Lease::new(cache_ref, item)) } } @@ -206,15 +171,6 @@ impl Pool for LazyPool { let cache_ref = Arc::downgrade(&self.descriptor_pool_cache); let mut cache = self.descriptor_pool_cache.lock(); - if cache.is_empty() { - let item = DescriptorPool::create(&self.device, info)?; - - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); - } - { profiling::scope!("Check cache"); @@ -236,27 +192,21 @@ impl Pool for LazyPool { { let item = cache.remove(idx).unwrap(); - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); + return Ok(Lease::new(cache_ref, item)); } } } let item = DescriptorPool::create(&self.device, info)?; - Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }) + Ok(Lease::new(cache_ref, item)) } } impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: ImageInfo) -> Result, DriverError> { - let image_cache = self + let cache = self .image_cache .entry(ImageKey { array_elements: info.array_elements, @@ -270,17 +220,8 @@ impl Pool for LazyPool { width: info.width, }) .or_default(); - let cache_ref = Arc::downgrade(image_cache); - let mut cache = image_cache.lock(); - - if cache.is_empty() { - let item = Image::create(&self.device, info)?; - - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); - } + let cache_ref = Arc::downgrade(cache); + let mut cache = cache.lock(); { profiling::scope!("Check cache"); @@ -291,20 +232,14 @@ impl Pool for LazyPool { if item.info.flags.contains(info.flags) && item.info.usage.contains(info.usage) { let item = cache.remove(idx).unwrap(); - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); + return Ok(Lease::new(cache_ref, item)); } } } let item = Image::create(&self.device, info)?; - Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }) + Ok(Lease::new(cache_ref, item)) } } @@ -317,54 +252,39 @@ impl Pool for LazyPool { impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { - if let Some(cache) = self.render_pass_cache.get(&info) { - let item = if let Some(item) = cache.lock().pop_front() { - item - } else { - RenderPass::create(&self.device, info)? - }; - - Ok(Lease { - cache: Some(Arc::downgrade(cache)), - item: Some(item), - }) + let cache = if let Some(cache) = self.render_pass_cache.get(&info) { + cache } else { - let cache = Arc::new(Mutex::new(VecDeque::new())); - let cache_ref = Arc::downgrade(&cache); - self.render_pass_cache.insert(info.clone(), cache); - - let item = RenderPass::create(&self.device, info)?; + // We tried to get the cache first in order to avoid this clone + self.render_pass_cache.entry(info.clone()).or_default() + }; + let item = cache + .lock() + .pop_front() + .map(Ok) + .unwrap_or_else(|| RenderPass::create(&self.device, info))?; - Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }) - } + Ok(Lease::new(Arc::downgrade(cache), item)) } } impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { - let command_buffer_cache = self + let cache = self .command_buffer_cache .entry(info.queue_family_index) .or_default(); - let cache_ref = Arc::downgrade(command_buffer_cache); - let mut cache = command_buffer_cache.lock(); + let mut item = cache + .lock() + .pop_front() + .filter(can_lease_command_buffer) + .map(Ok) + .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; - if cache.is_empty() || !Self::can_lease_command_buffer(cache.front_mut().unwrap()) { - let item = CommandBuffer::create(&self.device, info)?; - - return Ok(Lease { - cache: Some(cache_ref), - item: Some(item), - }); - } + // Drop anything we were holding from the last submission + CommandBuffer::drop_fenced(&mut item); - Ok(Lease { - cache: Some(cache_ref), - item: cache.pop_front(), - }) + Ok(Lease::new(Arc::downgrade(cache), item)) } } diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 8e2e23f..1ddc0b2 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -48,11 +48,12 @@ pub mod hash; pub mod lazy; use { - crate::driver::DriverError, + crate::driver::{CommandBuffer, DriverError}, parking_lot::Mutex, std::{ collections::VecDeque, fmt::Debug, + mem::ManuallyDrop, ops::{Deref, DerefMut}, sync::{Arc, Weak}, thread::panicking, @@ -62,6 +63,17 @@ use { type Cache = Arc>>; type CacheRef = Weak>>; +fn can_lease_command_buffer(cmd_buf: &CommandBuffer) -> bool { + unsafe { + // Don't lease this command buffer if it is unsignalled; we'll create a new one + // and wait for this, and those behind it, to signal. + cmd_buf + .device + .get_fence_status(cmd_buf.fence) + .unwrap_or_default() + } +} + /// Holds a leased resource and implements `Drop` in order to return the resource. /// /// This simple wrapper type implements only the `AsRef`, `AsMut`, `Deref` and `DerefMut` traits @@ -69,19 +81,28 @@ type CacheRef = Weak>>; /// owners and may be mutably accessed. #[derive(Debug)] pub struct Lease { - cache: Option>, - item: Option, + cache_ref: CacheRef, + item: ManuallyDrop, +} + +impl Lease { + fn new(cache_ref: CacheRef, item: T) -> Self { + Self { + cache_ref, + item: ManuallyDrop::new(item), + } + } } impl AsRef for Lease { fn as_ref(&self) -> &T { - self.item.as_ref().unwrap() + &self.item } } impl AsMut for Lease { fn as_mut(&mut self) -> &mut T { - self.item.as_mut().unwrap() + &mut self.item } } @@ -89,13 +110,13 @@ impl Deref for Lease { type Target = T; fn deref(&self) -> &Self::Target { - self.item.as_ref().unwrap() + &self.item } } impl DerefMut for Lease { fn deref_mut(&mut self) -> &mut Self::Target { - self.item.as_mut().unwrap() + &mut self.item } } @@ -105,9 +126,15 @@ impl Drop for Lease { return; } - if let Some(cache) = self.cache.as_ref() { - if let Some(cache) = cache.upgrade() { - cache.lock().push_back(self.item.take().unwrap()); + // If the pool cache has been dropped we must manually drop the item, otherwise it goes back + // into the pool. + if let Some(cache) = self.cache_ref.upgrade() { + cache + .lock() + .push_back(unsafe { ManuallyDrop::take(&mut self.item) }); + } else { + unsafe { + ManuallyDrop::drop(&mut self.item); } } } From 4b308e7743528823a738c486881b5a07c0020618 Mon Sep 17 00:00:00 2001 From: John Wells Date: Sat, 27 Jan 2024 22:48:14 -0500 Subject: [PATCH 2/8] Remove unused function --- src/pool/lazy.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/pool/lazy.rs b/src/pool/lazy.rs index 9700e48..1e8ab36 100644 --- a/src/pool/lazy.rs +++ b/src/pool/lazy.rs @@ -66,25 +66,6 @@ impl LazyPool { render_pass_cache: Default::default(), } } - - #[profiling::function] - fn can_lease_command_buffer(cmd_buf: &mut CommandBuffer) -> bool { - let can_lease = unsafe { - // Don't lease this command buffer if it is unsignalled; we'll create a new one - // and wait for this, and those behind it, to signal. - cmd_buf - .device - .get_fence_status(cmd_buf.fence) - .unwrap_or_default() - }; - - if can_lease { - // Drop anything we were holding from the last submission - CommandBuffer::drop_fenced(cmd_buf); - } - - can_lease - } } impl Pool for LazyPool { From a334604ac48ea12aaa362d139f7a5ced8f36bcba Mon Sep 17 00:00:00 2001 From: John Wells Date: Sat, 27 Jan 2024 23:12:39 -0500 Subject: [PATCH 3/8] Add basic pool memory management functions --- src/pool/hash.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++++- src/pool/lazy.rs | 52 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/pool/hash.rs b/src/pool/hash.rs index e828017..401257a 100644 --- a/src/pool/hash.rs +++ b/src/pool/hash.rs @@ -16,6 +16,7 @@ use { RenderPass, RenderPassInfo, }, parking_lot::Mutex, + paste::paste, std::{ collections::{HashMap, VecDeque}, sync::Arc, @@ -34,7 +35,6 @@ pub struct HashPool { render_pass_cache: HashMap>, } -// TODO: Add some sort of manager features (like, I dunno, "Clear Some Memory For me") impl HashPool { /// Constructs a new `HashPool`. pub fn new(device: &Arc) -> Self { @@ -50,8 +50,61 @@ impl HashPool { render_pass_cache: Default::default(), } } + + /// Clears the pool, removing all resources. + pub fn clear(&mut self) { + self.clear_accel_structs(); + self.clear_buffers(); + self.clear_images(); + } } +macro_rules! resource_mgmt_fns { + ($fn_plural:literal, $doc_plural:literal, $ty:ty, $field:ident) => { + paste! { + impl HashPool { + #[doc = "Clears the pool of " $doc_plural ", removing all resources."] + pub fn [](&mut self) { + self.$field.clear(); + } + + #[doc = "Clears the pool of " $doc_plural ", removing resources matching the +given information."] + pub fn []( + &mut self, + info: impl Into<$ty>, + ) { + self.$field.remove(&info.into()); + } + + #[doc = "Retains only the " $doc_plural " specified by the predicate.\n\nIn other +words, remove all resources for which `f(&" $ty ")` returns `false`.\n\n"] + /// The elements are visited in unsorted (and unspecified) order. + /// + /// # Performance + /// + /// Provides the same performance guarantees as + /// [`HashMap::retain`](HashMap::retain). + pub fn [](&mut self, mut f: F) + where + F: FnMut(&$ty) -> bool, + { + self.$field.retain(|info, _| f(info)) + } + } + } + }; +} + +resource_mgmt_fns!( + "accel_structs", + "acceleration structures", + AccelerationStructureInfo, + acceleration_structure_cache +); +resource_mgmt_fns!("buffers", "buffers", BufferInfo, buffer_cache); +resource_mgmt_fns!("images", "images", ImageInfo, image_cache); + impl Pool for HashPool { #[profiling::function] fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { diff --git a/src/pool/lazy.rs b/src/pool/lazy.rs index 1e8ab36..8c6752e 100644 --- a/src/pool/lazy.rs +++ b/src/pool/lazy.rs @@ -50,7 +50,6 @@ pub struct LazyPool { render_pass_cache: HashMap>, } -// TODO: Add some sort of manager features (like, I dunno, "Clear Some Memory For me") impl LazyPool { /// Constructs a new `LazyPool`. pub fn new(device: &Arc) -> Self { @@ -66,6 +65,52 @@ impl LazyPool { render_pass_cache: Default::default(), } } + + /// Clears the pool, removing all resources. + pub fn clear(&mut self) { + self.clear_accel_structs(); + self.clear_buffers(); + self.clear_images(); + } + + /// Clears the pool of acceleration structures, removing all resources. + pub fn clear_accel_structs(&mut self) { + self.acceleration_structure_cache.clear(); + } + + /// Clears the pool of acceleration structures, removing resources matching the given + /// type. + pub fn clear_accel_structs_by_ty(&mut self, ty: vk::AccelerationStructureTypeKHR) { + self.acceleration_structure_cache.remove(&ty); + } + + /// Clears the pool of buffers, removing all resources. + pub fn clear_buffers(&mut self) { + self.buffer_cache.clear(); + } + + /// Clears the pool of images, removing all resources. + pub fn clear_images(&mut self) { + self.image_cache.clear(); + } + + /// Retains only the acceleration structures specified by the predicate. + /// + /// In other words, remove all resources for which `f(vk::AccelerationStructureTypeKHR)` returns + /// `false`. + /// + /// The elements are visited in unsorted (and unspecified) order. + /// + /// # Performance + /// + /// Provides the same performance guarantees as + /// [`HashMap::retain`](HashMap::retain). + pub fn retain_accel_structs(&mut self, mut f: F) + where + F: FnMut(vk::AccelerationStructureTypeKHR) -> bool, + { + self.acceleration_structure_cache.retain(|&ty, _| f(ty)) + } } impl Pool for LazyPool { @@ -123,10 +168,7 @@ impl Pool for LazyPool { // Look for a compatible buffer (same mapping mode, big enough, superset of usage flags) for idx in 0..cache.len() { let item = &cache[idx]; - if item.info.can_map == info.can_map - && item.info.size >= info.size - && item.info.usage.contains(info.usage) - { + if item.info.size >= info.size && item.info.usage.contains(info.usage) { let item = cache.remove(idx).unwrap(); return Ok(Lease::new(cache_ref, item)); From 0aa680fe39b7efbf4371a856db2596fdb20d2e73 Mon Sep 17 00:00:00 2001 From: John Wells Date: Sun, 28 Jan 2024 16:25:04 -0500 Subject: [PATCH 4/8] Add FifoPool and configurable pool bucket sizes --- examples/msaa.rs | 2 +- src/lib.rs | 5 +- src/pool/fifo.rs | 227 +++++++++++++++++++++++++++++++++++++++++++++++ src/pool/hash.rs | 70 ++++++++------- src/pool/lazy.rs | 107 ++++++++++------------ src/pool/mod.rs | 115 +++++++++++++++++++++++- 6 files changed, 429 insertions(+), 97 deletions(-) create mode 100644 src/pool/fifo.rs diff --git a/examples/msaa.rs b/examples/msaa.rs index 14bf40d..1564344 100644 --- a/examples/msaa.rs +++ b/examples/msaa.rs @@ -24,7 +24,7 @@ fn main() -> anyhow::Result<()> { let mesh_msaa_pipeline = create_mesh_pipeline(&event_loop.device, sample_count)?; let mesh_noaa_pipeline = create_mesh_pipeline(&event_loop.device, SampleCount::X1)?; let cube_mesh = load_cube_mesh(&event_loop.device)?; - let mut pool = HashPool::new(&event_loop.device); + let mut pool = FifoPool::new(&event_loop.device); let mut angle = 0f32; diff --git a/src/lib.rs b/src/lib.rs index b62b287..476480f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,7 +385,10 @@ pub mod prelude { input::{ update_input, update_keyboard, update_mouse, KeyBuf, KeyMap, MouseBuf, MouseButton, }, - pool::{hash::HashPool, lazy::LazyPool, Lease, Pool}, + pool::{ + fifo::FifoPool, hash::HashPool, lazy::LazyPool, Lease, Pool, PoolInfo, + PoolInfoBuilder, + }, }, ash::vk, log::{debug, error, info, logger, trace, warn}, // Everyone wants a log diff --git a/src/pool/fifo.rs b/src/pool/fifo.rs new file mode 100644 index 0000000..3ca8b5a --- /dev/null +++ b/src/pool/fifo.rs @@ -0,0 +1,227 @@ +//! TODO + +use { + super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, + crate::driver::{ + accel_struct::{AccelerationStructure, AccelerationStructureInfo}, + buffer::{Buffer, BufferInfo}, + device::Device, + image::{Image, ImageInfo}, + CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, + RenderPass, RenderPassInfo, + }, + std::{collections::HashMap, sync::Arc}, +}; + +/// A space-efficient resource allocator. +#[derive(Debug)] +pub struct FifoPool { + accel_struct_cache: Cache, + buffer_cache: Cache, + command_buffer_cache: HashMap>, + descriptor_pool_cache: Cache, + device: Arc, + image_cache: Cache, + render_pass_cache: HashMap>, +} + +impl FifoPool { + /// Constructs a new `FifoPool`. + pub fn new(device: &Arc) -> Self { + Self::with_capacity(device, PoolInfo::default()) + } + + /// Constructs a new `FifoPool` with the given capacity information. + pub fn with_capacity(device: &Arc, info: impl Into) -> Self { + let info: PoolInfo = info.into(); + let device = Arc::clone(device); + + Self { + accel_struct_cache: PoolInfo::explicit_cache(info.accel_struct_capacity), + buffer_cache: PoolInfo::explicit_cache(info.buffer_capacity), + command_buffer_cache: Default::default(), + descriptor_pool_cache: PoolInfo::default_cache(), + device, + image_cache: PoolInfo::explicit_cache(info.image_capacity), + render_pass_cache: Default::default(), + } + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease( + &mut self, + info: AccelerationStructureInfo, + ) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.accel_struct_cache); + let mut cache = self.accel_struct_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible acceleration structure (big enough and same type) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.size >= info.size && item.info.ty == info.ty { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = AccelerationStructure::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: BufferInfo) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.buffer_cache); + let mut cache = self.buffer_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible buffer (compatible alignment, same mapping mode, big enough and + // superset of usage flags) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.alignment >= info.alignment + && item.info.can_map == info.can_map + && item.info.size >= info.size + && item.info.usage.contains(info.usage) + { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = Buffer::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { + let cache = self + .command_buffer_cache + .entry(info.queue_family_index) + .or_insert_with(PoolInfo::default_cache); + let mut item = cache + .lock() + .pop_front() + .filter(can_lease_command_buffer) + .map(Ok) + .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; + + // Drop anything we were holding from the last submission + CommandBuffer::drop_fenced(&mut item); + + Ok(Lease::new(Arc::downgrade(cache), item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: DescriptorPoolInfo) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.descriptor_pool_cache); + let mut cache = self.descriptor_pool_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible descriptor pool (has enough sets and descriptors) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.max_sets >= info.max_sets + && item.info.acceleration_structure_count >= info.acceleration_structure_count + && item.info.combined_image_sampler_count >= info.combined_image_sampler_count + && item.info.input_attachment_count >= info.input_attachment_count + && item.info.sampled_image_count >= info.sampled_image_count + && item.info.storage_buffer_count >= info.storage_buffer_count + && item.info.storage_buffer_dynamic_count >= info.storage_buffer_dynamic_count + && item.info.storage_image_count >= info.storage_image_count + && item.info.storage_texel_buffer_count >= info.storage_texel_buffer_count + && item.info.uniform_buffer_count >= info.uniform_buffer_count + && item.info.uniform_buffer_dynamic_count >= info.uniform_buffer_dynamic_count + && item.info.uniform_texel_buffer_count >= info.uniform_texel_buffer_count + { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = DescriptorPool::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: ImageInfo) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.image_cache); + let mut cache = self.image_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible image (same properties, superset of creation flags and usage + // flags) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.array_elements == info.array_elements + && item.info.depth == info.depth + && item.info.fmt == info.fmt + && item.info.height == info.height + && item.info.linear_tiling == info.linear_tiling + && item.info.mip_level_count == info.mip_level_count + && item.info.sample_count == info.sample_count + && item.info.ty == info.ty + && item.info.width == info.width + && item.info.flags.contains(info.flags) + && item.info.usage.contains(info.usage) + { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = Image::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { + let cache = if let Some(cache) = self.render_pass_cache.get(&info) { + cache + } else { + // We tried to get the cache first in order to avoid this clone + self.render_pass_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache) + }; + let item = cache + .lock() + .pop_front() + .map(Ok) + .unwrap_or_else(|| RenderPass::create(&self.device, info))?; + + Ok(Lease::new(Arc::downgrade(cache), item)) + } +} diff --git a/src/pool/hash.rs b/src/pool/hash.rs index 401257a..7bfa325 100644 --- a/src/pool/hash.rs +++ b/src/pool/hash.rs @@ -4,14 +4,12 @@ //! the exact information provided then a new resource is created and returned. use { - super::{can_lease_command_buffer, Cache, Lease, Pool}, + super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, crate::driver::{ - accel_struct::{ - AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, - }, - buffer::{Buffer, BufferInfo, BufferInfoBuilder}, + accel_struct::{AccelerationStructure, AccelerationStructureInfo}, + buffer::{Buffer, BufferInfo}, device::Device, - image::{Image, ImageInfo, ImageInfoBuilder}, + image::{Image, ImageInfo}, CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, RenderPass, RenderPassInfo, }, @@ -32,12 +30,19 @@ pub struct HashPool { descriptor_pool_cache: HashMap>, device: Arc, image_cache: HashMap>, + info: PoolInfo, render_pass_cache: HashMap>, } impl HashPool { /// Constructs a new `HashPool`. pub fn new(device: &Arc) -> Self { + Self::with_capacity(device, PoolInfo::default()) + } + + /// Constructs a new `HashPool` with the given capacity information. + pub fn with_capacity(device: &Arc, info: impl Into) -> Self { + let info: PoolInfo = info.into(); let device = Arc::clone(device); Self { @@ -47,6 +52,7 @@ impl HashPool { descriptor_pool_cache: Default::default(), device, image_cache: Default::default(), + info, render_pass_cache: Default::default(), } } @@ -111,7 +117,7 @@ impl Pool for HashPool { let cache = self .command_buffer_cache .entry(info.queue_family_index) - .or_default(); + .or_insert_with(PoolInfo::default_cache); let mut item = cache .lock() .pop_front() @@ -126,6 +132,23 @@ impl Pool for HashPool { } } +impl Pool for HashPool { + #[profiling::function] + fn lease(&mut self, info: DescriptorPoolInfo) -> Result, DriverError> { + let cache = self + .descriptor_pool_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache); + let item = cache + .lock() + .pop_front() + .map(Ok) + .unwrap_or_else(|| DescriptorPool::create(&self.device, info))?; + + Ok(Lease::new(Arc::downgrade(cache), item)) + } +} + impl Pool for HashPool { #[profiling::function] fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { @@ -133,7 +156,9 @@ impl Pool for HashPool { cache } else { // We tried to get the cache first in order to avoid this clone - self.render_pass_cache.entry(info.clone()).or_default() + self.render_pass_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache) }; let item = cache .lock() @@ -147,14 +172,14 @@ impl Pool for HashPool { // Enable leasing items using their basic info macro_rules! lease { - ($info:ident => $item:ident) => { + ($info:ident => $item:ident, $capacity:ident) => { paste::paste! { impl Pool<$info, $item> for HashPool { #[profiling::function] fn lease(&mut self, info: $info) -> Result, DriverError> { let cache = self.[<$item:snake _cache>].entry(info.clone()) .or_insert_with(|| { - Arc::new(Mutex::new(VecDeque::new())) + Cache::new(Mutex::new(VecDeque::with_capacity(self.info.$capacity))) }); let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| $item::create(&self.device, info))?; @@ -165,25 +190,6 @@ macro_rules! lease { }; } -lease!(DescriptorPoolInfo => DescriptorPool); - -// Enable leasing items as above, but also using their info builder type for convenience -macro_rules! lease_builder { - ($info:ident => $item:ident) => { - lease!($info => $item); - - paste::paste! { - impl Pool<[<$info Builder>], $item> for HashPool { - fn lease(&mut self, builder: [<$info Builder>]) -> Result, DriverError> { - let info = builder.build(); - - self.lease(info) - } - } - } - }; -} - -lease_builder!(AccelerationStructureInfo => AccelerationStructure); -lease_builder!(BufferInfo => Buffer); -lease_builder!(ImageInfo => Image); +lease!(AccelerationStructureInfo => AccelerationStructure, accel_struct_capacity); +lease!(BufferInfo => Buffer, buffer_capacity); +lease!(ImageInfo => Image, image_capacity); diff --git a/src/pool/lazy.rs b/src/pool/lazy.rs index 8c6752e..e32aa97 100644 --- a/src/pool/lazy.rs +++ b/src/pool/lazy.rs @@ -9,14 +9,12 @@ //! * Images may have additional usage flags use { - super::{can_lease_command_buffer, Cache, Lease, Pool}, + super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, crate::driver::{ - accel_struct::{ - AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, - }, - buffer::{Buffer, BufferInfo, BufferInfoBuilder}, + accel_struct::{AccelerationStructure, AccelerationStructureInfo}, + buffer::{Buffer, BufferInfo}, device::Device, - image::{Image, ImageInfo, ImageInfoBuilder, ImageType, SampleCount}, + image::{Image, ImageInfo, ImageType, SampleCount}, CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, RenderPass, RenderPassInfo, }, @@ -40,28 +38,35 @@ struct ImageKey { /// A high-efficiency resource allocator. #[derive(Debug)] pub struct LazyPool { - acceleration_structure_cache: - HashMap>, - buffer_cache: HashMap>, + accel_struct_cache: HashMap>, + buffer_cache: HashMap<(bool, vk::DeviceSize), Cache>, command_buffer_cache: HashMap>, descriptor_pool_cache: Cache, device: Arc, image_cache: HashMap>, + info: PoolInfo, render_pass_cache: HashMap>, } impl LazyPool { /// Constructs a new `LazyPool`. pub fn new(device: &Arc) -> Self { + Self::with_capacity(device, PoolInfo::default()) + } + + /// Constructs a new `LazyPool` with the given capacity information. + pub fn with_capacity(device: &Arc, info: impl Into) -> Self { + let info: PoolInfo = info.into(); let device = Arc::clone(device); Self { - acceleration_structure_cache: Default::default(), + accel_struct_cache: Default::default(), buffer_cache: Default::default(), command_buffer_cache: Default::default(), - descriptor_pool_cache: Default::default(), + descriptor_pool_cache: PoolInfo::default_cache(), device, image_cache: Default::default(), + info, render_pass_cache: Default::default(), } } @@ -75,13 +80,13 @@ impl LazyPool { /// Clears the pool of acceleration structures, removing all resources. pub fn clear_accel_structs(&mut self) { - self.acceleration_structure_cache.clear(); + self.accel_struct_cache.clear(); } /// Clears the pool of acceleration structures, removing resources matching the given /// type. pub fn clear_accel_structs_by_ty(&mut self, ty: vk::AccelerationStructureTypeKHR) { - self.acceleration_structure_cache.remove(&ty); + self.accel_struct_cache.remove(&ty); } /// Clears the pool of buffers, removing all resources. @@ -109,7 +114,7 @@ impl LazyPool { where F: FnMut(vk::AccelerationStructureTypeKHR) -> bool, { - self.acceleration_structure_cache.retain(|&ty, _| f(ty)) + self.accel_struct_cache.retain(|&ty, _| f(ty)) } } @@ -120,9 +125,9 @@ impl Pool for LazyPool { info: AccelerationStructureInfo, ) -> Result, DriverError> { let cache = self - .acceleration_structure_cache + .accel_struct_cache .entry(info.ty) - .or_default(); + .or_insert_with(|| PoolInfo::explicit_cache(self.info.accel_struct_capacity)); let cache_ref = Arc::downgrade(cache); let mut cache = cache.lock(); @@ -146,26 +151,20 @@ impl Pool for LazyPool { } } -impl Pool for LazyPool { - fn lease( - &mut self, - info: AccelerationStructureInfoBuilder, - ) -> Result, DriverError> { - self.lease(info.build()) - } -} - impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: BufferInfo) -> Result, DriverError> { - let cache = self.buffer_cache.entry(info.can_map).or_default(); + let cache = self + .buffer_cache + .entry((info.can_map, info.alignment)) + .or_insert_with(|| PoolInfo::explicit_cache(self.info.buffer_capacity)); let cache_ref = Arc::downgrade(cache); let mut cache = cache.lock(); { profiling::scope!("Check cache"); - // Look for a compatible buffer (same mapping mode, big enough, superset of usage flags) + // Look for a compatible buffer (big enough and superset of usage flags) for idx in 0..cache.len() { let item = &cache[idx]; if item.info.size >= info.size && item.info.usage.contains(info.usage) { @@ -182,9 +181,24 @@ impl Pool for LazyPool { } } -impl Pool for LazyPool { - fn lease(&mut self, info: BufferInfoBuilder) -> Result, DriverError> { - self.lease(info.build()) +impl Pool for LazyPool { + #[profiling::function] + fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { + let cache = self + .command_buffer_cache + .entry(info.queue_family_index) + .or_insert_with(PoolInfo::default_cache); + let mut item = cache + .lock() + .pop_front() + .filter(can_lease_command_buffer) + .map(Ok) + .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; + + // Drop anything we were holding from the last submission + CommandBuffer::drop_fenced(&mut item); + + Ok(Lease::new(Arc::downgrade(cache), item)) } } @@ -242,7 +256,7 @@ impl Pool for LazyPool { ty: info.ty, width: info.width, }) - .or_default(); + .or_insert_with(|| PoolInfo::explicit_cache(self.info.image_capacity)); let cache_ref = Arc::downgrade(cache); let mut cache = cache.lock(); @@ -266,12 +280,6 @@ impl Pool for LazyPool { } } -impl Pool for LazyPool { - fn lease(&mut self, info: ImageInfoBuilder) -> Result, DriverError> { - self.lease(info.build()) - } -} - impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { @@ -279,7 +287,9 @@ impl Pool for LazyPool { cache } else { // We tried to get the cache first in order to avoid this clone - self.render_pass_cache.entry(info.clone()).or_default() + self.render_pass_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache) }; let item = cache .lock() @@ -290,24 +300,3 @@ impl Pool for LazyPool { Ok(Lease::new(Arc::downgrade(cache), item)) } } - -impl Pool for LazyPool { - #[profiling::function] - fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { - let cache = self - .command_buffer_cache - .entry(info.queue_family_index) - .or_default(); - let mut item = cache - .lock() - .pop_front() - .filter(can_lease_command_buffer) - .map(Ok) - .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; - - // Drop anything we were holding from the last submission - CommandBuffer::drop_fenced(&mut item); - - Ok(Lease::new(Arc::downgrade(cache), item)) - } -} diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 1ddc0b2..61a4a75 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -44,11 +44,20 @@ //! * Processor usage is most important //! * Resources have consistent attributes each frame +pub mod fifo; pub mod hash; pub mod lazy; use { - crate::driver::{CommandBuffer, DriverError}, + crate::driver::{ + accel_struct::{ + AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, + }, + buffer::{Buffer, BufferInfo, BufferInfoBuilder}, + image::{Image, ImageInfo, ImageInfoBuilder}, + CommandBuffer, DriverError, + }, + derive_builder::{Builder, UninitializedFieldError}, parking_lot::Mutex, std::{ collections::VecDeque, @@ -129,9 +138,13 @@ impl Drop for Lease { // If the pool cache has been dropped we must manually drop the item, otherwise it goes back // into the pool. if let Some(cache) = self.cache_ref.upgrade() { - cache - .lock() - .push_back(unsafe { ManuallyDrop::take(&mut self.item) }); + let mut cache = cache.lock(); + + if cache.len() >= cache.capacity() { + cache.pop_front(); + } + + cache.push_back(unsafe { ManuallyDrop::take(&mut self.item) }); } else { unsafe { ManuallyDrop::drop(&mut self.item); @@ -145,3 +158,97 @@ pub trait Pool { /// Lease a resource. fn lease(&mut self, info: I) -> Result, DriverError>; } + +// Enable leasing items using their info builder type for convenience +macro_rules! lease_builder { + ($info:ident => $item:ident) => { + paste::paste! { + impl Pool<[<$info Builder>], $item> for T where T: Pool<$info, $item> { + fn lease(&mut self, builder: [<$info Builder>]) -> Result, DriverError> { + let info = builder.build(); + + self.lease(info) + } + } + } + }; +} + +lease_builder!(AccelerationStructureInfo => AccelerationStructure); +lease_builder!(BufferInfo => Buffer); +lease_builder!(ImageInfo => Image); + +/// TBD +#[derive(Builder, Clone, Copy, Debug)] +#[builder( + build_fn(private, name = "fallible_build", error = "PoolInfoBuilderError"), + derive(Debug), + pattern = "owned" +)] +pub struct PoolInfo { + /// TBD + #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] + pub accel_struct_capacity: usize, + + /// TBD + #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] + pub buffer_capacity: usize, + + /// TBD + #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] + pub image_capacity: usize, +} + +impl PoolInfo { + /// TBD + pub const DEFAULT_RESOURCE_CAPACITY: usize = 4; + + /// TBD + pub const fn with_capacity(resource_capacity: usize) -> Self { + Self { + accel_struct_capacity: resource_capacity, + buffer_capacity: resource_capacity, + image_capacity: resource_capacity, + } + } + + fn default_cache() -> Cache { + Cache::new(Mutex::new(VecDeque::with_capacity( + Self::DEFAULT_RESOURCE_CAPACITY, + ))) + } + + fn explicit_cache(capacity: usize) -> Cache { + Cache::new(Mutex::new(VecDeque::with_capacity(capacity))) + } +} + +impl Default for PoolInfo { + fn default() -> Self { + PoolInfoBuilder::default().into() + } +} + +impl From for PoolInfo { + fn from(info: PoolInfoBuilder) -> Self { + info.build() + } +} + +// HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56 +impl PoolInfoBuilder { + /// Builds a new `PoolInfo`. + pub fn build(self) -> PoolInfo { + self.fallible_build() + .expect("All required fields set at initialization") + } +} + +#[derive(Debug)] +struct PoolInfoBuilderError; + +impl From for PoolInfoBuilderError { + fn from(_: UninitializedFieldError) -> Self { + Self + } +} From b700f327023259d1de02e12414f064b1d4f18404 Mon Sep 17 00:00:00 2001 From: John Wells Date: Mon, 29 Jan 2024 13:05:08 -0500 Subject: [PATCH 5/8] Add From for PoolInfo --- src/pool/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 61a4a75..70067d8 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -235,6 +235,16 @@ impl From for PoolInfo { } } +impl From for PoolInfo { + fn from(value: usize) -> Self { + Self { + accel_struct_capacity: value, + buffer_capacity: value, + image_capacity: value, + } + } +} + // HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56 impl PoolInfoBuilder { /// Builds a new `PoolInfo`. From 4f178d2cffe767adcb6ab309f32a9661b604b368 Mon Sep 17 00:00:00 2001 From: John Wells Date: Mon, 29 Jan 2024 23:19:28 -0500 Subject: [PATCH 6/8] Add documentation --- src/pool/fifo.rs | 53 ++++++++++++++++++++++++++++++-- src/pool/hash.rs | 40 +++++++++++++++--------- src/pool/lazy.rs | 79 ++++++++++++++++++++++++++++++++---------------- src/pool/mod.rs | 66 +++++++++++++++++++++++++++++++--------- 4 files changed, 181 insertions(+), 57 deletions(-) diff --git a/src/pool/fifo.rs b/src/pool/fifo.rs index 3ca8b5a..8e6986d 100644 --- a/src/pool/fifo.rs +++ b/src/pool/fifo.rs @@ -1,4 +1,4 @@ -//! TODO +//! Pool which leases from a single bucket per resource type. use { super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, @@ -13,7 +13,32 @@ use { std::{collections::HashMap, sync::Arc}, }; -/// A space-efficient resource allocator. +/// A memory-efficient resource allocator. +/// +/// The information for each lease request is compared against the stored resources for +/// compatibility. If no acceptable resources are stored for the information provided a new resource +/// is created and returned. +/// +/// # Details +/// +/// * Acceleration structures may be larger than requested +/// * Buffers may be larger than requested or have additional usage flags +/// * Images may have additional usage flags +/// +/// # Bucket Strategy +/// +/// All resources are stored in a single bucket per resource type, regardless of their individual +/// attributes. +/// +/// In practice this means that for a [`PoolInfo::image_capacity`] of `4`, a maximum of `4` images +/// will be stored. Requests to lease an image or other resource will first look for a compatible +/// resource in the bucket and create a new resource as needed. +/// +/// # Memory Management +/// +/// The single-bucket strategy means that there will always be a reasonable and predictable number +/// of stored resources, however you may call [`FifoPool::clear`] or the other memory management +/// functions at any time to discard stored resources. #[derive(Debug)] pub struct FifoPool { accel_struct_cache: Cache, @@ -22,6 +47,7 @@ pub struct FifoPool { descriptor_pool_cache: Cache, device: Arc, image_cache: Cache, + info: PoolInfo, render_pass_cache: HashMap>, } @@ -43,9 +69,32 @@ impl FifoPool { descriptor_pool_cache: PoolInfo::default_cache(), device, image_cache: PoolInfo::explicit_cache(info.image_capacity), + info, render_pass_cache: Default::default(), } } + + /// Clears the pool, removing all resources. + pub fn clear(&mut self) { + self.clear_accel_structs(); + self.clear_buffers(); + self.clear_images(); + } + + /// Clears the pool of acceleration structure resources. + pub fn clear_accel_structs(&mut self) { + self.accel_struct_cache = PoolInfo::explicit_cache(self.info.accel_struct_capacity); + } + + /// Clears the pool of buffer resources. + pub fn clear_buffers(&mut self) { + self.buffer_cache = PoolInfo::explicit_cache(self.info.buffer_capacity); + } + + /// Clears the pool of image resources. + pub fn clear_images(&mut self) { + self.image_cache = PoolInfo::explicit_cache(self.info.image_capacity); + } } impl Pool for FifoPool { diff --git a/src/pool/hash.rs b/src/pool/hash.rs index 7bfa325..f40a0fd 100644 --- a/src/pool/hash.rs +++ b/src/pool/hash.rs @@ -1,7 +1,4 @@ //! Pool which leases by exactly matching the information before creating new resources. -//! -//! The information for each lease request is placed into a `HashMap`. If no resources exist for -//! the exact information provided then a new resource is created and returned. use { super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, @@ -22,6 +19,20 @@ use { }; /// A high-performance resource allocator. +/// +/// # Bucket Strategy +/// +/// The information for each lease request is the key for a `HashMap` of buckets. If no bucket +/// exists with the exact information provided a new bucket is created. +/// +/// In practice this means that for a [`PoolInfo::image_capacity`] of `4`, requests for a 1024x1024 +/// image with certain attributes will store a maximum of `4` such images. Requests for any image +/// having a different size or attributes will store an additional maximum of `4` images. +/// +/// # Memory Management +/// +/// If requests for varying resources is common [`HashPool::clear_images_by_info`] and other memory +/// management functions are nessecery in order to avoid using all available device memory. #[derive(Debug)] pub struct HashPool { acceleration_structure_cache: HashMap>, @@ -66,16 +77,16 @@ impl HashPool { } macro_rules! resource_mgmt_fns { - ($fn_plural:literal, $doc_plural:literal, $ty:ty, $field:ident) => { + ($fn_plural:literal, $doc_singular:literal, $ty:ty, $field:ident) => { paste! { impl HashPool { - #[doc = "Clears the pool of " $doc_plural ", removing all resources."] + #[doc = "Clears the pool of " $doc_singular " resources."] pub fn [](&mut self) { self.$field.clear(); } - #[doc = "Clears the pool of " $doc_plural ", removing resources matching the -given information."] + #[doc = "Clears the pool of all " $doc_singular " resources matching the given +information."] pub fn []( &mut self, info: impl Into<$ty>, @@ -83,8 +94,9 @@ given information."] self.$field.remove(&info.into()); } - #[doc = "Retains only the " $doc_plural " specified by the predicate.\n\nIn other -words, remove all resources for which `f(&" $ty ")` returns `false`.\n\n"] + #[doc = "Retains only the " $doc_singular " resources specified by the predicate.\n +\nIn other words, remove all " $doc_singular " resources for which `f(" $ty ")` returns `false`.\n +\n"] /// The elements are visited in unsorted (and unspecified) order. /// /// # Performance @@ -93,9 +105,9 @@ words, remove all resources for which `f(&" $ty ")` returns `false`.\n\n"] /// [`HashMap::retain`](HashMap::retain). pub fn [](&mut self, mut f: F) where - F: FnMut(&$ty) -> bool, + F: FnMut($ty) -> bool, { - self.$field.retain(|info, _| f(info)) + self.$field.retain(|&info, _| f(info)) } } } @@ -104,12 +116,12 @@ words, remove all resources for which `f(&" $ty ")` returns `false`.\n\n"] resource_mgmt_fns!( "accel_structs", - "acceleration structures", + "acceleration structure", AccelerationStructureInfo, acceleration_structure_cache ); -resource_mgmt_fns!("buffers", "buffers", BufferInfo, buffer_cache); -resource_mgmt_fns!("images", "images", ImageInfo, image_cache); +resource_mgmt_fns!("buffers", "buffer", BufferInfo, buffer_cache); +resource_mgmt_fns!("images", "image", ImageInfo, image_cache); impl Pool for HashPool { #[profiling::function] diff --git a/src/pool/lazy.rs b/src/pool/lazy.rs index e32aa97..bbac0ce 100644 --- a/src/pool/lazy.rs +++ b/src/pool/lazy.rs @@ -1,12 +1,4 @@ //! Pool which leases by looking for compatibile information before creating new resources. -//! -//! The information for each lease request is loosely bucketed by compatibility. If no acceptable -//! resources exist for the information provided then a new resource is created and returned. -//! -//! # Details -//! * Acceleration structures may be larger than requested -//! * Buffers may be larger than request or have additional usage flags -//! * Images may have additional usage flags use { super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, @@ -35,7 +27,48 @@ struct ImageKey { width: u32, } -/// A high-efficiency resource allocator. +impl From for ImageKey { + fn from(info: ImageInfo) -> Self { + Self { + array_elements: info.array_elements, + depth: info.depth, + fmt: info.fmt, + height: info.height, + linear_tiling: info.linear_tiling, + mip_level_count: info.mip_level_count, + sample_count: info.sample_count, + ty: info.ty, + width: info.width, + } + } +} + +/// A balanced resource allocator. +/// +/// The information for each lease request is compared against the stored resources for +/// compatibility. If no acceptable resources are stored for the information provided a new resource +/// is created and returned. +/// +/// # Details +/// +/// * Acceleration structures may be larger than requested +/// * Buffers may be larger than requested or have additional usage flags +/// * Images may have additional usage flags +/// +/// # Bucket Strategy +/// +/// The information for each lease request is the key for a `HashMap` of buckets. If no bucket +/// exists with compatible information a new bucket is created. +/// +/// In practice this means that for a [`PoolInfo::image_capacity`] of `4`, requests for a 1024x1024 +/// image with certain attributes will store a maximum of `4` such images. Requests for any image +/// having a different size or incompatible attributes will store an additional maximum of `4` +/// images. +/// +/// # Memory Management +/// +/// If requests for varying resources is common [`LazyPool::clear_images_by_info`] and other memory +/// management functions are nessecery in order to avoid using all available device memory. #[derive(Debug)] pub struct LazyPool { accel_struct_cache: HashMap>, @@ -78,28 +111,32 @@ impl LazyPool { self.clear_images(); } - /// Clears the pool of acceleration structures, removing all resources. + /// Clears the pool of acceleration structure resources. pub fn clear_accel_structs(&mut self) { self.accel_struct_cache.clear(); } - /// Clears the pool of acceleration structures, removing resources matching the given - /// type. + /// Clears the pool of all acceleration structure resources matching the given type. pub fn clear_accel_structs_by_ty(&mut self, ty: vk::AccelerationStructureTypeKHR) { self.accel_struct_cache.remove(&ty); } - /// Clears the pool of buffers, removing all resources. + /// Clears the pool of buffer resources. pub fn clear_buffers(&mut self) { self.buffer_cache.clear(); } - /// Clears the pool of images, removing all resources. + /// Clears the pool of image resources. pub fn clear_images(&mut self) { self.image_cache.clear(); } - /// Retains only the acceleration structures specified by the predicate. + /// Clears the pool of image resources matching the given information. + pub fn clear_images_by_info(&mut self, info: impl Into) { + self.image_cache.remove(&info.into().into()); + } + + /// Retains only the acceleration structure resources specified by the predicate. /// /// In other words, remove all resources for which `f(vk::AccelerationStructureTypeKHR)` returns /// `false`. @@ -245,17 +282,7 @@ impl Pool for LazyPool { fn lease(&mut self, info: ImageInfo) -> Result, DriverError> { let cache = self .image_cache - .entry(ImageKey { - array_elements: info.array_elements, - depth: info.depth, - fmt: info.fmt, - height: info.height, - linear_tiling: info.linear_tiling, - mip_level_count: info.mip_level_count, - sample_count: info.sample_count, - ty: info.ty, - width: info.width, - }) + .entry(info.into()) .or_insert_with(|| PoolInfo::explicit_cache(self.info.image_capacity)); let cache_ref = Arc::downgrade(cache); let mut cache = cache.lock(); diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 70067d8..51ca095 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -1,12 +1,23 @@ //! Resource leasing and pooling types. //! -//! _Screen 13_ provides caching for acceleration structure, buffer, and image resources which may -//! be leased from configurable pools using their corresponding information structure. Most programs -//! will do fine with a single `LazyPool`. +//! _Screen 13_ provides caching for acceleration structure, buffer and image resources which may be +//! leased from configurable pools using their corresponding information structure. Most programs +//! will do fine with a single [`FifoPool`](self::fifo::FifoPool). //! //! Leased resources may be bound directly to a render graph and used in the same manner as regular //! resources. After rendering has finished, the leased resources will return to the pool for reuse. //! +//! # Buckets +//! +//! The provided [`Pool`] implementations store resources in buckets, with each implementation +//! offering a different strategy which balances performance (_more buckets_) with memory efficiency +//! (_fewer buckets_). +//! +//! _Screen 13_'s pools can be grouped into two major categories: +//! +//! * Single-bucket: [`FifoPool`](self::fifo::FifoPool) +//! * Multi-bucket: [`LazyPool`](self::lazy::LazyPool), [`HashPool`](self::hash::HashPool) +//! //! # Examples //! //! Leasing an image: @@ -33,15 +44,17 @@ //! # When Should You Use Which Pool? //! //! These are fairly high-level break-downs of when each pool should be considered. You may need -//! to investigate each type of pool individually or write your own implementation to provide the -//! absolute best fit for your purpose. +//! to investigate each type of pool individually to provide the absolute best fit for your purpose. +//! +//! ### Use a [`FifoPool`](self::fifo::FifoPool) when: +//! * Low memory usage is most important +//! * Automatic bucket management is desired //! -//! ### Use a `LazyPool` when: -//! * Memory usage is most important +//! ### Use a [`LazyPool`](self::lazy::LazyPool) when: //! * Resources have different attributes each frame //! -//! ### Use a `HashPool` when: -//! * Processor usage is most important +//! ### Use a [`HashPool`](self::hash::HashPool) when: +//! * High performance is most important //! * Resources have consistent attributes each frame pub mod fifo; @@ -178,7 +191,8 @@ lease_builder!(AccelerationStructureInfo => AccelerationStructure); lease_builder!(BufferInfo => Buffer); lease_builder!(ImageInfo => Image); -/// TBD +/// Information used to create a [`FifoPool`](self::fifo::FifoPool), +/// [`HashPool`](self::hash::HashPool) or [`LazyPool`](self::lazy::LazyPool) instance. #[derive(Builder, Clone, Copy, Debug)] #[builder( build_fn(private, name = "fallible_build", error = "PoolInfoBuilderError"), @@ -186,24 +200,46 @@ lease_builder!(ImageInfo => Image); pattern = "owned" )] pub struct PoolInfo { - /// TBD + /// The maximum size of a single bucket of acceleration structure resource instances. The + /// default value is [`PoolInfo::DEFAULT_RESOURCE_CAPACITY`]. + /// + /// # Note + /// + /// Individual [`Pool`] implementations store varying numbers of buckets. Read the documentation + /// of each implementation to understand how this affects total number of stored acceleration + /// structure instances. #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] pub accel_struct_capacity: usize, - /// TBD + /// The maximum size of a single bucket of buffer resource instances. The default value is + /// [`PoolInfo::DEFAULT_RESOURCE_CAPACITY`]. + /// + /// # Note + /// + /// Individual [`Pool`] implementations store varying numbers of buckets. Read the documentation + /// of each implementation to understand how this affects total number of stored buffer + /// instances. #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] pub buffer_capacity: usize, - /// TBD + /// The maximum size of a single bucket of image resource instances. The default value is + /// [`PoolInfo::DEFAULT_RESOURCE_CAPACITY`]. + /// + /// # Note + /// + /// Individual [`Pool`] implementations store varying numbers of buckets. Read the documentation + /// of each implementation to understand how this affects total number of stored image + /// instances. #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] pub image_capacity: usize, } impl PoolInfo { - /// TBD + /// The maximum size of a single bucket of resource instances. pub const DEFAULT_RESOURCE_CAPACITY: usize = 4; - /// TBD + /// Constructs a new `PoolInfo` with the given acceleration structure, buffer and image resource + /// capacity for any single bucket. pub const fn with_capacity(resource_capacity: usize) -> Self { Self { accel_struct_capacity: resource_capacity, From 903f1afd365eea520d1d8cdf428ea1c937a38d76 Mon Sep 17 00:00:00 2001 From: John Wells Date: Tue, 30 Jan 2024 00:24:44 -0500 Subject: [PATCH 7/8] Fix crash during resize --- examples/vsm_omni.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vsm_omni.rs b/examples/vsm_omni.rs index 835d2aa..d271e40 100644 --- a/examples/vsm_omni.rs +++ b/examples/vsm_omni.rs @@ -89,7 +89,7 @@ fn main() -> anyhow::Result<()> { }?; // A pool will be used for per-frame resources - let mut pool = LazyPool::new(&event_loop.device); + let mut pool = FifoPool::new(&event_loop.device); let mut elapsed = 0.0; event_loop.run(|frame| { From 85fee8dfe6c3e478666ce7ffaa90180c52efb757 Mon Sep 17 00:00:00 2001 From: John Wells Date: Tue, 30 Jan 2024 00:25:00 -0500 Subject: [PATCH 8/8] Add logging --- src/pool/fifo.rs | 25 +++++++++++++++++++------ src/pool/hash.rs | 33 +++++++++++++++++++++------------ src/pool/lazy.rs | 25 +++++++++++++++++++------ 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/pool/fifo.rs b/src/pool/fifo.rs index 8e6986d..ae2efdc 100644 --- a/src/pool/fifo.rs +++ b/src/pool/fifo.rs @@ -10,6 +10,7 @@ use { CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, RenderPass, RenderPassInfo, }, + log::debug, std::{collections::HashMap, sync::Arc}, }; @@ -120,6 +121,8 @@ impl Pool for FifoPool { } } + debug!("Creating new {}", stringify!(AccelerationStructure)); + let item = AccelerationStructure::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -151,6 +154,8 @@ impl Pool for FifoPool { } } + debug!("Creating new {}", stringify!(Buffer)); + let item = Buffer::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -169,7 +174,11 @@ impl Pool for FifoPool { .pop_front() .filter(can_lease_command_buffer) .map(Ok) - .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; + .unwrap_or_else(|| { + debug!("Creating new {}", stringify!(CommandBuffer)); + + CommandBuffer::create(&self.device, info) + })?; // Drop anything we were holding from the last submission CommandBuffer::drop_fenced(&mut item); @@ -210,6 +219,8 @@ impl Pool for FifoPool { } } + debug!("Creating new {}", stringify!(DescriptorPool)); + let item = DescriptorPool::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -248,6 +259,8 @@ impl Pool for FifoPool { } } + debug!("Creating new {}", stringify!(Image)); + let item = Image::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -265,11 +278,11 @@ impl Pool for FifoPool { .entry(info.clone()) .or_insert_with(PoolInfo::default_cache) }; - let item = cache - .lock() - .pop_front() - .map(Ok) - .unwrap_or_else(|| RenderPass::create(&self.device, info))?; + let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| { + debug!("Creating new {}", stringify!(RenderPass)); + + RenderPass::create(&self.device, info) + })?; Ok(Lease::new(Arc::downgrade(cache), item)) } diff --git a/src/pool/hash.rs b/src/pool/hash.rs index f40a0fd..ed17434 100644 --- a/src/pool/hash.rs +++ b/src/pool/hash.rs @@ -10,6 +10,7 @@ use { CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, RenderPass, RenderPassInfo, }, + log::debug, parking_lot::Mutex, paste::paste, std::{ @@ -135,7 +136,11 @@ impl Pool for HashPool { .pop_front() .filter(can_lease_command_buffer) .map(Ok) - .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; + .unwrap_or_else(|| { + debug!("Creating new {}", stringify!(CommandBuffer)); + + CommandBuffer::create(&self.device, info) + })?; // Drop anything we were holding from the last submission CommandBuffer::drop_fenced(&mut item); @@ -151,11 +156,11 @@ impl Pool for HashPool { .descriptor_pool_cache .entry(info.clone()) .or_insert_with(PoolInfo::default_cache); - let item = cache - .lock() - .pop_front() - .map(Ok) - .unwrap_or_else(|| DescriptorPool::create(&self.device, info))?; + let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| { + debug!("Creating new {}", stringify!(DescriptorPool)); + + DescriptorPool::create(&self.device, info) + })?; Ok(Lease::new(Arc::downgrade(cache), item)) } @@ -172,11 +177,11 @@ impl Pool for HashPool { .entry(info.clone()) .or_insert_with(PoolInfo::default_cache) }; - let item = cache - .lock() - .pop_front() - .map(Ok) - .unwrap_or_else(|| RenderPass::create(&self.device, info))?; + let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| { + debug!("Creating new {}", stringify!(RenderPass)); + + RenderPass::create(&self.device, info) + })?; Ok(Lease::new(Arc::downgrade(cache), item)) } @@ -193,7 +198,11 @@ macro_rules! lease { .or_insert_with(|| { Cache::new(Mutex::new(VecDeque::with_capacity(self.info.$capacity))) }); - let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| $item::create(&self.device, info))?; + let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| { + debug!("Creating new {}", stringify!($item)); + + $item::create(&self.device, info) + })?; Ok(Lease::new(Arc::downgrade(cache), item)) } diff --git a/src/pool/lazy.rs b/src/pool/lazy.rs index bbac0ce..6dfccc3 100644 --- a/src/pool/lazy.rs +++ b/src/pool/lazy.rs @@ -11,6 +11,7 @@ use { RenderPass, RenderPassInfo, }, ash::vk, + log::debug, std::{collections::HashMap, sync::Arc}, }; @@ -182,6 +183,8 @@ impl Pool for LazyPool { } } + debug!("Creating new {}", stringify!(AccelerationStructure)); + let item = AccelerationStructure::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -212,6 +215,8 @@ impl Pool for LazyPool { } } + debug!("Creating new {}", stringify!(Buffer)); + let item = Buffer::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -230,7 +235,11 @@ impl Pool for LazyPool { .pop_front() .filter(can_lease_command_buffer) .map(Ok) - .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; + .unwrap_or_else(|| { + debug!("Creating new {}", stringify!(CommandBuffer)); + + CommandBuffer::create(&self.device, info) + })?; // Drop anything we were holding from the last submission CommandBuffer::drop_fenced(&mut item); @@ -271,6 +280,8 @@ impl Pool for LazyPool { } } + debug!("Creating new {}", stringify!(DescriptorPool)); + let item = DescriptorPool::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -301,6 +312,8 @@ impl Pool for LazyPool { } } + debug!("Creating new {}", stringify!(Image)); + let item = Image::create(&self.device, info)?; Ok(Lease::new(cache_ref, item)) @@ -318,11 +331,11 @@ impl Pool for LazyPool { .entry(info.clone()) .or_insert_with(PoolInfo::default_cache) }; - let item = cache - .lock() - .pop_front() - .map(Ok) - .unwrap_or_else(|| RenderPass::create(&self.device, info))?; + let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| { + debug!("Creating new {}", stringify!(RenderPass)); + + RenderPass::create(&self.device, info) + })?; Ok(Lease::new(Arc::downgrade(cache), item)) }