screen_13/pool/mod.rs
1//! Resource leasing and pooling types.
2//!
3//! _Screen 13_ provides caching for acceleration structure, buffer and image resources which may be
4//! leased from configurable pools using their corresponding information structure. Most programs
5//! will do fine with a single [`FifoPool`](self::fifo::FifoPool).
6//!
7//! Leased resources may be bound directly to a render graph and used in the same manner as regular
8//! resources. After rendering has finished, the leased resources will return to the 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//! _Screen 13_'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 screen_13::driver::DriverError;
29//! # use screen_13::driver::device::{Device, DeviceInfo};
30//! # use screen_13::driver::image::{ImageInfo};
31//! # use screen_13::pool::{Pool};
32//! # use screen_13::pool::lazy::{LazyPool};
33//! # fn main() -> Result<(), DriverError> {
34//! # let device = Arc::new(Device::create_headless(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.lease(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 Aliasing?
61//!
62//! Wrapping any pool using [`AliasPool::new`](self::alias::AliasPool::new) enables resource
63//! aliasing, which prevents excess resources from being created even when different parts of your
64//! code request new resources.
65//!
66//! **_NOTE:_** Render graph submission will automatically attempt to re-order submitted passes to
67//! reduce contention between individual resources.
68//!
69//! **_NOTE:_** In cases where multiple aliased resources using identical request information are
70//! used in the same render graph pass you must ensure the resources are aliased from different
71//! pools. There is currently no tagging or filter which would prevent "ping-pong" rendering of such
72//! resources from being the same actual resources; this causes Vulkan validation warnings when
73//! reading from and writing to the same images, or whatever your operations may be.
74//!
75//! ### Pros:
76//!
77//! * Fewer resources are created overall
78//! * Wrapped pools behave like and retain all functionality of unwrapped pools
79//! * Easy to experiment with and benchmark in your existing code
80//!
81//! ### Cons:
82//!
83//! * Non-zero cost: Atomic load and compatibility check per active alias
84//! * May cause GPU stalling if there is not enough work being submitted
85//! * Aliased resources are typed `Arc<Lease<T>>` and are not guaranteed to be mutable or unique
86
87pub mod alias;
88pub mod fifo;
89pub mod hash;
90pub mod lazy;
91
92use {
93 crate::driver::{
94 CommandBuffer, DriverError,
95 accel_struct::{
96 AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder,
97 },
98 buffer::{Buffer, BufferInfo, BufferInfoBuilder},
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_buf = 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_buf
128 .device
129 .get_fence_status(cmd_buf.fence)
130 .unwrap_or_default()
131 } {
132 return Some(cache.swap_remove(idx));
133 }
134 }
135
136 None
137}
138
139/// Holds a leased resource and implements `Drop` in order to return the resource.
140///
141/// This simple wrapper type implements only the `AsRef`, `AsMut`, `Deref` and `DerefMut` traits
142/// and provides no other functionality. A freshly leased resource is guaranteed to have no other
143/// owners and may be mutably accessed.
144#[derive(Debug)]
145pub struct Lease<T> {
146 cache_ref: CacheRef<T>,
147 item: ManuallyDrop<T>,
148}
149
150impl<T> Lease<T> {
151 #[inline(always)]
152 fn new(cache_ref: CacheRef<T>, item: T) -> Self {
153 Self {
154 cache_ref,
155 item: ManuallyDrop::new(item),
156 }
157 }
158}
159
160impl<T> AsRef<T> for Lease<T> {
161 fn as_ref(&self) -> &T {
162 &self.item
163 }
164}
165
166impl<T> AsMut<T> for Lease<T> {
167 fn as_mut(&mut self) -> &mut T {
168 &mut self.item
169 }
170}
171
172impl<T> Deref for Lease<T> {
173 type Target = T;
174
175 fn deref(&self) -> &Self::Target {
176 &self.item
177 }
178}
179
180impl<T> DerefMut for Lease<T> {
181 fn deref_mut(&mut self) -> &mut Self::Target {
182 &mut self.item
183 }
184}
185
186impl<T> Drop for Lease<T> {
187 #[profiling::function]
188 fn drop(&mut self) {
189 if panicking() {
190 return;
191 }
192
193 // If the pool cache has been dropped we must manually drop the item, otherwise it goes back
194 // into the pool.
195 if let Some(cache) = self.cache_ref.upgrade() {
196 #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
197 let mut cache = cache.lock();
198
199 #[cfg(not(feature = "parking_lot"))]
200 let mut cache = cache.unwrap();
201
202 if cache.len() == cache.capacity() {
203 cache.pop();
204 }
205
206 cache.push(unsafe { ManuallyDrop::take(&mut self.item) });
207 } else {
208 unsafe {
209 ManuallyDrop::drop(&mut self.item);
210 }
211 }
212 }
213}
214
215/// Allows leasing of resources using driver information structures.
216pub trait Pool<I, T> {
217 /// Lease a resource.
218 fn lease(&mut self, info: I) -> Result<Lease<T>, DriverError>;
219}
220
221// Enable leasing items using their info builder type for convenience
222macro_rules! lease_builder {
223 ($info:ident => $item:ident) => {
224 paste::paste! {
225 impl<T> Pool<[<$info Builder>], $item> for T where T: Pool<$info, $item> {
226 fn lease(&mut self, builder: [<$info Builder>]) -> Result<Lease<$item>, DriverError> {
227 let info = builder.build();
228
229 self.lease(info)
230 }
231 }
232 }
233 };
234}
235
236lease_builder!(AccelerationStructureInfo => AccelerationStructure);
237lease_builder!(BufferInfo => Buffer);
238lease_builder!(ImageInfo => Image);
239
240/// Information used to create a [`FifoPool`](self::fifo::FifoPool),
241/// [`HashPool`](self::hash::HashPool) or [`LazyPool`](self::lazy::LazyPool) instance.
242#[derive(Builder, Clone, Copy, Debug)]
243#[builder(
244 build_fn(private, name = "fallible_build", error = "PoolInfoBuilderError"),
245 derive(Clone, Copy, Debug),
246 pattern = "owned"
247)]
248#[non_exhaustive]
249pub struct PoolInfo {
250 /// The maximum size of a single bucket of acceleration structure resource instances. The
251 /// default value is [`PoolInfo::DEFAULT_RESOURCE_CAPACITY`].
252 ///
253 /// # Note
254 ///
255 /// Individual [`Pool`] implementations store varying numbers of buckets. Read the documentation
256 /// of each implementation to understand how this affects total number of stored acceleration
257 /// structure instances.
258 #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))]
259 pub accel_struct_capacity: usize,
260
261 /// The maximum size of a single bucket of buffer resource instances. The default value is
262 /// [`PoolInfo::DEFAULT_RESOURCE_CAPACITY`].
263 ///
264 /// # Note
265 ///
266 /// Individual [`Pool`] implementations store varying numbers of buckets. Read the documentation
267 /// of each implementation to understand how this affects total number of stored buffer
268 /// instances.
269 #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))]
270 pub buffer_capacity: usize,
271
272 /// The maximum size of a single bucket of image resource instances. The default value is
273 /// [`PoolInfo::DEFAULT_RESOURCE_CAPACITY`].
274 ///
275 /// # Note
276 ///
277 /// Individual [`Pool`] implementations store varying numbers of buckets. Read the documentation
278 /// of each implementation to understand how this affects total number of stored image
279 /// instances.
280 #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))]
281 pub image_capacity: usize,
282}
283
284impl PoolInfo {
285 /// The maximum size of a single bucket of resource instances.
286 pub const DEFAULT_RESOURCE_CAPACITY: usize = 16;
287
288 /// Constructs a new `PoolInfo` with the given acceleration structure, buffer and image resource
289 /// capacity for any single bucket.
290 pub const fn with_capacity(resource_capacity: usize) -> Self {
291 Self {
292 accel_struct_capacity: resource_capacity,
293 buffer_capacity: resource_capacity,
294 image_capacity: resource_capacity,
295 }
296 }
297
298 fn default_cache<T>() -> Cache<T> {
299 Cache::new(Mutex::new(Vec::with_capacity(
300 Self::DEFAULT_RESOURCE_CAPACITY,
301 )))
302 }
303
304 fn explicit_cache<T>(capacity: usize) -> Cache<T> {
305 Cache::new(Mutex::new(Vec::with_capacity(capacity)))
306 }
307}
308
309impl Default for PoolInfo {
310 fn default() -> Self {
311 PoolInfoBuilder::default().into()
312 }
313}
314
315impl From<PoolInfoBuilder> for PoolInfo {
316 fn from(info: PoolInfoBuilder) -> Self {
317 info.build()
318 }
319}
320
321impl From<usize> for PoolInfo {
322 fn from(value: usize) -> Self {
323 Self {
324 accel_struct_capacity: value,
325 buffer_capacity: value,
326 image_capacity: value,
327 }
328 }
329}
330
331// HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56
332impl PoolInfoBuilder {
333 /// Builds a new `PoolInfo`.
334 pub fn build(self) -> PoolInfo {
335 self.fallible_build()
336 .expect("All required fields set at initialization")
337 }
338}
339
340#[derive(Debug)]
341struct PoolInfoBuilderError;
342
343impl From<UninitializedFieldError> for PoolInfoBuilderError {
344 fn from(_: UninitializedFieldError) -> Self {
345 Self
346 }
347}