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