Skip to main content

vk_graph/pool/
mod.rs

1//! Resource pooling, requesting, and caching types.
2//!
3//! Resource pools provide caching for buffer, image, and acceleration structure resources. Pooled
4//! resources may be requested from a pool using their corresponding information structure.
5//!
6//! Leased resources may be bound directly to a [`Graph`](crate::Graph) and used in the same manner
7//! as regular resources. After execution has completed pooled resources are automatically returned
8//! to their pool for reuse.
9//!
10//! # Buckets
11//!
12//! The provided [`Pool`] implementations store resources in buckets, with each implementation
13//! offering a different strategy which balances performance (_more buckets_) with memory efficiency
14//! (_fewer buckets_).
15//!
16//! _vk-graph_'s pools can be grouped into two major categories:
17//!
18//! * Single-bucket: [`FifoPool`](self::fifo::FifoPool)
19//! * Multi-bucket: [`LazyPool`](self::lazy::LazyPool), [`HashPool`](self::hash::HashPool)
20//!
21//! # Examples
22//!
23//! Leasing an image:
24//!
25//! ```no_run
26//! # use std::sync::Arc;
27//! # use ash::vk;
28//! # use vk_graph::driver::DriverError;
29//! # use vk_graph::driver::device::{Device, DeviceInfo};
30//! # use vk_graph::driver::image::{ImageInfo};
31//! # use vk_graph::pool::{Pool};
32//! # use vk_graph::pool::lazy::{LazyPool};
33//! # fn main() -> Result<(), DriverError> {
34//! # let device = Device::create(DeviceInfo::default())?;
35//! let mut pool = LazyPool::new(&device);
36//!
37//! let info = ImageInfo::image_2d(8, 8, vk::Format::R8G8B8A8_UNORM, vk::ImageUsageFlags::STORAGE);
38//! let my_image = pool.resource(info)?;
39//!
40//! assert!(my_image.info.usage.contains(vk::ImageUsageFlags::STORAGE));
41//! # Ok(()) }
42//! ```
43//!
44//! # When Should You Use Which Pool?
45//!
46//! These are fairly high-level break-downs of when each pool should be considered. You may need
47//! to investigate each type of pool individually to provide the absolute best fit for your purpose.
48//!
49//! ### Use a [`FifoPool`](self::fifo::FifoPool) when:
50//! * Low memory usage is most important
51//! * Automatic bucket management is desired
52//!
53//! ### Use a [`LazyPool`](self::lazy::LazyPool) when:
54//! * Resources have different attributes each frame
55//!
56//! ### Use a [`HashPool`](self::hash::HashPool) when:
57//! * High performance is most important
58//! * Resources have consistent attributes each frame
59//!
60//! # When Should You Use Resource Caching?
61//!
62//! Wrapping any pool using [`cache::Cache::new`] enables resource caching, which prevents excess
63//! resources from being created even when different parts of your code request compatible
64//! resources.
65//!
66//! **_NOTE:_** Graph submission will automatically attempt to re-order submitted commands to
67//! reduce contention between individual resources.
68//!
69//! **_NOTE:_** In cases where multiple cached resources using identical request information are
70//! used in the same graph command, ensure they come from different cache tags or different pool
71//! wrappers. Otherwise, two requests may resolve to the same underlying resource and trigger
72//! Vulkan validation warnings when reading from and writing to the same images.
73//!
74//! ### Pros:
75//!
76//! * Fewer resources are created overall
77//! * Wrapped pools behave like and retain all functionality of unwrapped pools
78//! * Easy to experiment with and benchmark in your existing code
79//!
80//! ### Cons:
81//!
82//! * Non-zero cost: atomic load and compatibility check per active cached resource
83//! * May cause GPU stalling if there is not enough work being submitted
84//! * Cached resources are typed `Arc<Lease<T>>` and are not guaranteed to be mutable or unique
85//!
86//! # Garbage Collection
87//!
88//! Wrapping a built-in pool using [`garbage_collector::GarbageCollector::new`] records successful
89//! resource requests. Calling [`garbage_collector::GarbageCollector::collect_resources`] retains
90//! only cached acceleration structures, buffers, and images that support requests made since the
91//! previous collection.
92
93pub mod cache;
94pub mod fifo;
95pub mod garbage_collector;
96pub mod hash;
97pub mod lazy;
98
99use {
100    crate::driver::{
101        DriverError,
102        accel_struct::{
103            AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder,
104        },
105        buffer::{Buffer, BufferInfo, BufferInfoBuilder},
106        descriptor_set::{DescriptorPool, DescriptorPoolInfo},
107        image::{Image, ImageInfo, ImageInfoBuilder},
108        render_pass::{RenderPass, RenderPassInfo},
109    },
110    derive_builder::{Builder, UninitializedFieldError},
111    std::{
112        fmt::Debug,
113        mem::ManuallyDrop,
114        ops::{Deref, DerefMut},
115        sync::{Arc, Weak},
116        thread::panicking,
117    },
118};
119
120#[derive(Clone, Copy)]
121enum BufferHostMappingCompatibility {
122    Exact,
123    Superset,
124}
125
126fn compatible_buffer_info(
127    item_info: &BufferInfo,
128    requested_info: &BufferInfo,
129    host_mapping: BufferHostMappingCompatibility,
130) -> bool {
131    (item_info.alloc_dedicated & requested_info.alloc_dedicated) == requested_info.alloc_dedicated
132        && compatible_buffer_host_mapping(item_info, requested_info, host_mapping)
133        && item_info.alignment >= requested_info.alignment
134        && item_info.sharing_mode == requested_info.sharing_mode
135        && item_info.size >= requested_info.size
136        && item_info.usage.contains(requested_info.usage)
137}
138
139fn compatible_buffer_host_mapping(
140    item_info: &BufferInfo,
141    requested_info: &BufferInfo,
142    compatibility: BufferHostMappingCompatibility,
143) -> bool {
144    match compatibility {
145        BufferHostMappingCompatibility::Exact => {
146            item_info.host_readable == requested_info.host_readable
147                && item_info.host_writable == requested_info.host_writable
148        }
149        BufferHostMappingCompatibility::Superset => {
150            (item_info.host_readable & requested_info.host_readable) == requested_info.host_readable
151                && (item_info.host_writable & requested_info.host_writable)
152                    == requested_info.host_writable
153        }
154    }
155}
156
157fn compatible_image_info(item_info: &ImageInfo, requested_info: &ImageInfo) -> bool {
158    item_info.array_layer_count == requested_info.array_layer_count
159        && item_info.alloc_dedicated == requested_info.alloc_dedicated
160        && item_info.depth == requested_info.depth
161        && item_info.format == requested_info.format
162        && item_info.height == requested_info.height
163        && item_info.host_readable == requested_info.host_readable
164        && item_info.host_writable == requested_info.host_writable
165        && item_info.mip_level_count == requested_info.mip_level_count
166        && item_info.sample_count == requested_info.sample_count
167        && item_info.sharing_mode == requested_info.sharing_mode
168        && item_info.tiling == requested_info.tiling
169        && item_info.image_type == requested_info.image_type
170        && item_info.width == requested_info.width
171        && item_info.flags.contains(requested_info.flags)
172        && item_info.usage.contains(requested_info.usage)
173}
174
175#[cfg(feature = "parking_lot")]
176use parking_lot::Mutex;
177
178#[cfg(not(feature = "parking_lot"))]
179use std::sync::Mutex;
180
181type Cache<T> = Arc<Mutex<Vec<T>>>;
182type CacheRef<T> = Weak<Mutex<Vec<T>>>;
183
184fn with_cache<T, R>(cache: &Cache<T>, f: impl FnOnce(&mut Vec<T>) -> R) -> R {
185    let cache = cache.lock();
186
187    #[cfg(not(feature = "parking_lot"))]
188    let cache = cache.expect("poisoned cache lock");
189
190    let mut cache = cache;
191
192    f(&mut cache)
193}
194
195/// Holds a pooled resource and implements `Drop` in order to return the resource.
196///
197/// This simple wrapper type implements only the `AsRef`, `AsMut`, `Deref` and `DerefMut` traits
198/// and provides no other functionality. A freshly obtained resource is guaranteed to have no other
199/// owners and may be mutably accessed.
200#[derive(Debug)]
201pub struct Lease<T> {
202    cache_ref: CacheRef<T>,
203    item: ManuallyDrop<T>,
204}
205
206/*
207The following debug_name functions take a self of Lease<T> and return Self.
208This allows pooled resources to have the same `.debug_name("bugs")` chaining.
209*/
210
211impl Lease<AccelerationStructure> {
212    /// Sets the debugging name assigned to this acceleration structure.
213    pub fn with_debug_name(self, name: impl AsRef<str>) -> Self {
214        self.set_debug_name(name);
215
216        self
217    }
218}
219
220impl Lease<Buffer> {
221    /// Sets the debugging name assigned to this buffer.
222    pub fn with_debug_name(self, name: impl AsRef<str>) -> Self {
223        self.set_debug_name(name);
224
225        self
226    }
227}
228
229impl Lease<Image> {
230    /// Sets the debugging name assigned to this image.
231    pub fn with_debug_name(self, name: impl AsRef<str>) -> Self {
232        self.set_debug_name(name);
233
234        self
235    }
236}
237
238impl<T> Lease<T> {
239    fn new(cache_ref: CacheRef<T>, item: T) -> Self {
240        Self {
241            cache_ref,
242            item: ManuallyDrop::new(item),
243        }
244    }
245}
246
247impl<T> AsRef<T> for Lease<T> {
248    fn as_ref(&self) -> &T {
249        self
250    }
251}
252
253impl<T> Deref for Lease<T> {
254    type Target = T;
255
256    fn deref(&self) -> &Self::Target {
257        &self.item
258    }
259}
260
261impl<T> DerefMut for Lease<T> {
262    fn deref_mut(&mut self) -> &mut Self::Target {
263        &mut self.item
264    }
265}
266
267impl<T> Drop for Lease<T> {
268    #[profiling::function]
269    fn drop(&mut self) {
270        if panicking() {
271            return;
272        }
273
274        // If the pool cache has been dropped we must manually drop the item, otherwise it goes back
275        // into the pool
276        if let Some(cache) = self.cache_ref.upgrade() {
277            with_cache(&cache, |cache| {
278                if cache.len() >= cache.capacity() {
279                    cache.pop();
280                }
281
282                cache.push(unsafe { ManuallyDrop::take(&mut self.item) });
283            });
284        } else {
285            unsafe {
286                ManuallyDrop::drop(&mut self.item);
287            }
288        }
289    }
290}
291
292/// Allows requesting resources using driver information structures.
293pub trait Pool<I, T> {
294    /// Request a resource.
295    fn resource(&mut self, info: I) -> Result<Lease<T>, DriverError>;
296}
297
298/// Pool capability required by graph submission scheduling.
299///
300/// This sealed trait is implemented by the built-in pools. It covers internal descriptor-pool and
301/// render-pass leases without exposing their cache-key types in public API bounds.
302#[allow(private_bounds)]
303pub trait SubmissionPool: submission_pool_private::SubmissionPoolSealed {}
304
305impl<T> SubmissionPool for T where T: submission_pool_private::SubmissionPoolSealed {}
306
307pub(crate) mod submission_pool_private {
308    use super::*;
309
310    pub(crate) trait SubmissionPoolSealed {
311        fn descriptor_pool(
312            &mut self,
313            info: DescriptorPoolInfo,
314        ) -> Result<Lease<DescriptorPool>, DriverError>;
315
316        fn render_pass(&mut self, info: RenderPassInfo) -> Result<Lease<RenderPass>, DriverError>;
317    }
318
319    impl<T> SubmissionPoolSealed for T
320    where
321        T: Pool<DescriptorPoolInfo, DescriptorPool> + Pool<RenderPassInfo, RenderPass>,
322    {
323        fn descriptor_pool(
324            &mut self,
325            info: DescriptorPoolInfo,
326        ) -> Result<Lease<DescriptorPool>, DriverError> {
327            self.resource(info)
328        }
329
330        fn render_pass(&mut self, info: RenderPassInfo) -> Result<Lease<RenderPass>, DriverError> {
331            self.resource(info)
332        }
333    }
334}
335
336// Enable requesting items using their info builder type for convenience
337macro_rules! lease_builder {
338    ($info:ident => $item:ident) => {
339        paste::paste! {
340            impl<T> Pool<[<$info Builder>], $item> for T where T: Pool<$info, $item> {
341                fn resource(
342                    &mut self,
343                    builder: [<$info Builder>],
344                ) -> Result<Lease<$item>, DriverError> {
345                    let info = builder.build();
346
347                    self.resource(info)
348                }
349            }
350        }
351    };
352}
353
354lease_builder!(AccelerationStructureInfo => AccelerationStructure);
355lease_builder!(BufferInfo => Buffer);
356lease_builder!(ImageInfo => Image);
357
358/// Information used to create a [`FifoPool`](self::fifo::FifoPool),
359/// [`HashPool`](self::hash::HashPool) or [`LazyPool`](self::lazy::LazyPool) instance.
360#[derive(Builder, Clone, Copy, Debug, Eq, PartialEq)]
361#[builder(
362    build_fn(private, name = "fallible_build", error = "UninitializedFieldError"),
363    derive(Clone, Copy, Debug),
364    pattern = "owned"
365)]
366pub struct PoolConfig {
367    /// The maximum size of a single bucket of acceleration structure resource instances. The
368    /// default value is [`PoolConfig::DEFAULT_RESOURCE_CAPACITY`].
369    ///
370    /// # Note
371    ///
372    /// Individual [`Pool`] implementations store varying numbers of buckets. Read the
373    /// documentation of each implementation to understand how this affects total number of
374    /// stored acceleration structure instances.
375    #[builder(
376        default = "PoolConfig::DEFAULT_RESOURCE_CAPACITY",
377        setter(strip_option)
378    )]
379    pub accel_struct_capacity: usize,
380
381    /// The maximum size of a single bucket of buffer resource instances. The default value is
382    /// [`PoolConfig::DEFAULT_RESOURCE_CAPACITY`].
383    ///
384    /// # Note
385    ///
386    /// Individual [`Pool`] implementations store varying numbers of buckets. Read the
387    /// documentation of each implementation to understand how this affects total number of
388    /// stored buffer instances.
389    #[builder(
390        default = "PoolConfig::DEFAULT_RESOURCE_CAPACITY",
391        setter(strip_option)
392    )]
393    pub buffer_capacity: usize,
394
395    /// The maximum size of a single bucket of image resource instances. The default value is
396    /// [`PoolConfig::DEFAULT_RESOURCE_CAPACITY`].
397    ///
398    /// # Note
399    ///
400    /// Individual [`Pool`] implementations store varying numbers of buckets. Read the
401    /// documentation of each implementation to understand how this affects total number of
402    /// stored image instances.
403    #[builder(
404        default = "PoolConfig::DEFAULT_RESOURCE_CAPACITY",
405        setter(strip_option)
406    )]
407    pub image_capacity: usize,
408}
409
410impl PoolConfig {
411    /// The maximum size of a single bucket of resource instances.
412    pub const DEFAULT_RESOURCE_CAPACITY: usize = 16;
413
414    /// Creates a default `PoolConfigBuilder`.
415    pub fn builder() -> PoolConfigBuilder {
416        Default::default()
417    }
418
419    fn default_cache<T>() -> Cache<T> {
420        Cache::new(Mutex::new(Vec::with_capacity(
421            Self::DEFAULT_RESOURCE_CAPACITY,
422        )))
423    }
424
425    fn explicit_cache<T>(capacity: usize) -> Cache<T> {
426        Cache::new(Mutex::new(Vec::with_capacity(capacity)))
427    }
428
429    /// Converts a `PoolConfig` into a `PoolConfigBuilder`.
430    pub fn into_builder(self) -> PoolConfigBuilder {
431        PoolConfigBuilder {
432            accel_struct_capacity: Some(self.accel_struct_capacity),
433            buffer_capacity: Some(self.buffer_capacity),
434            image_capacity: Some(self.image_capacity),
435        }
436    }
437
438    /// Constructs a new `PoolConfig` with the given acceleration structure, buffer and image
439    /// resource capacity for any single bucket.
440    pub const fn with_capacity(resource_capacity: usize) -> Self {
441        Self {
442            accel_struct_capacity: resource_capacity,
443            buffer_capacity: resource_capacity,
444            image_capacity: resource_capacity,
445        }
446    }
447}
448
449impl Default for PoolConfig {
450    fn default() -> Self {
451        PoolConfigBuilder::default().into()
452    }
453}
454
455impl From<PoolConfigBuilder> for PoolConfig {
456    fn from(info: PoolConfigBuilder) -> Self {
457        info.build()
458    }
459}
460
461impl From<usize> for PoolConfig {
462    fn from(value: usize) -> Self {
463        Self {
464            accel_struct_capacity: value,
465            buffer_capacity: value,
466            image_capacity: value,
467        }
468    }
469}
470
471// HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56
472impl PoolConfigBuilder {
473    /// Builds a new `PoolConfig`.
474    pub fn build(self) -> PoolConfig {
475        self.fallible_build().expect("invalid pool config")
476    }
477}
478
479#[cfg(test)]
480mod test {
481    use super::*;
482    use crate::driver::ash::vk;
483
484    type Info = PoolConfig;
485    type Builder = PoolConfigBuilder;
486
487    #[test]
488    pub fn pool_info() {
489        let info = Info::default();
490        let builder = info.into_builder().build();
491
492        assert_eq!(info, builder);
493    }
494
495    #[test]
496    pub fn pool_info_builder() {
497        let info = Info {
498            accel_struct_capacity: 1,
499            buffer_capacity: 2,
500            image_capacity: 3,
501        };
502        let builder = Builder::default()
503            .accel_struct_capacity(1)
504            .buffer_capacity(2)
505            .image_capacity(3)
506            .build();
507
508        assert_eq!(info, builder);
509    }
510
511    #[test]
512    fn buffer_info_compatibility_rejects_different_sharing_mode() {
513        let exclusive = BufferInfo::device_mem(64, vk::BufferUsageFlags::STORAGE_BUFFER);
514        let concurrent = BufferInfo {
515            sharing_mode: vk::SharingMode::CONCURRENT,
516            ..exclusive
517        };
518
519        assert!(!compatible_buffer_info(
520            &exclusive,
521            &concurrent,
522            BufferHostMappingCompatibility::Exact,
523        ));
524        assert!(!compatible_buffer_info(
525            &exclusive,
526            &concurrent,
527            BufferHostMappingCompatibility::Superset,
528        ));
529    }
530
531    #[test]
532    fn image_info_compatibility_rejects_different_sharing_mode() {
533        let exclusive = ImageInfo::image_2d(
534            16,
535            16,
536            vk::Format::R8G8B8A8_UNORM,
537            vk::ImageUsageFlags::STORAGE,
538        );
539        let concurrent = ImageInfo {
540            sharing_mode: vk::SharingMode::CONCURRENT,
541            ..exclusive
542        };
543
544        assert!(!compatible_image_info(&exclusive, &concurrent));
545    }
546}