screen_13/pool/
fifo.rs

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