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::new(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
86pub mod cache;
87pub mod fifo;
88pub mod hash;
89pub mod lazy;
90
91use {
92    crate::driver::{
93        DriverError,
94        accel_struct::{
95            AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder,
96        },
97        buffer::{Buffer, BufferInfo, BufferInfoBuilder},
98        cmd_buf::CommandBuffer,
99        image::{Image, ImageInfo, ImageInfoBuilder},
100    },
101    derive_builder::{Builder, UninitializedFieldError},
102    std::{
103        fmt::Debug,
104        mem::ManuallyDrop,
105        ops::{Deref, DerefMut},
106        sync::{Arc, Weak},
107        thread::panicking,
108    },
109};
110
111#[cfg(feature = "parking_lot")]
112use parking_lot::Mutex;
113
114#[cfg(not(feature = "parking_lot"))]
115use std::sync::Mutex;
116
117type Cache<T> = Arc<Mutex<Vec<T>>>;
118type CacheRef<T> = Weak<Mutex<Vec<T>>>;
119
120fn lease_command_buffer(cache: &mut Vec<CommandBuffer>) -> Option<CommandBuffer> {
121    for idx in 0..cache.len() {
122        if unsafe {
123            let cmd = cache.get_unchecked(idx);
124
125            // Don't lease this command buffer if it is unsignalled; we'll create a new one
126            // and wait for this, and those behind it, to signal.
127            cmd.device.get_fence_status(cmd.fence).unwrap_or_default()
128        } {
129            return Some(cache.swap_remove(idx));
130        }
131    }
132
133    None
134}
135
136fn with_cache<T, R>(cache: &Cache<T>, f: impl FnOnce(&mut Vec<T>) -> R) -> R {
137    let cache = cache.lock();
138
139    #[cfg(not(feature = "parking_lot"))]
140    let cache = cache.expect("poisoned cache lock");
141
142    let mut cache = cache;
143
144    f(&mut cache)
145}
146
147/// Holds a pooled resource and implements `Drop` in order to return the resource.
148///
149/// This simple wrapper type implements only the `AsRef`, `AsMut`, `Deref` and `DerefMut` traits
150/// and provides no other functionality. A freshly obtained resource is guaranteed to have no other
151/// owners and may be mutably accessed.
152#[derive(Debug)]
153pub struct Lease<T> {
154    cache_ref: CacheRef<T>,
155    item: ManuallyDrop<T>,
156}
157
158// The following debug_name functions take a self of Lease<T> and return Self.
159// This allows pooled resources to have the same `.debug_name("bugs")` chaining
160
161impl Lease<AccelerationStructure> {
162    /// Sets the debugging name assigned to this acceleration structure.
163    pub fn debug_name(mut self, name: impl Into<String>) -> Self {
164        self.name = Some(name.into());
165
166        self
167    }
168}
169
170impl Lease<Buffer> {
171    /// Sets the debugging name assigned to this buffer.
172    pub fn debug_name(mut self, name: impl Into<String>) -> Self {
173        self.name = Some(name.into());
174
175        self
176    }
177}
178
179impl Lease<Image> {
180    /// Sets the debugging name assigned to this image.
181    pub fn debug_name(mut self, name: impl Into<String>) -> Self {
182        self.name = Some(name.into());
183
184        self
185    }
186}
187
188impl<T> Lease<T> {
189    fn new(cache_ref: CacheRef<T>, item: T) -> Self {
190        Self {
191            cache_ref,
192            item: ManuallyDrop::new(item),
193        }
194    }
195}
196
197impl<T> Deref for Lease<T> {
198    type Target = T;
199
200    fn deref(&self) -> &Self::Target {
201        &self.item
202    }
203}
204
205impl<T> DerefMut for Lease<T> {
206    fn deref_mut(&mut self) -> &mut Self::Target {
207        &mut self.item
208    }
209}
210
211impl<T> Drop for Lease<T> {
212    #[profiling::function]
213    fn drop(&mut self) {
214        if panicking() {
215            return;
216        }
217
218        // If the pool cache has been dropped we must manually drop the item, otherwise it goes back
219        // into the pool.
220        if let Some(cache) = self.cache_ref.upgrade() {
221            with_cache(&cache, |cache| {
222                if cache.len() >= cache.capacity() {
223                    cache.pop();
224                }
225
226                cache.push(unsafe { ManuallyDrop::take(&mut self.item) });
227            });
228        } else {
229            unsafe {
230                ManuallyDrop::drop(&mut self.item);
231            }
232        }
233    }
234}
235
236/// Allows requesting resources using driver information structures.
237pub trait Pool<I, T> {
238    #[deprecated = "use resource function"]
239    #[doc(hidden)]
240    fn lease(&mut self, info: I) -> Result<Lease<T>, DriverError> {
241        self.resource(info)
242    }
243
244    /// Request a resource.
245    fn resource(&mut self, info: I) -> Result<Lease<T>, DriverError>;
246}
247
248// Enable requesting items using their info builder type for convenience
249macro_rules! lease_builder {
250    ($info:ident => $item:ident) => {
251        paste::paste! {
252            impl<T> Pool<[<$info Builder>], $item> for T where T: Pool<$info, $item> {
253                fn resource(
254                    &mut self,
255                    builder: [<$info Builder>],
256                ) -> Result<Lease<$item>, DriverError> {
257                    let info = builder.build();
258
259                    self.resource(info)
260                }
261            }
262        }
263    };
264}
265
266lease_builder!(AccelerationStructureInfo => AccelerationStructure);
267lease_builder!(BufferInfo => Buffer);
268lease_builder!(ImageInfo => Image);
269
270/// Information used to create a [`FifoPool`](self::fifo::FifoPool),
271/// [`HashPool`](self::hash::HashPool) or [`LazyPool`](self::lazy::LazyPool) instance.
272#[derive(Builder, Clone, Copy, Debug, Eq, PartialEq)]
273#[builder(
274    build_fn(private, name = "fallible_build", error = "UninitializedFieldError"),
275    derive(Clone, Copy, Debug),
276    pattern = "owned"
277)]
278pub struct PoolConfig {
279    /// The maximum size of a single bucket of acceleration structure resource instances. The
280    /// default value is [`PoolConfig::DEFAULT_RESOURCE_CAPACITY`].
281    ///
282    /// # Note
283    ///
284    /// Individual [`Pool`] implementations store varying numbers of buckets. Read the
285    /// documentation of each implementation to understand how this affects total number of
286    /// stored acceleration structure instances.
287    #[builder(
288        default = "PoolConfig::DEFAULT_RESOURCE_CAPACITY",
289        setter(strip_option)
290    )]
291    pub accel_struct_capacity: usize,
292
293    /// The maximum size of a single bucket of buffer resource instances. The default value is
294    /// [`PoolConfig::DEFAULT_RESOURCE_CAPACITY`].
295    ///
296    /// # Note
297    ///
298    /// Individual [`Pool`] implementations store varying numbers of buckets. Read the
299    /// documentation of each implementation to understand how this affects total number of
300    /// stored buffer instances.
301    #[builder(
302        default = "PoolConfig::DEFAULT_RESOURCE_CAPACITY",
303        setter(strip_option)
304    )]
305    pub buffer_capacity: usize,
306
307    /// The maximum size of a single bucket of image resource instances. The default value is
308    /// [`PoolConfig::DEFAULT_RESOURCE_CAPACITY`].
309    ///
310    /// # Note
311    ///
312    /// Individual [`Pool`] implementations store varying numbers of buckets. Read the
313    /// documentation of each implementation to understand how this affects total number of
314    /// stored image instances.
315    #[builder(
316        default = "PoolConfig::DEFAULT_RESOURCE_CAPACITY",
317        setter(strip_option)
318    )]
319    pub image_capacity: usize,
320}
321
322impl PoolConfig {
323    /// The maximum size of a single bucket of resource instances.
324    pub const DEFAULT_RESOURCE_CAPACITY: usize = 16;
325
326    /// Creates a default `PoolConfigBuilder`.
327    pub fn builder() -> PoolConfigBuilder {
328        Default::default()
329    }
330
331    fn default_cache<T>() -> Cache<T> {
332        Cache::new(Mutex::new(Vec::with_capacity(
333            Self::DEFAULT_RESOURCE_CAPACITY,
334        )))
335    }
336
337    fn explicit_cache<T>(capacity: usize) -> Cache<T> {
338        Cache::new(Mutex::new(Vec::with_capacity(capacity)))
339    }
340
341    /// Converts a `PoolConfig` into a `PoolConfigBuilder`.
342    pub fn into_builder(self) -> PoolConfigBuilder {
343        PoolConfigBuilder {
344            accel_struct_capacity: Some(self.accel_struct_capacity),
345            buffer_capacity: Some(self.buffer_capacity),
346            image_capacity: Some(self.image_capacity),
347        }
348    }
349
350    #[deprecated = "use into_builder function"]
351    #[doc(hidden)]
352    pub fn to_builder(self) -> PoolConfigBuilder {
353        self.into_builder()
354    }
355
356    /// Constructs a new `PoolConfig` with the given acceleration structure, buffer and image
357    /// resource capacity for any single bucket.
358    pub const fn with_capacity(resource_capacity: usize) -> Self {
359        Self {
360            accel_struct_capacity: resource_capacity,
361            buffer_capacity: resource_capacity,
362            image_capacity: resource_capacity,
363        }
364    }
365}
366
367impl Default for PoolConfig {
368    fn default() -> Self {
369        PoolConfigBuilder::default().into()
370    }
371}
372
373impl From<PoolConfigBuilder> for PoolConfig {
374    fn from(info: PoolConfigBuilder) -> Self {
375        info.build()
376    }
377}
378
379impl From<usize> for PoolConfig {
380    fn from(value: usize) -> Self {
381        Self {
382            accel_struct_capacity: value,
383            buffer_capacity: value,
384            image_capacity: value,
385        }
386    }
387}
388
389// HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56
390impl PoolConfigBuilder {
391    /// Builds a new `PoolConfig`.
392    pub fn build(self) -> PoolConfig {
393        self.fallible_build().expect("invalid pool config")
394    }
395}
396
397#[doc(hidden)]
398#[deprecated = "use PoolConfig instead"]
399pub type PoolInfo = PoolConfig;
400
401#[doc(hidden)]
402#[deprecated = "use PoolConfigBuilder instead"]
403pub type PoolInfoBuilder = PoolConfigBuilder;
404
405mod deprecated {
406    use {
407        crate::pool::Lease,
408        std::convert::{AsMut, AsRef},
409    };
410
411    impl<T> Lease<T> {
412        #[allow(clippy::should_implement_trait)]
413        #[deprecated = "use Deref impl"]
414        #[doc(hidden)]
415        pub fn as_ref(&self) -> &T {
416            &self.item
417        }
418
419        #[allow(clippy::should_implement_trait)]
420        #[deprecated = "use DerefMut impl"]
421        #[doc(hidden)]
422        pub fn as_mut(&mut self) -> &mut T {
423            &mut self.item
424        }
425    }
426
427    impl<T> AsRef<T> for Lease<T> {
428        fn as_ref(&self) -> &T {
429            &self.item
430        }
431    }
432
433    impl<T> AsMut<T> for Lease<T> {
434        fn as_mut(&mut self) -> &mut T {
435            &mut self.item
436        }
437    }
438}
439
440#[cfg(test)]
441mod test {
442    use super::*;
443
444    type Info = PoolConfig;
445    type Builder = PoolConfigBuilder;
446
447    #[test]
448    pub fn pool_info() {
449        let info = Info::default();
450        let builder = info.into_builder().build();
451
452        assert_eq!(info, builder);
453    }
454
455    #[test]
456    pub fn pool_info_builder() {
457        let info = Info {
458            accel_struct_capacity: 1,
459            buffer_capacity: 2,
460            image_capacity: 3,
461        };
462        let builder = Builder::default()
463            .accel_struct_capacity(1)
464            .buffer_capacity(2)
465            .image_capacity(3)
466            .build();
467
468        assert_eq!(info, builder);
469    }
470}