Skip to main content

vk_graph/pool/
fifo.rs

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