Skip to main content

vk_graph/pool/
hash.rs

1//! Pool which requests by exactly matching the information before creating new resources.
2
3use {
4    super::{
5        Cache, Lease, Pool, PoolConfig,
6        garbage_collector::{CollectResources, ResourceRequests},
7    },
8    crate::driver::{
9        DriverError,
10        accel_struct::{AccelerationStructure, AccelerationStructureInfo},
11        buffer::{Buffer, BufferInfo},
12        cmd_buf::{CommandBuffer, CommandBufferInfo},
13        descriptor_set::{DescriptorPool, DescriptorPoolInfo},
14        device::Device,
15        image::{Image, ImageInfo},
16        render_pass::{RenderPass, RenderPassInfo},
17    },
18    log::debug,
19    paste::paste,
20    std::{collections::HashMap, sync::Arc},
21};
22
23#[cfg(feature = "parking_lot")]
24use parking_lot::Mutex;
25
26#[cfg(not(feature = "parking_lot"))]
27use std::sync::Mutex;
28
29/// A high-performance resource allocator.
30///
31/// # Bucket Strategy
32///
33/// The information for each resource request is the key for a `HashMap` of buckets. If no bucket
34/// exists with the exact information provided a new bucket is created.
35///
36/// In practice this means that for a [`PoolConfig::image_capacity`] of `4`, requests for a
37/// 1024x1024 image with certain attributes will store a maximum of `4` such images. Requests for
38/// any image having a different size or attributes will store an additional maximum of `4` images.
39///
40/// # Memory Management
41///
42/// If requests for varying resources are common [`HashPool::clear_images_by_info`] and other
43/// memory management functions are necessary in order to avoid using all available device memory.
44#[derive(Debug)]
45#[read_only::cast]
46pub struct HashPool {
47    acceleration_structure_cache: HashMap<AccelerationStructureInfo, Cache<AccelerationStructure>>,
48    buffer_cache: HashMap<BufferInfo, Cache<Buffer>>,
49    command_buffer_cache: HashMap<u32, Cache<CommandBuffer>>,
50    descriptor_pool_cache: HashMap<DescriptorPoolInfo, Cache<DescriptorPool>>,
51
52    /// The device which owns this pool.
53    ///
54    /// _Note:_ This field is read-only.
55    #[readonly]
56    pub device: Device,
57
58    image_cache: HashMap<ImageInfo, Cache<Image>>,
59
60    /// Information used to create this pool.
61    ///
62    /// _Note:_ This field is read-only.
63    #[readonly]
64    pub info: PoolConfig,
65
66    render_pass_cache: HashMap<RenderPassInfo, Cache<RenderPass>>,
67}
68
69impl HashPool {
70    /// Constructs a new `HashPool`.
71    pub fn new(device: &Device) -> Self {
72        Self::with_capacity(device, PoolConfig::default())
73    }
74
75    /// Constructs a new `HashPool` with the given capacity information.
76    pub fn with_capacity(device: &Device, info: impl Into<PoolConfig>) -> Self {
77        let info: PoolConfig = info.into();
78        let device = device.clone();
79
80        Self {
81            acceleration_structure_cache: Default::default(),
82            buffer_cache: Default::default(),
83            command_buffer_cache: Default::default(),
84            descriptor_pool_cache: Default::default(),
85            device,
86            image_cache: Default::default(),
87            info,
88            render_pass_cache: Default::default(),
89        }
90    }
91
92    /// Clears the pool, removing all resources.
93    pub fn clear(&mut self) {
94        self.clear_accel_structs();
95        self.clear_buffers();
96        self.clear_images();
97    }
98}
99
100impl CollectResources for HashPool {
101    fn collect_resources(&mut self, requests: &ResourceRequests) {
102        self.acceleration_structure_cache
103            .retain(|info, _| requests.accel_structs.contains(info));
104        self.buffer_cache
105            .retain(|info, _| requests.buffers.contains(info));
106        self.image_cache
107            .retain(|info, _| requests.images.contains(info));
108    }
109}
110
111macro_rules! resource_mgmt_fns {
112    ($fn_plural:literal, $doc_singular:literal, $ty:ty, $field:ident) => {
113        paste! {
114            impl HashPool {
115                #[doc = "Clears the pool of " $doc_singular " resources."]
116                pub fn [<clear_ $fn_plural>](&mut self) {
117                    self.$field.clear();
118                }
119
120                #[doc = "Clears the pool of all " $doc_singular " resources matching the given
121information."]
122                pub fn [<clear_ $fn_plural _by_info>](
123                    &mut self,
124                    info: impl Into<$ty>,
125                ) {
126                    self.$field.remove(&info.into());
127                }
128
129                #[doc = "Retains only the " $doc_singular " resources specified by the predicate.\n
130\nIn other words, remove all " $doc_singular " resources for which `f(" $ty ")` returns `false`.\n
131\n"]
132                /// The elements are visited in unsorted (and unspecified) order.
133                ///
134                /// # Performance
135                ///
136                /// Provides the same performance guarantees as
137                /// [`HashMap::retain`](HashMap::retain).
138                pub fn [<retain_ $fn_plural>]<F>(&mut self, mut f: F)
139                where
140                    F: FnMut($ty) -> bool,
141                {
142                    self.$field.retain(|&info, _| f(info))
143                }
144            }
145        }
146    };
147}
148
149resource_mgmt_fns!(
150    "accel_structs",
151    "acceleration structure",
152    AccelerationStructureInfo,
153    acceleration_structure_cache
154);
155resource_mgmt_fns!("buffers", "buffer", BufferInfo, buffer_cache);
156resource_mgmt_fns!("images", "image", ImageInfo, image_cache);
157
158impl Pool<CommandBufferInfo, CommandBuffer> for HashPool {
159    #[profiling::function]
160    fn resource(&mut self, info: CommandBufferInfo) -> Result<Lease<CommandBuffer>, DriverError> {
161        let cache_ref = self
162            .command_buffer_cache
163            .entry(info.queue_family_index)
164            .or_insert_with(PoolConfig::default_cache);
165        let item = {
166            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
167            let mut cache = cache_ref.lock();
168
169            #[cfg(not(feature = "parking_lot"))]
170            let mut cache = cache.expect("poisoned cache lock");
171
172            cache.pop()
173        }
174        .map(Ok)
175        .unwrap_or_else(|| {
176            debug!("Creating new {}", stringify!(CommandBuffer));
177
178            CommandBuffer::create(&self.device, info)
179        })?;
180
181        // Drop anything we were holding from the last submission
182        //item.wait_until_executed()?;
183
184        Ok(Lease::new(Arc::downgrade(cache_ref), item))
185    }
186}
187
188impl Pool<DescriptorPoolInfo, DescriptorPool> for HashPool {
189    #[profiling::function]
190    fn resource(&mut self, info: DescriptorPoolInfo) -> Result<Lease<DescriptorPool>, DriverError> {
191        let cache_ref = self
192            .descriptor_pool_cache
193            .entry(info.clone())
194            .or_insert_with(PoolConfig::default_cache);
195        let item = {
196            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
197            let mut cache = cache_ref.lock();
198
199            #[cfg(not(feature = "parking_lot"))]
200            let mut cache = cache.expect("poisoned cache lock");
201
202            cache.pop()
203        }
204        .map(Ok)
205        .unwrap_or_else(|| {
206            debug!("Creating new {}", stringify!(DescriptorPool));
207
208            DescriptorPool::create(&self.device, info)
209        })?;
210
211        Ok(Lease::new(Arc::downgrade(cache_ref), item))
212    }
213}
214
215impl Pool<RenderPassInfo, RenderPass> for HashPool {
216    #[profiling::function]
217    fn resource(&mut self, info: RenderPassInfo) -> Result<Lease<RenderPass>, DriverError> {
218        let cache_ref = if let Some(cache) = self.render_pass_cache.get(&info) {
219            cache
220        } else {
221            // We tried to get the cache first in order to avoid this clone
222            self.render_pass_cache
223                .entry(info.clone())
224                .or_insert_with(PoolConfig::default_cache)
225        };
226        let item = {
227            #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
228            let mut cache = cache_ref.lock();
229
230            #[cfg(not(feature = "parking_lot"))]
231            let mut cache = cache.expect("poisoned cache lock");
232
233            cache.pop()
234        }
235        .map(Ok)
236        .unwrap_or_else(|| {
237            debug!("Creating new {}", stringify!(RenderPass));
238
239            RenderPass::create(&self.device, info)
240        })?;
241
242        Ok(Lease::new(Arc::downgrade(cache_ref), item))
243    }
244}
245
246// Enable requesting items using their basic info
247macro_rules! lease {
248    ($info:ident => $item:ident, $capacity:ident) => {
249        paste::paste! {
250            impl Pool<$info, $item> for HashPool {
251                #[profiling::function]
252                fn resource(&mut self, info: $info) -> Result<Lease<$item>, DriverError> {
253                    let cache_ref = self.[<$item:snake _cache>].entry(info)
254                        .or_insert_with(|| {
255                            Cache::new(Mutex::new(Vec::with_capacity(self.info.$capacity)))
256                        });
257                    let item = {
258                        #[cfg_attr(not(feature = "parking_lot"), allow(unused_mut))]
259                        let mut cache = cache_ref.lock();
260
261                        #[cfg(not(feature = "parking_lot"))]
262                        let mut cache = cache.expect("poisoned cache lock");
263
264                        cache.pop()
265                    }
266                    .map(Ok)
267                    .unwrap_or_else(|| {
268                        debug!("Creating new {}", stringify!($item));
269
270                        $item::create(&self.device, info)
271                    })?;
272
273                    Ok(Lease::new(Arc::downgrade(cache_ref), item))
274                }
275            }
276        }
277    };
278}
279
280lease!(AccelerationStructureInfo => AccelerationStructure, accel_struct_capacity);
281lease!(BufferInfo => Buffer, buffer_capacity);
282lease!(ImageInfo => Image, image_capacity);
283
284#[cfg(test)]
285mod test {
286    use {
287        super::*,
288        crate::{
289            driver::device::{Device, DeviceInfo},
290            pool::garbage_collector::GarbageCollector,
291        },
292        ash::vk,
293    };
294
295    #[test]
296    #[ignore = "requires Vulkan device"]
297    fn vulkan_garbage_collector_retains_requested_hash_buckets() -> Result<(), DriverError> {
298        let device = Device::create(DeviceInfo::default())?;
299        let mut collector = GarbageCollector::new(HashPool::with_capacity(&device, 4));
300        let retained_info = BufferInfo::device_mem(64, vk::BufferUsageFlags::TRANSFER_SRC);
301        let removed_info = BufferInfo::device_mem(64, vk::BufferUsageFlags::STORAGE_BUFFER);
302
303        drop(collector.resource(retained_info)?);
304        drop(collector.resource(removed_info)?);
305        collector.collect_resources();
306        assert_eq!(collector.buffer_cache.len(), 2);
307
308        drop(collector.resource(retained_info)?);
309        collector.collect_resources();
310        assert_eq!(collector.buffer_cache.len(), 1);
311        assert!(collector.buffer_cache.contains_key(&retained_info));
312
313        collector.collect_resources();
314        assert!(collector.buffer_cache.is_empty());
315
316        Ok(())
317    }
318}