1 // Copyright (c) 2021 The vulkano developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 //! In the Vulkan API, descriptor sets must be allocated from *descriptor pools*.
11 //!
12 //! A descriptor pool holds and manages the memory of one or more descriptor sets. If you destroy a
13 //! descriptor pool, all of its descriptor sets are automatically destroyed.
14 //!
15 //! In vulkano, creating a descriptor set requires passing an implementation of the
16 //! [`DescriptorSetAllocator`] trait, which you can implement yourself or use the vulkano-provided
17 //! [`StandardDescriptorSetAllocator`].
18 
19 use self::sorted_map::SortedMap;
20 use super::{
21     layout::DescriptorSetLayout,
22     pool::{
23         DescriptorPool, DescriptorPoolAllocError, DescriptorPoolCreateInfo,
24         DescriptorSetAllocateInfo,
25     },
26     sys::UnsafeDescriptorSet,
27 };
28 use crate::{
29     device::{Device, DeviceOwned},
30     OomError,
31 };
32 use crossbeam_queue::ArrayQueue;
33 use std::{cell::UnsafeCell, mem::ManuallyDrop, num::NonZeroU64, sync::Arc, thread};
34 use thread_local::ThreadLocal;
35 
36 const MAX_POOLS: usize = 32;
37 
38 const MAX_SETS: usize = 256;
39 
40 /// Types that manage the memory of descriptor sets.
41 ///
42 /// # Safety
43 ///
44 /// A Vulkan descriptor pool must be externally synchronized as if it owned the descriptor sets that
45 /// were allocated from it. This includes allocating from the pool, freeing from the pool and
46 /// resetting the pool or individual descriptor sets. The implementation of `DescriptorSetAllocator`
47 /// is expected to manage this.
48 ///
49 /// The destructor of the [`DescriptorSetAlloc`] is expected to free the descriptor set, reset the
50 /// descriptor set, or add it to a pool so that it gets reused. If the implementation frees or
51 /// resets the descriptor set, it must not forget that this operation must be externally
52 /// synchronized.
53 pub unsafe trait DescriptorSetAllocator: DeviceOwned {
54     /// Object that represented an allocated descriptor set.
55     ///
56     /// The destructor of this object should free the descriptor set.
57     type Alloc: DescriptorSetAlloc;
58 
59     /// Allocates a descriptor set.
allocate( &self, layout: &Arc<DescriptorSetLayout>, variable_descriptor_count: u32, ) -> Result<Self::Alloc, OomError>60     fn allocate(
61         &self,
62         layout: &Arc<DescriptorSetLayout>,
63         variable_descriptor_count: u32,
64     ) -> Result<Self::Alloc, OomError>;
65 }
66 
67 /// An allocated descriptor set.
68 pub trait DescriptorSetAlloc: Send + Sync {
69     /// Returns the inner unsafe descriptor set object.
inner(&self) -> &UnsafeDescriptorSet70     fn inner(&self) -> &UnsafeDescriptorSet;
71 
72     /// Returns the inner unsafe descriptor set object.
inner_mut(&mut self) -> &mut UnsafeDescriptorSet73     fn inner_mut(&mut self) -> &mut UnsafeDescriptorSet;
74 }
75 
76 /// Standard implementation of a descriptor set allocator.
77 ///
78 /// The intended way to use this allocator is to have one that is used globally for the duration of
79 /// the program, in order to avoid creating and destroying [`DescriptorPool`]s, as that is
80 /// expensive. Alternatively, you can have one locally on a thread for the duration of the thread.
81 ///
82 /// Internally, this allocator uses one or more `DescriptorPool`s per descriptor set layout per
83 /// thread, using Thread-Local Storage. When a thread first allocates, an entry is reserved for the
84 /// thread and descriptor set layout combination. After a thread exits and the allocator wasn't
85 /// dropped yet, its entries are freed, but the pools it used are not dropped. The next time a new
86 /// thread allocates for the first time, the entries are reused along with the pools. If all
87 /// threads drop their reference to the allocator, all entries along with the allocator are
88 /// dropped, even if the threads didn't exit yet, which is why you should keep the allocator alive
89 /// for as long as you need to allocate so that the pools can keep being reused.
90 ///
91 /// This allocator only needs to lock when a thread first allocates or when a thread that
92 /// previously allocated exits. In all other cases, allocation is lock-free.
93 ///
94 /// [`DescriptorPool`]: crate::descriptor_set::pool::DescriptorPool
95 #[derive(Debug)]
96 pub struct StandardDescriptorSetAllocator {
97     device: Arc<Device>,
98     pools: ThreadLocal<UnsafeCell<SortedMap<NonZeroU64, Entry>>>,
99 }
100 
101 #[derive(Debug)]
102 enum Entry {
103     Fixed(FixedEntry),
104     Variable(VariableEntry),
105 }
106 
107 // This is needed because of the blanket impl of `Send` on `Arc<T>`, which requires that `T` is
108 // `Send + Sync`. `FixedPool` and `VariablePool` are `Send + !Sync` because `DescriptorPool` is
109 // `!Sync`. That's fine however because we never access the `DescriptorPool` concurrently.
110 unsafe impl Send for Entry {}
111 
112 impl StandardDescriptorSetAllocator {
113     /// Creates a new `StandardDescriptorSetAllocator`.
114     #[inline]
new(device: Arc<Device>) -> StandardDescriptorSetAllocator115     pub fn new(device: Arc<Device>) -> StandardDescriptorSetAllocator {
116         StandardDescriptorSetAllocator {
117             device,
118             pools: ThreadLocal::new(),
119         }
120     }
121 
122     /// Clears the entry for the given descriptor set layout and the current thread. This does not
123     /// mean that the pools are dropped immediately. A pool is kept alive for as long as descriptor
124     /// sets allocated from it exist.
125     ///
126     /// This has no effect if the entry was not initialized yet.
127     #[inline]
clear(&self, layout: &Arc<DescriptorSetLayout>)128     pub fn clear(&self, layout: &Arc<DescriptorSetLayout>) {
129         unsafe { &mut *self.pools.get_or(Default::default).get() }.remove(layout.id())
130     }
131 
132     /// Clears all entries for the current thread. This does not mean that the pools are dropped
133     /// immediately. A pool is kept alive for as long as descriptor sets allocated from it exist.
134     ///
135     /// This has no effect if no entries were initialized yet.
136     #[inline]
clear_all(&self)137     pub fn clear_all(&self) {
138         unsafe { *self.pools.get_or(Default::default).get() = SortedMap::default() };
139     }
140 }
141 
142 unsafe impl DescriptorSetAllocator for StandardDescriptorSetAllocator {
143     type Alloc = StandardDescriptorSetAlloc;
144 
145     /// Allocates a descriptor set.
146     ///
147     /// # Panics
148     ///
149     /// - Panics if the provided `layout` is for push descriptors rather than regular descriptor
150     ///   sets.
151     /// - Panics if the provided `variable_descriptor_count` is greater than the maximum number of
152     ///   variable count descriptors in the set.
153     #[inline]
allocate( &self, layout: &Arc<DescriptorSetLayout>, variable_descriptor_count: u32, ) -> Result<StandardDescriptorSetAlloc, OomError>154     fn allocate(
155         &self,
156         layout: &Arc<DescriptorSetLayout>,
157         variable_descriptor_count: u32,
158     ) -> Result<StandardDescriptorSetAlloc, OomError> {
159         assert!(
160             !layout.push_descriptor(),
161             "the provided descriptor set layout is for push descriptors, and cannot be used to \
162             build a descriptor set object",
163         );
164 
165         let max_count = layout.variable_descriptor_count();
166 
167         assert!(
168             variable_descriptor_count <= max_count,
169             "the provided variable_descriptor_count ({}) is greater than the maximum number of \
170             variable count descriptors in the set ({})",
171             variable_descriptor_count,
172             max_count,
173         );
174 
175         let pools = self.pools.get_or(Default::default);
176         let entry = unsafe { &mut *pools.get() }.get_or_try_insert(layout.id(), || {
177             if max_count == 0 {
178                 FixedEntry::new(layout.clone()).map(Entry::Fixed)
179             } else {
180                 VariableEntry::new(layout.clone()).map(Entry::Variable)
181             }
182         })?;
183 
184         match entry {
185             Entry::Fixed(entry) => entry.allocate(),
186             Entry::Variable(entry) => entry.allocate(variable_descriptor_count),
187         }
188     }
189 }
190 
191 unsafe impl DescriptorSetAllocator for Arc<StandardDescriptorSetAllocator> {
192     type Alloc = StandardDescriptorSetAlloc;
193 
194     #[inline]
allocate( &self, layout: &Arc<DescriptorSetLayout>, variable_descriptor_count: u32, ) -> Result<Self::Alloc, OomError>195     fn allocate(
196         &self,
197         layout: &Arc<DescriptorSetLayout>,
198         variable_descriptor_count: u32,
199     ) -> Result<Self::Alloc, OomError> {
200         (**self).allocate(layout, variable_descriptor_count)
201     }
202 }
203 
204 unsafe impl DeviceOwned for StandardDescriptorSetAllocator {
205     #[inline]
device(&self) -> &Arc<Device>206     fn device(&self) -> &Arc<Device> {
207         &self.device
208     }
209 }
210 
211 #[derive(Debug)]
212 struct FixedEntry {
213     // The `FixedPool` struct contains an actual Vulkan pool. Every time it is full we create
214     // a new pool and replace the current one with the new one.
215     pool: Arc<FixedPool>,
216     // The amount of sets available to use when we create a new Vulkan pool.
217     set_count: usize,
218     // The descriptor set layout that this pool is for.
219     layout: Arc<DescriptorSetLayout>,
220 }
221 
222 impl FixedEntry {
new(layout: Arc<DescriptorSetLayout>) -> Result<Self, OomError>223     fn new(layout: Arc<DescriptorSetLayout>) -> Result<Self, OomError> {
224         Ok(FixedEntry {
225             pool: FixedPool::new(&layout, MAX_SETS)?,
226             set_count: MAX_SETS,
227             layout,
228         })
229     }
230 
allocate(&mut self) -> Result<StandardDescriptorSetAlloc, OomError>231     fn allocate(&mut self) -> Result<StandardDescriptorSetAlloc, OomError> {
232         let inner = if let Some(inner) = self.pool.reserve.pop() {
233             inner
234         } else {
235             self.set_count *= 2;
236             self.pool = FixedPool::new(&self.layout, self.set_count)?;
237 
238             self.pool.reserve.pop().unwrap()
239         };
240 
241         Ok(StandardDescriptorSetAlloc {
242             inner: ManuallyDrop::new(inner),
243             parent: AllocParent::Fixed(self.pool.clone()),
244         })
245     }
246 }
247 
248 #[derive(Debug)]
249 struct FixedPool {
250     // The actual Vulkan descriptor pool. This field isn't actually used anywhere, but we need to
251     // keep the pool alive in order to keep the descriptor sets valid.
252     _inner: DescriptorPool,
253     // List of descriptor sets. When `alloc` is called, a descriptor will be extracted from this
254     // list. When a `SingleLayoutPoolAlloc` is dropped, its descriptor set is put back in this list.
255     reserve: ArrayQueue<UnsafeDescriptorSet>,
256 }
257 
258 impl FixedPool {
new(layout: &Arc<DescriptorSetLayout>, set_count: usize) -> Result<Arc<Self>, OomError>259     fn new(layout: &Arc<DescriptorSetLayout>, set_count: usize) -> Result<Arc<Self>, OomError> {
260         let inner = DescriptorPool::new(
261             layout.device().clone(),
262             DescriptorPoolCreateInfo {
263                 max_sets: set_count as u32,
264                 pool_sizes: layout
265                     .descriptor_counts()
266                     .iter()
267                     .map(|(&ty, &count)| (ty, count * set_count as u32))
268                     .collect(),
269                 ..Default::default()
270             },
271         )?;
272 
273         let allocate_infos = (0..set_count).map(|_| DescriptorSetAllocateInfo {
274             layout,
275             variable_descriptor_count: 0,
276         });
277 
278         let reserve = match unsafe { inner.allocate_descriptor_sets(allocate_infos) } {
279             Ok(allocs) => {
280                 let reserve = ArrayQueue::new(set_count);
281                 for alloc in allocs {
282                     let _ = reserve.push(alloc);
283                 }
284 
285                 reserve
286             }
287             Err(DescriptorPoolAllocError::OutOfHostMemory) => {
288                 return Err(OomError::OutOfHostMemory);
289             }
290             Err(DescriptorPoolAllocError::OutOfDeviceMemory) => {
291                 return Err(OomError::OutOfDeviceMemory);
292             }
293             Err(DescriptorPoolAllocError::FragmentedPool) => {
294                 // This can't happen as we don't free individual sets.
295                 unreachable!();
296             }
297             Err(DescriptorPoolAllocError::OutOfPoolMemory) => {
298                 // We created the pool with an exact size.
299                 unreachable!();
300             }
301         };
302 
303         Ok(Arc::new(FixedPool {
304             _inner: inner,
305             reserve,
306         }))
307     }
308 }
309 
310 #[derive(Debug)]
311 struct VariableEntry {
312     // The `VariablePool` struct contains an actual Vulkan pool. Every time it is full
313     // we grab one from the reserve, or create a new pool if there are none.
314     pool: Arc<VariablePool>,
315     // When a `VariablePool` is dropped, it returns its Vulkan pool here for reuse.
316     reserve: Arc<ArrayQueue<DescriptorPool>>,
317     // The descriptor set layout that this pool is for.
318     layout: Arc<DescriptorSetLayout>,
319     // The number of sets currently allocated from the Vulkan pool.
320     allocations: usize,
321 }
322 
323 impl VariableEntry {
new(layout: Arc<DescriptorSetLayout>) -> Result<Self, OomError>324     fn new(layout: Arc<DescriptorSetLayout>) -> Result<Self, OomError> {
325         let reserve = Arc::new(ArrayQueue::new(MAX_POOLS));
326 
327         Ok(VariableEntry {
328             pool: VariablePool::new(&layout, reserve.clone())?,
329             reserve,
330             layout,
331             allocations: 0,
332         })
333     }
334 
allocate( &mut self, variable_descriptor_count: u32, ) -> Result<StandardDescriptorSetAlloc, OomError>335     fn allocate(
336         &mut self,
337         variable_descriptor_count: u32,
338     ) -> Result<StandardDescriptorSetAlloc, OomError> {
339         if self.allocations >= MAX_SETS {
340             self.pool = if let Some(inner) = self.reserve.pop() {
341                 Arc::new(VariablePool {
342                     inner: ManuallyDrop::new(inner),
343                     reserve: self.reserve.clone(),
344                 })
345             } else {
346                 VariablePool::new(&self.layout, self.reserve.clone())?
347             };
348             self.allocations = 0;
349         }
350 
351         let allocate_info = DescriptorSetAllocateInfo {
352             layout: &self.layout,
353             variable_descriptor_count,
354         };
355 
356         let inner = match unsafe { self.pool.inner.allocate_descriptor_sets([allocate_info]) } {
357             Ok(mut sets) => sets.next().unwrap(),
358             Err(DescriptorPoolAllocError::OutOfHostMemory) => {
359                 return Err(OomError::OutOfHostMemory);
360             }
361             Err(DescriptorPoolAllocError::OutOfDeviceMemory) => {
362                 return Err(OomError::OutOfDeviceMemory);
363             }
364             Err(DescriptorPoolAllocError::FragmentedPool) => {
365                 // This can't happen as we don't free individual sets.
366                 unreachable!();
367             }
368             Err(DescriptorPoolAllocError::OutOfPoolMemory) => {
369                 // We created the pool to fit the maximum variable descriptor count.
370                 unreachable!();
371             }
372         };
373 
374         self.allocations += 1;
375 
376         Ok(StandardDescriptorSetAlloc {
377             inner: ManuallyDrop::new(inner),
378             parent: AllocParent::Variable(self.pool.clone()),
379         })
380     }
381 }
382 
383 #[derive(Debug)]
384 struct VariablePool {
385     // The actual Vulkan descriptor pool.
386     inner: ManuallyDrop<DescriptorPool>,
387     // Where we return the Vulkan descriptor pool in our `Drop` impl.
388     reserve: Arc<ArrayQueue<DescriptorPool>>,
389 }
390 
391 impl VariablePool {
new( layout: &Arc<DescriptorSetLayout>, reserve: Arc<ArrayQueue<DescriptorPool>>, ) -> Result<Arc<Self>, OomError>392     fn new(
393         layout: &Arc<DescriptorSetLayout>,
394         reserve: Arc<ArrayQueue<DescriptorPool>>,
395     ) -> Result<Arc<Self>, OomError> {
396         DescriptorPool::new(
397             layout.device().clone(),
398             DescriptorPoolCreateInfo {
399                 max_sets: MAX_SETS as u32,
400                 pool_sizes: layout
401                     .descriptor_counts()
402                     .iter()
403                     .map(|(&ty, &count)| (ty, count * MAX_SETS as u32))
404                     .collect(),
405                 ..Default::default()
406             },
407         )
408         .map(|inner| {
409             Arc::new(Self {
410                 inner: ManuallyDrop::new(inner),
411                 reserve,
412             })
413         })
414     }
415 }
416 
417 impl Drop for VariablePool {
drop(&mut self)418     fn drop(&mut self) {
419         let inner = unsafe { ManuallyDrop::take(&mut self.inner) };
420 
421         if thread::panicking() {
422             return;
423         }
424 
425         unsafe { inner.reset() }.unwrap();
426 
427         // If there is not enough space in the reserve, we destroy the pool. The only way this can
428         // happen is if something is resource hogging, forcing new pools to be created such that
429         // the number exceeds `MAX_POOLS`, and then drops them all at once.
430         let _ = self.reserve.push(inner);
431     }
432 }
433 
434 /// A descriptor set allocated from a [`StandardDescriptorSetAllocator`].
435 #[derive(Debug)]
436 pub struct StandardDescriptorSetAlloc {
437     // The actual descriptor set.
438     inner: ManuallyDrop<UnsafeDescriptorSet>,
439     // The pool where we allocated from. Needed for our `Drop` impl.
440     parent: AllocParent,
441 }
442 
443 #[derive(Debug)]
444 enum AllocParent {
445     Fixed(Arc<FixedPool>),
446     Variable(Arc<VariablePool>),
447 }
448 
449 // This is needed because of the blanket impl of `Send` on `Arc<T>`, which requires that `T` is
450 // `Send + Sync`. `FixedPool` and `VariablePool` are `Send + !Sync` because `DescriptorPool` is
451 // `!Sync`. That's fine however because we never access the `DescriptorPool` concurrently.
452 unsafe impl Send for StandardDescriptorSetAlloc {}
453 unsafe impl Sync for StandardDescriptorSetAlloc {}
454 
455 impl DescriptorSetAlloc for StandardDescriptorSetAlloc {
456     #[inline]
inner(&self) -> &UnsafeDescriptorSet457     fn inner(&self) -> &UnsafeDescriptorSet {
458         &self.inner
459     }
460 
461     #[inline]
inner_mut(&mut self) -> &mut UnsafeDescriptorSet462     fn inner_mut(&mut self) -> &mut UnsafeDescriptorSet {
463         &mut self.inner
464     }
465 }
466 
467 impl Drop for StandardDescriptorSetAlloc {
468     #[inline]
drop(&mut self)469     fn drop(&mut self) {
470         let inner = unsafe { ManuallyDrop::take(&mut self.inner) };
471 
472         match &self.parent {
473             AllocParent::Fixed(pool) => {
474                 let _ = pool.reserve.push(inner);
475             }
476             AllocParent::Variable(_) => {}
477         }
478     }
479 }
480 
481 mod sorted_map {
482     use smallvec::SmallVec;
483 
484     /// Minimal implementation of a `SortedMap`. This outperforms both a [`BTreeMap`] and
485     /// [`HashMap`] for small numbers of elements. In Vulkan, having too many descriptor set
486     /// layouts is highly discouraged, which is why this optimization makes sense.
487     #[derive(Debug)]
488     pub(super) struct SortedMap<K, V> {
489         inner: SmallVec<[(K, V); 8]>,
490     }
491 
492     impl<K, V> Default for SortedMap<K, V> {
default() -> Self493         fn default() -> Self {
494             Self {
495                 inner: SmallVec::default(),
496             }
497         }
498     }
499 
500     impl<K: Ord + Copy, V> SortedMap<K, V> {
get_or_try_insert<E>( &mut self, key: K, f: impl FnOnce() -> Result<V, E>, ) -> Result<&mut V, E>501         pub fn get_or_try_insert<E>(
502             &mut self,
503             key: K,
504             f: impl FnOnce() -> Result<V, E>,
505         ) -> Result<&mut V, E> {
506             match self.inner.binary_search_by_key(&key, |&(k, _)| k) {
507                 Ok(index) => Ok(&mut self.inner[index].1),
508                 Err(index) => {
509                     self.inner.insert(index, (key, f()?));
510                     Ok(&mut self.inner[index].1)
511                 }
512             }
513         }
514 
remove(&mut self, key: K)515         pub fn remove(&mut self, key: K) {
516             if let Ok(index) = self.inner.binary_search_by_key(&key, |&(k, _)| k) {
517                 self.inner.remove(index);
518             }
519         }
520     }
521 }
522 
523 #[cfg(test)]
524 mod tests {
525     use super::*;
526     use crate::{
527         descriptor_set::layout::{
528             DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType,
529         },
530         shader::ShaderStages,
531         VulkanObject,
532     };
533     use std::thread;
534 
535     #[test]
threads_use_different_pools()536     fn threads_use_different_pools() {
537         let (device, _) = gfx_dev_and_queue!();
538 
539         let layout = DescriptorSetLayout::new(
540             device.clone(),
541             DescriptorSetLayoutCreateInfo {
542                 bindings: [(
543                     0,
544                     DescriptorSetLayoutBinding {
545                         stages: ShaderStages::all_graphics(),
546                         ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::UniformBuffer)
547                     },
548                 )]
549                 .into(),
550                 ..Default::default()
551             },
552         )
553         .unwrap();
554 
555         let allocator = StandardDescriptorSetAllocator::new(device);
556 
557         let pool1 =
558             if let AllocParent::Fixed(pool) = &allocator.allocate(&layout, 0).unwrap().parent {
559                 pool._inner.handle()
560             } else {
561                 unreachable!()
562             };
563 
564         thread::spawn(move || {
565             let pool2 =
566                 if let AllocParent::Fixed(pool) = &allocator.allocate(&layout, 0).unwrap().parent {
567                     pool._inner.handle()
568                 } else {
569                     unreachable!()
570                 };
571             assert_ne!(pool1, pool2);
572         })
573         .join()
574         .unwrap();
575     }
576 }
577