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