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