screen_13/pool/
lazy.rs

1//! Pool which leases by looking for compatibile information before creating new resources.
2
3use {
4    super::{Cache, Lease, Pool, PoolInfo, lease_command_buffer},
5    crate::driver::{
6        CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError,
7        RenderPass, RenderPassInfo,
8        accel_struct::{AccelerationStructure, AccelerationStructureInfo},
9        buffer::{Buffer, BufferInfo},
10        device::Device,
11        image::{Image, ImageInfo, SampleCount},
12    },
13    ash::vk,
14    log::debug,
15    std::{collections::HashMap, sync::Arc},
16};
17
18#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
19struct ImageKey {
20    array_layer_count: u32,
21    depth: u32,
22    fmt: vk::Format,
23    height: u32,
24    mip_level_count: u32,
25    sample_count: SampleCount,
26    tiling: vk::ImageTiling,
27    ty: vk::ImageType,
28    width: u32,
29}
30
31impl From<ImageInfo> for ImageKey {
32    fn from(info: ImageInfo) -> Self {
33        Self {
34            array_layer_count: info.array_layer_count,
35            depth: info.depth,
36            fmt: info.fmt,
37            height: info.height,
38            mip_level_count: info.mip_level_count,
39            sample_count: info.sample_count,
40            tiling: info.tiling,
41            ty: info.ty,
42            width: info.width,
43        }
44    }
45}
46
47/// A balanced resource allocator.
48///
49/// The information for each lease request is compared against the stored resources for
50/// compatibility. If no acceptable resources are stored for the information provided a new resource
51/// is created and returned.
52///
53/// # Details
54///
55/// * Acceleration structures may be larger than requested
56/// * Buffers may be larger than requested or have additional usage flags
57/// * Images may have additional usage flags
58///
59/// # Bucket Strategy
60///
61/// The information for each lease request is the key for a `HashMap` of buckets. If no bucket
62/// exists with compatible information a new bucket is created.
63///
64/// In practice this means that for a [`PoolInfo::image_capacity`] of `4`, requests for a 1024x1024
65/// image with certain attributes will store a maximum of `4` such images. Requests for any image
66/// having a different size or incompatible attributes will store an additional maximum of `4`
67/// images.
68///
69/// # Memory Management
70///
71/// If requests for varying resources is common [`LazyPool::clear_images_by_info`] and other memory
72/// management functions are nessecery in order to avoid using all available device memory.
73#[derive(Debug)]
74pub struct LazyPool {
75    accel_struct_cache: HashMap<vk::AccelerationStructureTypeKHR, Cache<AccelerationStructure>>,
76    buffer_cache: HashMap<(bool, vk::DeviceSize), Cache<Buffer>>,
77    command_buffer_cache: HashMap<u32, Cache<CommandBuffer>>,
78    descriptor_pool_cache: Cache<DescriptorPool>,
79    device: Arc<Device>,
80    image_cache: HashMap<ImageKey, Cache<Image>>,
81    info: PoolInfo,
82    render_pass_cache: HashMap<RenderPassInfo, Cache<RenderPass>>,
83}
84
85impl LazyPool {
86    /// Constructs a new `LazyPool`.
87    pub fn new(device: &Arc<Device>) -> Self {
88        Self::with_capacity(device, PoolInfo::default())
89    }
90
91    /// Constructs a new `LazyPool` with the given capacity information.
92    pub fn with_capacity(device: &Arc<Device>, info: impl Into<PoolInfo>) -> Self {
93        let info: PoolInfo = info.into();
94        let device = Arc::clone(device);
95
96        Self {
97            accel_struct_cache: Default::default(),
98            buffer_cache: Default::default(),
99            command_buffer_cache: Default::default(),
100            descriptor_pool_cache: PoolInfo::default_cache(),
101            device,
102            image_cache: Default::default(),
103            info,
104            render_pass_cache: Default::default(),
105        }
106    }
107
108    /// Clears the pool, removing all resources.
109    pub fn clear(&mut self) {
110        self.clear_accel_structs();
111        self.clear_buffers();
112        self.clear_images();
113    }
114
115    /// Clears the pool of acceleration structure resources.
116    pub fn clear_accel_structs(&mut self) {
117        self.accel_struct_cache.clear();
118    }
119
120    /// Clears the pool of all acceleration structure resources matching the given type.
121    pub fn clear_accel_structs_by_ty(&mut self, ty: vk::AccelerationStructureTypeKHR) {
122        self.accel_struct_cache.remove(&ty);
123    }
124
125    /// Clears the pool of buffer resources.
126    pub fn clear_buffers(&mut self) {
127        self.buffer_cache.clear();
128    }
129
130    /// Clears the pool of image resources.
131    pub fn clear_images(&mut self) {
132        self.image_cache.clear();
133    }
134
135    /// Clears the pool of image resources matching the given information.
136    pub fn clear_images_by_info(&mut self, info: impl Into<ImageInfo>) {
137        self.image_cache.remove(&info.into().into());
138    }
139
140    /// Retains only the acceleration structure resources specified by the predicate.
141    ///
142    /// In other words, remove all resources for which `f(vk::AccelerationStructureTypeKHR)` returns
143    /// `false`.
144    ///
145    /// The elements are visited in unsorted (and unspecified) order.
146    ///
147    /// # Performance
148    ///
149    /// Provides the same performance guarantees as
150    /// [`HashMap::retain`](HashMap::retain).
151    pub fn retain_accel_structs<F>(&mut self, mut f: F)
152    where
153        F: FnMut(vk::AccelerationStructureTypeKHR) -> bool,
154    {
155        self.accel_struct_cache.retain(|&ty, _| f(ty))
156    }
157}
158
159impl Pool<AccelerationStructureInfo, AccelerationStructure> for LazyPool {
160    #[profiling::function]
161    fn lease(
162        &mut self,
163        info: AccelerationStructureInfo,
164    ) -> Result<Lease<AccelerationStructure>, DriverError> {
165        let cache = self
166            .accel_struct_cache
167            .entry(info.ty)
168            .or_insert_with(|| PoolInfo::explicit_cache(self.info.accel_struct_capacity));
169        let cache_ref = Arc::downgrade(cache);
170
171        {
172            profiling::scope!("check cache");
173
174            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
175            let mut cache = cache.lock();
176
177            #[cfg(not(feature = "parking_lot"))]
178            let mut cache = cache.unwrap();
179
180            // Look for a compatible acceleration structure (big enough)
181            for idx in 0..cache.len() {
182                let item = unsafe { cache.get_unchecked(idx) };
183                if item.info.size >= info.size {
184                    let item = cache.swap_remove(idx);
185
186                    return Ok(Lease::new(cache_ref, item));
187                }
188            }
189        }
190
191        debug!("Creating new {}", stringify!(AccelerationStructure));
192
193        let item = AccelerationStructure::create(&self.device, info)?;
194
195        Ok(Lease::new(cache_ref, item))
196    }
197}
198
199impl Pool<BufferInfo, Buffer> for LazyPool {
200    #[profiling::function]
201    fn lease(&mut self, info: BufferInfo) -> Result<Lease<Buffer>, DriverError> {
202        let cache = self
203            .buffer_cache
204            .entry((info.mappable, info.alignment))
205            .or_insert_with(|| PoolInfo::explicit_cache(self.info.buffer_capacity));
206        let cache_ref = Arc::downgrade(cache);
207
208        {
209            profiling::scope!("check cache");
210
211            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
212            let mut cache = cache.lock();
213
214            #[cfg(not(feature = "parking_lot"))]
215            let mut cache = cache.unwrap();
216
217            // Look for a compatible buffer (big enough and superset of usage flags)
218            for idx in 0..cache.len() {
219                let item = unsafe { cache.get_unchecked(idx) };
220                if item.info.size >= info.size && item.info.usage.contains(info.usage) {
221                    let item = cache.swap_remove(idx);
222
223                    return Ok(Lease::new(cache_ref, item));
224                }
225            }
226        }
227
228        debug!("Creating new {}", stringify!(Buffer));
229
230        let item = Buffer::create(&self.device, info)?;
231
232        Ok(Lease::new(cache_ref, item))
233    }
234}
235
236impl Pool<CommandBufferInfo, CommandBuffer> for LazyPool {
237    #[profiling::function]
238    fn lease(&mut self, info: CommandBufferInfo) -> Result<Lease<CommandBuffer>, DriverError> {
239        let cache_ref = self
240            .command_buffer_cache
241            .entry(info.queue_family_index)
242            .or_insert_with(PoolInfo::default_cache);
243        let mut item = {
244            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
245            let mut cache = cache_ref.lock();
246
247            #[cfg(not(feature = "parking_lot"))]
248            let mut cache = cache.unwrap();
249
250            lease_command_buffer(&mut cache)
251        }
252        .map(Ok)
253        .unwrap_or_else(|| {
254            debug!("Creating new {}", stringify!(CommandBuffer));
255
256            CommandBuffer::create(&self.device, info)
257        })?;
258
259        // Drop anything we were holding from the last submission
260        CommandBuffer::drop_fenced(&mut item);
261
262        Ok(Lease::new(Arc::downgrade(cache_ref), item))
263    }
264}
265
266impl Pool<DescriptorPoolInfo, DescriptorPool> for LazyPool {
267    #[profiling::function]
268    fn lease(&mut self, info: DescriptorPoolInfo) -> Result<Lease<DescriptorPool>, DriverError> {
269        let cache_ref = Arc::downgrade(&self.descriptor_pool_cache);
270
271        {
272            profiling::scope!("check cache");
273
274            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
275            let mut cache = self.descriptor_pool_cache.lock();
276
277            #[cfg(not(feature = "parking_lot"))]
278            let mut cache = cache.unwrap();
279
280            // Look for a compatible descriptor pool (has enough sets and descriptors)
281            for idx in 0..cache.len() {
282                let item = unsafe { cache.get_unchecked(idx) };
283                if item.info.max_sets >= info.max_sets
284                    && item.info.acceleration_structure_count >= info.acceleration_structure_count
285                    && item.info.combined_image_sampler_count >= info.combined_image_sampler_count
286                    && item.info.input_attachment_count >= info.input_attachment_count
287                    && item.info.sampled_image_count >= info.sampled_image_count
288                    && item.info.sampler_count >= info.sampled_image_count
289                    && item.info.storage_buffer_count >= info.storage_buffer_count
290                    && item.info.storage_buffer_dynamic_count >= info.storage_buffer_dynamic_count
291                    && item.info.storage_image_count >= info.storage_image_count
292                    && item.info.storage_texel_buffer_count >= info.storage_texel_buffer_count
293                    && item.info.uniform_buffer_count >= info.uniform_buffer_count
294                    && item.info.uniform_buffer_dynamic_count >= info.uniform_buffer_dynamic_count
295                    && item.info.uniform_texel_buffer_count >= info.uniform_texel_buffer_count
296                {
297                    let item = cache.swap_remove(idx);
298
299                    return Ok(Lease::new(cache_ref, item));
300                }
301            }
302        }
303
304        debug!("Creating new {}", stringify!(DescriptorPool));
305
306        let item = DescriptorPool::create(&self.device, info)?;
307
308        Ok(Lease::new(cache_ref, item))
309    }
310}
311
312impl Pool<ImageInfo, Image> for LazyPool {
313    #[profiling::function]
314    fn lease(&mut self, info: ImageInfo) -> Result<Lease<Image>, DriverError> {
315        let cache = self
316            .image_cache
317            .entry(info.into())
318            .or_insert_with(|| PoolInfo::explicit_cache(self.info.image_capacity));
319        let cache_ref = Arc::downgrade(cache);
320
321        {
322            profiling::scope!("check cache");
323
324            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
325            let mut cache = cache.lock();
326
327            #[cfg(not(feature = "parking_lot"))]
328            let mut cache = cache.unwrap();
329
330            // Look for a compatible image (superset of creation flags and usage flags)
331            for idx in 0..cache.len() {
332                let item = unsafe { cache.get_unchecked(idx) };
333                if item.info.flags.contains(info.flags) && item.info.usage.contains(info.usage) {
334                    let item = cache.swap_remove(idx);
335
336                    return Ok(Lease::new(cache_ref, item));
337                }
338            }
339        }
340
341        debug!("Creating new {}", stringify!(Image));
342
343        let item = Image::create(&self.device, info)?;
344
345        Ok(Lease::new(cache_ref, item))
346    }
347}
348
349impl Pool<RenderPassInfo, RenderPass> for LazyPool {
350    #[profiling::function]
351    fn lease(&mut self, info: RenderPassInfo) -> Result<Lease<RenderPass>, DriverError> {
352        let cache_ref = if let Some(cache) = self.render_pass_cache.get(&info) {
353            cache
354        } else {
355            // We tried to get the cache first in order to avoid this clone
356            self.render_pass_cache
357                .entry(info.clone())
358                .or_insert_with(PoolInfo::default_cache)
359        };
360        let item = {
361            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
362            let mut cache = cache_ref.lock();
363
364            #[cfg(not(feature = "parking_lot"))]
365            let mut cache = cache.unwrap();
366
367            cache.pop()
368        }
369        .map(Ok)
370        .unwrap_or_else(|| {
371            debug!("Creating new {}", stringify!(RenderPass));
372
373            RenderPass::create(&self.device, info)
374        })?;
375
376        Ok(Lease::new(Arc::downgrade(cache_ref), item))
377    }
378}