Skip to main content

vk_graph/pool/
garbage_collector.rs

1//! Pool wrapper which removes cached resources that no longer support observed requests.
2
3use {
4    super::{Lease, Pool},
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        image::{Image, ImageInfo},
12        render_pass::{RenderPass, RenderPassInfo},
13    },
14    std::{
15        collections::HashSet,
16        ops::{Deref, DerefMut},
17    },
18};
19
20#[derive(Default)]
21pub(super) struct ResourceRequests {
22    pub(super) accel_structs: HashSet<AccelerationStructureInfo>,
23    pub(super) buffers: HashSet<BufferInfo>,
24    pub(super) images: HashSet<ImageInfo>,
25}
26
27impl ResourceRequests {
28    fn clear(&mut self) {
29        self.accel_structs.clear();
30        self.buffers.clear();
31        self.images.clear();
32    }
33}
34
35pub(super) trait CollectResources {
36    fn collect_resources(&mut self, requests: &ResourceRequests);
37}
38
39/// A request-aware garbage collector for built-in [`Pool`] types.
40///
41/// Successful acceleration-structure, buffer, and image requests are recorded until
42/// [`GarbageCollector::collect_resources`] is called. Collection retains only cached resources that
43/// the wrapped pool could use to satisfy those requests, then begins a new observation interval.
44/// Calling `collect_resources` without making any requests clears all managed resources.
45///
46/// Checked-out resources remain valid during collection. Resources returning to a retained bucket
47/// are evaluated by the next collection, while resources returning to a removed bucket are dropped.
48/// Command buffers, descriptor pools, and render passes are forwarded without being collected.
49///
50/// # Examples
51///
52/// ```no_run
53/// # use ash::vk;
54/// # use vk_graph::driver::DriverError;
55/// # use vk_graph::driver::buffer::BufferInfo;
56/// # use vk_graph::driver::device::{Device, DeviceInfo};
57/// # use vk_graph::pool::Pool;
58/// # use vk_graph::pool::garbage_collector::GarbageCollector;
59/// # use vk_graph::pool::lazy::LazyPool;
60/// # fn main() -> Result<(), DriverError> {
61/// # let device = Device::create(DeviceInfo::default())?;
62/// let mut pool = GarbageCollector::new(LazyPool::new(&device));
63///
64/// let buffer = pool.resource(BufferInfo::device_mem(
65///     1024,
66///     vk::BufferUsageFlags::STORAGE_BUFFER,
67/// ))?;
68/// drop(buffer);
69///
70/// // Retain cached resources supporting requests made since the previous collection.
71/// pool.collect_resources();
72/// # Ok(()) }
73/// ```
74pub struct GarbageCollector<T> {
75    pool: T,
76    requests: ResourceRequests,
77}
78
79impl<T> GarbageCollector<T> {
80    /// Creates a garbage collector wrapper over the given pool.
81    pub fn new(pool: T) -> Self {
82        Self {
83            pool,
84            requests: Default::default(),
85        }
86    }
87}
88
89#[allow(private_bounds)]
90impl<T> GarbageCollector<T>
91where
92    T: CollectResources,
93{
94    /// Collects cached resources and begins a new request observation interval.
95    ///
96    /// Only acceleration structures, buffers, and images supporting successful requests made since
97    /// the previous call are retained. If there were no such requests, all managed resources are
98    /// removed.
99    pub fn collect_resources(&mut self) {
100        self.pool.collect_resources(&self.requests);
101        self.requests.clear();
102    }
103}
104
105macro_rules! tracked_pool {
106    ($info:ty => $item:ty, $requests:ident) => {
107        impl<T> Pool<$info, $item> for GarbageCollector<T>
108        where
109            T: Pool<$info, $item>,
110        {
111            fn resource(&mut self, info: $info) -> Result<Lease<$item>, DriverError> {
112                let item = self.pool.resource(info)?;
113                self.requests.$requests.insert(info);
114
115                Ok(item)
116            }
117        }
118    };
119}
120
121tracked_pool!(AccelerationStructureInfo => AccelerationStructure, accel_structs);
122tracked_pool!(BufferInfo => Buffer, buffers);
123tracked_pool!(ImageInfo => Image, images);
124
125macro_rules! forwarded_pool {
126    ($info:ty => $item:ty) => {
127        impl<T> Pool<$info, $item> for GarbageCollector<T>
128        where
129            T: Pool<$info, $item>,
130        {
131            fn resource(&mut self, info: $info) -> Result<Lease<$item>, DriverError> {
132                self.pool.resource(info)
133            }
134        }
135    };
136}
137
138forwarded_pool!(CommandBufferInfo => CommandBuffer);
139forwarded_pool!(DescriptorPoolInfo => DescriptorPool);
140forwarded_pool!(RenderPassInfo => RenderPass);
141
142impl<T> Deref for GarbageCollector<T> {
143    type Target = T;
144
145    fn deref(&self) -> &Self::Target {
146        &self.pool
147    }
148}
149
150impl<T> DerefMut for GarbageCollector<T> {
151    fn deref_mut(&mut self) -> &mut Self::Target {
152        &mut self.pool
153    }
154}
155
156#[cfg(test)]
157mod test {
158    use {
159        super::*,
160        crate::{
161            driver::{
162                accel_struct::AccelerationStructureInfoBuilder, buffer::BufferInfoBuilder,
163                image::ImageInfoBuilder,
164            },
165            pool::{SubmissionPool, fifo::FifoPool, hash::HashPool, lazy::LazyPool},
166        },
167        ash::vk,
168    };
169
170    #[derive(Default)]
171    struct CollectSpy {
172        calls: Vec<(usize, usize, usize)>,
173    }
174
175    impl CollectResources for CollectSpy {
176        fn collect_resources(&mut self, requests: &ResourceRequests) {
177            self.calls.push((
178                requests.accel_structs.len(),
179                requests.buffers.len(),
180                requests.images.len(),
181            ));
182        }
183    }
184
185    struct FailingPool;
186
187    impl Pool<BufferInfo, Buffer> for FailingPool {
188        fn resource(&mut self, _: BufferInfo) -> Result<Lease<Buffer>, DriverError> {
189            Err(DriverError::Unsupported)
190        }
191    }
192
193    fn assert_pool_capabilities<T>()
194    where
195        T: Pool<AccelerationStructureInfo, AccelerationStructure>
196            + Pool<AccelerationStructureInfoBuilder, AccelerationStructure>
197            + Pool<BufferInfo, Buffer>
198            + Pool<BufferInfoBuilder, Buffer>
199            + Pool<ImageInfo, Image>
200            + Pool<ImageInfoBuilder, Image>
201            + Pool<CommandBufferInfo, CommandBuffer>
202            + SubmissionPool,
203    {
204    }
205
206    fn assert_collect_resources<T: CollectResources>() {}
207
208    #[test]
209    fn collect_resources_sweeps_and_resets_observed_requests() {
210        let mut collector = GarbageCollector::new(CollectSpy::default());
211        let info = BufferInfo::device_mem(64, vk::BufferUsageFlags::STORAGE_BUFFER);
212
213        collector.requests.buffers.insert(info);
214        collector.requests.buffers.insert(info);
215        collector.collect_resources();
216        collector.collect_resources();
217
218        assert_eq!(collector.pool.calls, [(0, 1, 0), (0, 0, 0)]);
219    }
220
221    #[test]
222    fn failed_requests_are_not_observed() {
223        let mut collector = GarbageCollector::new(FailingPool);
224        let info = BufferInfo::device_mem(64, vk::BufferUsageFlags::STORAGE_BUFFER);
225
226        assert!(matches!(
227            Pool::<BufferInfo, Buffer>::resource(&mut collector, info),
228            Err(DriverError::Unsupported)
229        ));
230        assert!(collector.requests.buffers.is_empty());
231    }
232
233    #[test]
234    fn built_in_pools_support_collection_and_pool_capabilities() {
235        assert_collect_resources::<FifoPool>();
236        assert_collect_resources::<HashPool>();
237        assert_collect_resources::<LazyPool>();
238        assert_pool_capabilities::<GarbageCollector<FifoPool>>();
239        assert_pool_capabilities::<GarbageCollector<HashPool>>();
240        assert_pool_capabilities::<GarbageCollector<LazyPool>>();
241    }
242}