Skip to main content

vk_graph/pool/
lazy.rs

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