1use {
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#[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 #[readonly]
56 pub device: Device,
57
58 image_cache: HashMap<ImageInfo, Cache<Image>>,
59
60 #[readonly]
64 pub info: PoolConfig,
65
66 render_pass_cache: HashMap<RenderPassInfo, Cache<RenderPass>>,
67}
68
69impl HashPool {
70 pub fn new(device: &Device) -> Self {
72 Self::with_capacity(device, PoolConfig::default())
73 }
74
75 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 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 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 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 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
246macro_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}