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}