Skip to main content

vk_graph/pool/
fifo.rs

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