Skip to main content

vk_graph/pool/
lazy.rs

1//! Pool which requests by looking for compatible information before creating new resources.
2
3use {
4    super::{
5        BufferHostMappingCompatibility, Cache, Lease, Pool, PoolConfig, compatible_buffer_info,
6        garbage_collector::{CollectResources, ResourceRequests},
7        with_cache,
8    },
9    crate::driver::{
10        DriverError,
11        accel_struct::{AccelerationStructure, AccelerationStructureInfo},
12        buffer::{Buffer, BufferInfo},
13        cmd_buf::{CommandBuffer, CommandBufferInfo},
14        descriptor_set::{DescriptorPool, DescriptorPoolInfo},
15        device::Device,
16        image::{Image, ImageInfo, SampleCount},
17        render_pass::{RenderPass, RenderPassInfo},
18    },
19    ash::vk,
20    log::debug,
21    std::{collections::HashMap, sync::Arc},
22};
23
24type BufferKey = (bool, vk::DeviceSize, vk::SharingMode);
25
26fn buffer_key(info: &BufferInfo) -> BufferKey {
27    (
28        info.host_readable | info.host_writable,
29        info.alignment,
30        info.sharing_mode,
31    )
32}
33
34fn compatible_accel_struct_info(
35    item_info: &AccelerationStructureInfo,
36    requested_info: &AccelerationStructureInfo,
37) -> bool {
38    item_info.acceleration_structure_type == requested_info.acceleration_structure_type
39        && item_info.size >= requested_info.size
40}
41
42fn compatible_lazy_buffer_info(item_info: &BufferInfo, requested_info: &BufferInfo) -> bool {
43    buffer_key(item_info) == buffer_key(requested_info)
44        && compatible_buffer_info(
45            item_info,
46            requested_info,
47            BufferHostMappingCompatibility::Superset,
48        )
49}
50
51#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
52struct ImageKey {
53    array_layer_count: u32,
54    depth: u32,
55    fmt: vk::Format,
56    height: u32,
57    mip_level_count: u32,
58    sample_count: SampleCount,
59    sharing_mode: vk::SharingMode,
60    tiling: vk::ImageTiling,
61    image_type: vk::ImageType,
62    width: u32,
63}
64
65impl From<ImageInfo> for ImageKey {
66    fn from(info: ImageInfo) -> Self {
67        Self {
68            array_layer_count: info.array_layer_count,
69            depth: info.depth,
70            fmt: info.format,
71            height: info.height,
72            mip_level_count: info.mip_level_count,
73            sample_count: info.sample_count,
74            sharing_mode: info.sharing_mode,
75            tiling: info.tiling,
76            image_type: info.image_type,
77            width: info.width,
78        }
79    }
80}
81
82fn compatible_lazy_image_info(item_info: &ImageInfo, requested_info: &ImageInfo) -> bool {
83    ImageKey::from(*item_info) == ImageKey::from(*requested_info)
84        && item_info.flags.contains(requested_info.flags)
85        && item_info.usage.contains(requested_info.usage)
86}
87
88/// A balanced resource allocator.
89///
90/// The information for each resource request is compared against the stored resources for
91/// compatibility. If no acceptable resources are stored for the information provided a new resource
92/// is created and returned.
93///
94/// # Details
95///
96/// * Acceleration structures may be larger than requested
97/// * Buffers may be larger than requested or have additional usage flags
98/// * Images may have additional usage flags
99///
100/// # Bucket Strategy
101///
102/// The information for each resource request is the key for a `HashMap` of buckets. If no bucket
103/// exists with compatible information a new bucket is created.
104///
105/// In practice this means that for a [`PoolConfig::image_capacity`] of `4`, requests for a
106/// 1024x1024 image with certain attributes will store a maximum of `4` such images. Requests for
107/// any image having a different size or incompatible attributes will store an additional maximum of
108/// `4` images.
109///
110/// # Memory Management
111///
112/// If requests for varying resources are common [`LazyPool::clear_images_by_info`] and other
113/// memory management functions are necessary in order to avoid using all available device memory.
114#[derive(Debug)]
115#[read_only::cast]
116pub struct LazyPool {
117    accel_struct_cache: HashMap<vk::AccelerationStructureTypeKHR, Cache<AccelerationStructure>>,
118    buffer_cache: HashMap<BufferKey, Cache<Buffer>>,
119    command_buffer_cache: HashMap<u32, Cache<CommandBuffer>>,
120    descriptor_pool_cache: Cache<DescriptorPool>,
121
122    /// The device which owns this pool.
123    ///
124    /// _Note:_ This field is read-only.
125    #[readonly]
126    pub device: Device,
127
128    image_cache: HashMap<ImageKey, Cache<Image>>,
129
130    /// Information used to create this pool.
131    ///
132    /// _Note:_ This field is read-only.
133    #[readonly]
134    pub info: PoolConfig,
135
136    render_pass_cache: HashMap<RenderPassInfo, Cache<RenderPass>>,
137}
138
139impl LazyPool {
140    /// Constructs a new `LazyPool`.
141    pub fn new(device: &Device) -> Self {
142        Self::with_capacity(device, PoolConfig::default())
143    }
144
145    /// Constructs a new `LazyPool` with the given capacity information.
146    pub fn with_capacity(device: &Device, info: impl Into<PoolConfig>) -> Self {
147        let info: PoolConfig = info.into();
148        let device = device.clone();
149
150        Self {
151            accel_struct_cache: Default::default(),
152            buffer_cache: Default::default(),
153            command_buffer_cache: Default::default(),
154            descriptor_pool_cache: PoolConfig::default_cache(),
155            device,
156            image_cache: Default::default(),
157            info,
158            render_pass_cache: Default::default(),
159        }
160    }
161
162    /// Clears the pool, removing all resources.
163    pub fn clear(&mut self) {
164        self.clear_accel_structs();
165        self.clear_buffers();
166        self.clear_images();
167    }
168
169    /// Clears the pool of acceleration structure resources.
170    pub fn clear_accel_structs(&mut self) {
171        self.accel_struct_cache.clear();
172    }
173
174    /// Clears the pool of all acceleration structure resources matching the given type.
175    pub fn clear_accel_structs_by_type(
176        &mut self,
177        accel_struct_ty: vk::AccelerationStructureTypeKHR,
178    ) {
179        self.accel_struct_cache.remove(&accel_struct_ty);
180    }
181
182    /// Clears the pool of buffer resources.
183    pub fn clear_buffers(&mut self) {
184        self.buffer_cache.clear();
185    }
186
187    /// Clears the pool of image resources.
188    pub fn clear_images(&mut self) {
189        self.image_cache.clear();
190    }
191
192    /// Clears the pool of image resources matching the given information.
193    pub fn clear_images_by_info(&mut self, info: impl Into<ImageInfo>) {
194        self.image_cache.remove(&info.into().into());
195    }
196
197    /// Retains only the acceleration structure resources specified by the predicate.
198    ///
199    /// In other words, remove all resources for which `f(vk::AccelerationStructureTypeKHR)` returns
200    /// `false`.
201    ///
202    /// The elements are visited in unsorted (and unspecified) order.
203    ///
204    /// # Performance
205    ///
206    /// Provides the same performance guarantees as
207    /// [`HashMap::retain`](HashMap::retain).
208    pub fn retain_accel_structs<F>(&mut self, mut f: F)
209    where
210        F: FnMut(vk::AccelerationStructureTypeKHR) -> bool,
211    {
212        self.accel_struct_cache
213            .retain(|&accel_struct_ty, _| f(accel_struct_ty))
214    }
215}
216
217impl CollectResources for LazyPool {
218    fn collect_resources(&mut self, requests: &ResourceRequests) {
219        self.accel_struct_cache.retain(|accel_struct_ty, cache| {
220            let retain_bucket = requests
221                .accel_structs
222                .iter()
223                .any(|info| info.acceleration_structure_type == *accel_struct_ty);
224
225            if retain_bucket {
226                with_cache(cache, |cache| {
227                    cache.retain(|item| {
228                        requests
229                            .accel_structs
230                            .iter()
231                            .any(|info| compatible_accel_struct_info(&item.info, info))
232                    });
233                });
234            }
235
236            retain_bucket
237        });
238
239        self.buffer_cache.retain(|key, cache| {
240            let retain_bucket = requests.buffers.iter().any(|info| buffer_key(info) == *key);
241
242            if retain_bucket {
243                with_cache(cache, |cache| {
244                    cache.retain(|item| {
245                        requests
246                            .buffers
247                            .iter()
248                            .any(|info| compatible_lazy_buffer_info(&item.info, info))
249                    });
250                });
251            }
252
253            retain_bucket
254        });
255
256        self.image_cache.retain(|key, cache| {
257            let retain_bucket = requests
258                .images
259                .iter()
260                .any(|info| ImageKey::from(*info) == *key);
261
262            if retain_bucket {
263                with_cache(cache, |cache| {
264                    cache.retain(|item| {
265                        requests
266                            .images
267                            .iter()
268                            .any(|info| compatible_lazy_image_info(&item.info, info))
269                    });
270                });
271            }
272
273            retain_bucket
274        });
275    }
276}
277
278impl Pool<AccelerationStructureInfo, AccelerationStructure> for LazyPool {
279    #[profiling::function]
280    fn resource(
281        &mut self,
282        info: AccelerationStructureInfo,
283    ) -> Result<Lease<AccelerationStructure>, DriverError> {
284        let cache = self
285            .accel_struct_cache
286            .entry(info.acceleration_structure_type)
287            .or_insert_with(|| PoolConfig::explicit_cache(self.info.accel_struct_capacity));
288        let cache_ref = Arc::downgrade(cache);
289
290        {
291            profiling::scope!("check cache");
292
293            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
294            let mut cache = cache.lock();
295
296            #[cfg(not(feature = "parking_lot"))]
297            let mut cache = cache.expect("poisoned cache lock");
298
299            // Look for a compatible acceleration structure (big enough)
300            for idx in 0..cache.len() {
301                let item = unsafe { cache.get_unchecked(idx) };
302                if compatible_accel_struct_info(&item.info, &info) {
303                    let item = cache.swap_remove(idx);
304
305                    return Ok(Lease::new(cache_ref, item));
306                }
307            }
308        }
309
310        debug!("Creating new {}", stringify!(AccelerationStructure));
311
312        let item = AccelerationStructure::create(&self.device, info)?;
313
314        Ok(Lease::new(cache_ref, item))
315    }
316}
317
318impl Pool<BufferInfo, Buffer> for LazyPool {
319    #[profiling::function]
320    fn resource(&mut self, info: BufferInfo) -> Result<Lease<Buffer>, DriverError> {
321        let cache = self
322            .buffer_cache
323            .entry(buffer_key(&info))
324            .or_insert_with(|| PoolConfig::explicit_cache(self.info.buffer_capacity));
325        let cache_ref = Arc::downgrade(cache);
326
327        {
328            profiling::scope!("check cache");
329
330            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
331            let mut cache = cache.lock();
332
333            #[cfg(not(feature = "parking_lot"))]
334            let mut cache = cache.expect("poisoned cache lock");
335
336            // Look for a compatible buffer (big enough and superset of usage flags)
337            for idx in 0..cache.len() {
338                let item = unsafe { cache.get_unchecked(idx) };
339                if compatible_lazy_buffer_info(&item.info, &info) {
340                    let item = cache.swap_remove(idx);
341
342                    return Ok(Lease::new(cache_ref, item));
343                }
344            }
345        }
346
347        debug!("Creating new {}", stringify!(Buffer));
348
349        let item = Buffer::create(&self.device, info)?;
350
351        Ok(Lease::new(cache_ref, item))
352    }
353}
354
355impl Pool<CommandBufferInfo, CommandBuffer> for LazyPool {
356    #[profiling::function]
357    fn resource(&mut self, info: CommandBufferInfo) -> Result<Lease<CommandBuffer>, DriverError> {
358        let cache_ref = self
359            .command_buffer_cache
360            .entry(info.queue_family_index)
361            .or_insert_with(PoolConfig::default_cache);
362        let item = {
363            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
364            let mut cache = cache_ref.lock();
365
366            #[cfg(not(feature = "parking_lot"))]
367            let mut cache = cache.expect("poisoned cache lock");
368
369            cache.pop()
370        }
371        .map(Ok)
372        .unwrap_or_else(|| {
373            debug!("Creating new {}", stringify!(CommandBuffer));
374
375            CommandBuffer::create(&self.device, info)
376        })?;
377
378        // Drop anything we were holding from the last submission
379        //item.wait_until_executed()?;
380
381        Ok(Lease::new(Arc::downgrade(cache_ref), item))
382    }
383}
384
385impl Pool<DescriptorPoolInfo, DescriptorPool> for LazyPool {
386    #[profiling::function]
387    fn resource(&mut self, info: DescriptorPoolInfo) -> Result<Lease<DescriptorPool>, DriverError> {
388        let cache_ref = Arc::downgrade(&self.descriptor_pool_cache);
389
390        {
391            profiling::scope!("check cache");
392
393            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
394            let mut cache = self.descriptor_pool_cache.lock();
395
396            #[cfg(not(feature = "parking_lot"))]
397            let mut cache = cache.expect("poisoned cache lock");
398
399            // Look for a compatible descriptor pool (has enough sets and descriptors)
400            for idx in 0..cache.len() {
401                let item = unsafe { cache.get_unchecked(idx) };
402                if item.info.max_sets >= info.max_sets
403                    && item.info.acceleration_structure_count >= info.acceleration_structure_count
404                    && item.info.combined_image_sampler_count >= info.combined_image_sampler_count
405                    && item.info.input_attachment_count >= info.input_attachment_count
406                    && item.info.sampled_image_count >= info.sampled_image_count
407                    && item.info.sampler_count >= info.sampled_image_count
408                    && item.info.storage_buffer_count >= info.storage_buffer_count
409                    && item.info.storage_buffer_dynamic_count >= info.storage_buffer_dynamic_count
410                    && item.info.storage_image_count >= info.storage_image_count
411                    && item.info.storage_texel_buffer_count >= info.storage_texel_buffer_count
412                    && item.info.uniform_buffer_count >= info.uniform_buffer_count
413                    && item.info.uniform_buffer_dynamic_count >= info.uniform_buffer_dynamic_count
414                    && item.info.uniform_texel_buffer_count >= info.uniform_texel_buffer_count
415                {
416                    let item = cache.swap_remove(idx);
417
418                    return Ok(Lease::new(cache_ref, item));
419                }
420            }
421        }
422
423        debug!("Creating new {}", stringify!(DescriptorPool));
424
425        let item = DescriptorPool::create(&self.device, info)?;
426
427        Ok(Lease::new(cache_ref, item))
428    }
429}
430
431impl Pool<ImageInfo, Image> for LazyPool {
432    #[profiling::function]
433    fn resource(&mut self, info: ImageInfo) -> Result<Lease<Image>, DriverError> {
434        let cache = self
435            .image_cache
436            .entry(info.into())
437            .or_insert_with(|| PoolConfig::explicit_cache(self.info.image_capacity));
438        let cache_ref = Arc::downgrade(cache);
439
440        {
441            profiling::scope!("check cache");
442
443            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
444            let mut cache = cache.lock();
445
446            #[cfg(not(feature = "parking_lot"))]
447            let mut cache = cache.expect("poisoned cache lock");
448
449            // Look for a compatible image (superset of creation flags and usage flags)
450            for idx in 0..cache.len() {
451                let item = unsafe { cache.get_unchecked(idx) };
452                if compatible_lazy_image_info(&item.info, &info) {
453                    let item = cache.swap_remove(idx);
454
455                    return Ok(Lease::new(cache_ref, item));
456                }
457            }
458        }
459
460        debug!("Creating new {}", stringify!(Image));
461
462        let item = Image::create(&self.device, info)?;
463
464        Ok(Lease::new(cache_ref, item))
465    }
466}
467
468impl Pool<RenderPassInfo, RenderPass> for LazyPool {
469    #[profiling::function]
470    fn resource(&mut self, info: RenderPassInfo) -> Result<Lease<RenderPass>, DriverError> {
471        let cache_ref = if let Some(cache) = self.render_pass_cache.get(&info) {
472            cache
473        } else {
474            // We tried to get the cache first in order to avoid this clone
475            self.render_pass_cache
476                .entry(info.clone())
477                .or_insert_with(PoolConfig::default_cache)
478        };
479        let item = {
480            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
481            let mut cache = cache_ref.lock();
482
483            #[cfg(not(feature = "parking_lot"))]
484            let mut cache = cache.expect("poisoned cache lock");
485
486            cache.pop()
487        }
488        .map(Ok)
489        .unwrap_or_else(|| {
490            debug!("Creating new {}", stringify!(RenderPass));
491
492            RenderPass::create(&self.device, info)
493        })?;
494
495        Ok(Lease::new(Arc::downgrade(cache_ref), item))
496    }
497}
498
499#[cfg(test)]
500mod test {
501    use {
502        super::*,
503        crate::{
504            driver::device::{Device, DeviceInfo},
505            pool::garbage_collector::GarbageCollector,
506        },
507    };
508
509    #[test]
510    #[ignore = "requires Vulkan device"]
511    fn vulkan_garbage_collector_retains_supported_lazy_resources() -> Result<(), DriverError> {
512        let device = Device::create(DeviceInfo::default())?;
513        let mut collector = GarbageCollector::new(LazyPool::with_capacity(&device, 4));
514        let retained_info = BufferInfo::device_mem(64, vk::BufferUsageFlags::TRANSFER_SRC);
515        let removed_info = BufferInfo::device_mem(64, vk::BufferUsageFlags::STORAGE_BUFFER);
516        let key = buffer_key(&retained_info);
517
518        drop(collector.resource(retained_info)?);
519        drop(collector.resource(removed_info)?);
520        collector.collect_resources();
521        assert_eq!(
522            with_cache(&collector.buffer_cache[&key], |cache| cache.len()),
523            2
524        );
525
526        drop(collector.resource(retained_info)?);
527        collector.collect_resources();
528        assert_eq!(
529            with_cache(&collector.buffer_cache[&key], |cache| cache.len()),
530            1
531        );
532
533        collector.collect_resources();
534        assert!(collector.buffer_cache.is_empty());
535
536        Ok(())
537    }
538}