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        with_cache,
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    std::{collections::HashMap, sync::Arc},
20};
21
22/// A memory-efficient resource allocator.
23///
24/// The information for each resource request is compared against the stored resources for
25/// compatibility. If no acceptable resources are stored for the information provided a new resource
26/// is created and returned.
27///
28/// # Details
29///
30/// * Acceleration structures may be larger than requested
31/// * Buffers may be larger than requested or have additional usage flags
32/// * Images may have additional usage flags
33///
34/// # Bucket Strategy
35///
36/// All resources are stored in a single bucket per resource type, regardless of their individual
37/// attributes.
38///
39/// In practice this means that for a [`PoolConfig::image_capacity`] of `4`, a maximum of `4` images
40/// will be stored. Requests to obtain an image or other resource will first look for a compatible
41/// resource in the bucket and create a new resource as needed.
42///
43/// # Memory Management
44///
45/// The single-bucket strategy means that there will always be a reasonable and predictable number
46/// of stored resources, however you may call [`FifoPool::clear`] or the other memory management
47/// functions at any time to discard stored resources.
48#[derive(Debug)]
49#[read_only::cast]
50pub struct FifoPool {
51    accel_struct_cache: Cache<AccelerationStructure>,
52    buffer_cache: Cache<Buffer>,
53    command_buffer_cache: HashMap<u32, Cache<CommandBuffer>>,
54    descriptor_pool_cache: Cache<DescriptorPool>,
55
56    /// The device which owns this pool.
57    ///
58    /// _Note:_ This field is read-only.
59    #[readonly]
60    pub device: Device,
61
62    image_cache: Cache<Image>,
63
64    /// Information used to create this pool.
65    ///
66    /// _Note:_ This field is read-only.
67    #[readonly]
68    pub info: PoolConfig,
69
70    render_pass_cache: HashMap<RenderPassInfo, Cache<RenderPass>>,
71}
72
73impl FifoPool {
74    /// Constructs a new `FifoPool`.
75    pub fn new(device: &Device) -> Self {
76        Self::with_capacity(device, PoolConfig::default())
77    }
78
79    /// Constructs a new `FifoPool` with the given capacity information.
80    pub fn with_capacity(device: &Device, info: impl Into<PoolConfig>) -> Self {
81        let info: PoolConfig = info.into();
82        let device = device.clone();
83
84        Self {
85            accel_struct_cache: PoolConfig::explicit_cache(info.accel_struct_capacity),
86            buffer_cache: PoolConfig::explicit_cache(info.buffer_capacity),
87            command_buffer_cache: Default::default(),
88            descriptor_pool_cache: PoolConfig::default_cache(),
89            device,
90            image_cache: PoolConfig::explicit_cache(info.image_capacity),
91            info,
92            render_pass_cache: Default::default(),
93        }
94    }
95
96    /// Clears the pool, removing all resources.
97    pub fn clear(&mut self) {
98        self.clear_accel_structs();
99        self.clear_buffers();
100        self.clear_images();
101    }
102
103    /// Clears the pool of acceleration structure resources.
104    pub fn clear_accel_structs(&mut self) {
105        self.accel_struct_cache = PoolConfig::explicit_cache(self.info.accel_struct_capacity);
106    }
107
108    /// Clears the pool of buffer resources.
109    pub fn clear_buffers(&mut self) {
110        self.buffer_cache = PoolConfig::explicit_cache(self.info.buffer_capacity);
111    }
112
113    /// Clears the pool of image resources.
114    pub fn clear_images(&mut self) {
115        self.image_cache = PoolConfig::explicit_cache(self.info.image_capacity);
116    }
117}
118
119impl Pool<AccelerationStructureInfo, AccelerationStructure> for FifoPool {
120    #[profiling::function]
121    fn resource(
122        &mut self,
123        info: AccelerationStructureInfo,
124    ) -> Result<Lease<AccelerationStructure>, DriverError> {
125        let cache_ref = Arc::downgrade(&self.accel_struct_cache);
126
127        {
128            profiling::scope!("check cache");
129
130            if let Some(item) = with_cache(&self.accel_struct_cache, |cache| {
131                // Look for a compatible acceleration structure (big enough and same type)
132                for idx in 0..cache.len() {
133                    let item = unsafe { cache.get_unchecked(idx) };
134                    if item.info.size >= info.size
135                        && item.info.acceleration_structure_type == info.acceleration_structure_type
136                    {
137                        let item = cache.swap_remove(idx);
138
139                        return Some(Lease::new(cache_ref.clone(), item));
140                    }
141                }
142
143                None
144            }) {
145                return Ok(item);
146            }
147        }
148
149        debug!("Creating new {}", stringify!(AccelerationStructure));
150
151        let item = AccelerationStructure::create(&self.device, info)?;
152
153        Ok(Lease::new(cache_ref, item))
154    }
155}
156
157impl Pool<BufferInfo, Buffer> for FifoPool {
158    #[profiling::function]
159    fn resource(&mut self, info: BufferInfo) -> Result<Lease<Buffer>, DriverError> {
160        let cache_ref = Arc::downgrade(&self.buffer_cache);
161
162        {
163            profiling::scope!("check cache");
164
165            if let Some(item) = with_cache(&self.buffer_cache, |cache| {
166                // Look for a compatible buffer (compatible alignment, same mapping mode, big enough
167                // and superset of usage flags)
168                for idx in 0..cache.len() {
169                    let item = unsafe { cache.get_unchecked(idx) };
170                    if compatible_buffer_info(
171                        &item.info,
172                        &info,
173                        BufferHostMappingCompatibility::Exact,
174                    ) {
175                        let item = cache.swap_remove(idx);
176
177                        return Some(Lease::new(cache_ref.clone(), item));
178                    }
179                }
180
181                None
182            }) {
183                return Ok(item);
184            }
185        }
186
187        debug!("Creating new {}", stringify!(Buffer));
188
189        let item = Buffer::create(&self.device, info)?;
190
191        Ok(Lease::new(cache_ref, item))
192    }
193}
194
195impl Pool<CommandBufferInfo, CommandBuffer> for FifoPool {
196    #[profiling::function]
197    fn resource(&mut self, info: CommandBufferInfo) -> Result<Lease<CommandBuffer>, DriverError> {
198        let cache_ref = self
199            .command_buffer_cache
200            .entry(info.queue_family_index)
201            .or_insert_with(PoolConfig::default_cache);
202
203        let item = with_cache(cache_ref, Vec::pop).map(Ok).unwrap_or_else(|| {
204            debug!("Creating new {}", stringify!(CommandBuffer));
205
206            CommandBuffer::create(&self.device, info)
207        })?;
208
209        // Drop anything we were holding from the last submission
210        //item.wait_until_executed()?;
211
212        Ok(Lease::new(Arc::downgrade(cache_ref), item))
213    }
214}
215
216impl Pool<DescriptorPoolInfo, DescriptorPool> for FifoPool {
217    #[profiling::function]
218    fn resource(&mut self, info: DescriptorPoolInfo) -> Result<Lease<DescriptorPool>, DriverError> {
219        let cache_ref = Arc::downgrade(&self.descriptor_pool_cache);
220
221        {
222            profiling::scope!("check cache");
223
224            if let Some(item) = with_cache(&self.descriptor_pool_cache, |cache| {
225                // Look for a compatible descriptor pool (has enough sets and descriptors)
226                for idx in 0..cache.len() {
227                    let item = unsafe { cache.get_unchecked(idx) };
228                    if item.info.max_sets >= info.max_sets
229                        && item.info.acceleration_structure_count
230                            >= info.acceleration_structure_count
231                        && item.info.combined_image_sampler_count
232                            >= info.combined_image_sampler_count
233                        && item.info.input_attachment_count >= info.input_attachment_count
234                        && item.info.sampled_image_count >= info.sampled_image_count
235                        && item.info.sampler_count >= info.sampler_count
236                        && item.info.storage_buffer_count >= info.storage_buffer_count
237                        && item.info.storage_buffer_dynamic_count
238                            >= info.storage_buffer_dynamic_count
239                        && item.info.storage_image_count >= info.storage_image_count
240                        && item.info.storage_texel_buffer_count >= info.storage_texel_buffer_count
241                        && item.info.uniform_buffer_count >= info.uniform_buffer_count
242                        && item.info.uniform_buffer_dynamic_count
243                            >= info.uniform_buffer_dynamic_count
244                        && item.info.uniform_texel_buffer_count >= info.uniform_texel_buffer_count
245                    {
246                        let item = cache.swap_remove(idx);
247
248                        return Some(Lease::new(cache_ref.clone(), item));
249                    }
250                }
251
252                None
253            }) {
254                return Ok(item);
255            }
256        }
257
258        debug!("Creating new {}", stringify!(DescriptorPool));
259
260        let item = DescriptorPool::create(&self.device, info)?;
261
262        Ok(Lease::new(cache_ref, item))
263    }
264}
265
266impl Pool<ImageInfo, Image> for FifoPool {
267    #[profiling::function]
268    fn resource(&mut self, info: ImageInfo) -> Result<Lease<Image>, DriverError> {
269        let cache_ref = Arc::downgrade(&self.image_cache);
270
271        {
272            profiling::scope!("check cache");
273
274            if let Some(item) = with_cache(&self.image_cache, |cache| {
275                // Look for a compatible image (same properties, superset of creation flags and
276                // usage flags)
277                for idx in 0..cache.len() {
278                    let item = unsafe { cache.get_unchecked(idx) };
279                    if item.info.array_layer_count == info.array_layer_count
280                        && item.info.alloc_dedicated == info.alloc_dedicated
281                        && item.info.depth == info.depth
282                        && item.info.format == info.format
283                        && item.info.height == info.height
284                        && item.info.mip_level_count == info.mip_level_count
285                        && item.info.sample_count == info.sample_count
286                        && item.info.sharing_mode == info.sharing_mode
287                        && item.info.tiling == info.tiling
288                        && item.info.image_type == info.image_type
289                        && item.info.width == info.width
290                        && item.info.flags.contains(info.flags)
291                        && item.info.usage.contains(info.usage)
292                    {
293                        let item = cache.swap_remove(idx);
294
295                        return Some(Lease::new(cache_ref.clone(), item));
296                    }
297                }
298
299                None
300            }) {
301                return Ok(item);
302            }
303        }
304
305        debug!("Creating new {}", stringify!(Image));
306
307        let item = Image::create(&self.device, info)?;
308
309        Ok(Lease::new(cache_ref, item))
310    }
311}
312
313impl Pool<RenderPassInfo, RenderPass> for FifoPool {
314    #[profiling::function]
315    fn resource(&mut self, info: RenderPassInfo) -> Result<Lease<RenderPass>, DriverError> {
316        let cache_ref = if let Some(cache) = self.render_pass_cache.get(&info) {
317            cache
318        } else {
319            // We tried to get the cache first in order to avoid this clone
320            self.render_pass_cache
321                .entry(info.clone())
322                .or_insert_with(PoolConfig::default_cache)
323        };
324        let item = with_cache(cache_ref, |cache| cache.pop())
325            .map(Ok)
326            .unwrap_or_else(|| {
327                debug!("Creating new {}", stringify!(RenderPass));
328
329                RenderPass::create(&self.device, info)
330            })?;
331
332        Ok(Lease::new(Arc::downgrade(cache_ref), item))
333    }
334}