1use alloc::vec::Vec;
2use arrayvec::ArrayVec;
3use ash::vk;
4use hashbrown::{HashMap, HashSet};
5
6const POOL_MIN_SETS: u32 = 64;
7const POOL_MAX_SETS: u32 = 512;
8
9#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
12pub struct DescriptorCounts {
13 pub sampler: u32,
14 pub sampled_image: u32,
15 pub storage_image: u32,
16 pub uniform_buffer: u32,
17 pub uniform_buffer_dynamic: u32,
18 pub storage_buffer: u32,
19 pub storage_buffer_dynamic: u32,
20 pub acceleration_structure: u32,
21}
22
23impl DescriptorCounts {
24 fn total(&self) -> u32 {
25 self.sampler
26 + self.sampled_image
27 + self.storage_image
28 + self.uniform_buffer
29 + self.uniform_buffer_dynamic
30 + self.storage_buffer
31 + self.storage_buffer_dynamic
32 + self.acceleration_structure
33 }
34}
35
36#[derive(Debug)]
37pub struct DescriptorSet {
38 raw: vk::DescriptorSet,
39 bucket_key: BucketKey,
40 pool_index: usize,
41}
42
43impl DescriptorSet {
44 pub fn raw(&self) -> vk::DescriptorSet {
45 self.raw
46 }
47}
48
49#[derive(Debug, PartialEq, Eq, Hash)]
50struct BucketKey {
51 counts: DescriptorCounts,
52 update_after_bind: bool,
53}
54
55struct Pool {
56 raw: vk::DescriptorPool,
57 capacity: u32,
58 available: u32,
59 fragmented: bool,
63}
64
65#[derive(Default)]
67struct Bucket {
68 layouts: HashSet<vk::DescriptorSetLayout>,
71 pools: Vec<Pool>,
72 available_sets: u32,
73 allocated_sets: u32,
74}
75
76impl Bucket {
77 fn create_pool(
78 &mut self,
79 device: &ash::Device,
80 key: &BucketKey,
81 capacity_hint: u32,
82 ) -> Result<(usize, &mut Pool), crate::DeviceError> {
83 let index = self.pools.len();
84 let pool = create_descriptor_pool(device, key, capacity_hint)?;
85 self.available_sets += pool.capacity;
86 self.pools.push(pool);
87 Ok((index, self.pools.last_mut().unwrap()))
88 }
89}
90
91pub struct DescriptorAllocator {
92 buckets: HashMap<BucketKey, Bucket>,
93 max_update_after_bind_descriptors_in_all_pools: u32,
94 update_after_bind_descriptors_in_all_pools: u32,
95}
96
97impl super::BindGroupLayout {
98 fn bucket_key(&self) -> BucketKey {
99 let update_after_bind = self.contains_binding_arrays;
100 let counts = self.desc_count.clone();
101 BucketKey {
102 counts,
103 update_after_bind,
104 }
105 }
106}
107
108impl DescriptorAllocator {
109 pub fn new(max_update_after_bind_descriptors_in_all_pools: u32) -> Self {
110 DescriptorAllocator {
111 buckets: HashMap::default(),
112 max_update_after_bind_descriptors_in_all_pools,
113 update_after_bind_descriptors_in_all_pools: 0,
114 }
115 }
116
117 pub fn register_layout(
118 &mut self,
119 device: &ash::Device,
120 layout: &super::BindGroupLayout,
121 ) -> Result<(), crate::DeviceError> {
122 let key = layout.bucket_key();
123 let bucket = match self.buckets.entry(key) {
124 hashbrown::hash_map::Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
125 hashbrown::hash_map::Entry::Vacant(vacant_entry) => {
126 let mut bucket = Bucket::default();
127 bucket.create_pool(device, vacant_entry.key(), POOL_MIN_SETS)?;
130 vacant_entry.insert(bucket)
131 }
132 };
133
134 assert!(bucket.layouts.insert(layout.raw));
135
136 Ok(())
137 }
138
139 pub fn unregister_layout(&mut self, device: &ash::Device, layout: &super::BindGroupLayout) {
140 let key = layout.bucket_key();
141 let bucket = self.buckets.get_mut(&key).unwrap();
142
143 assert!(bucket.layouts.remove(&layout.raw));
144
145 if bucket.layouts.is_empty() {
146 let bucket = self.buckets.remove(&key).unwrap();
148 for pool in bucket.pools {
149 assert_eq!(
150 pool.available, pool.capacity,
151 "pool is not empty, at least one DescriptorSet has not been freed"
152 );
153 unsafe { device.destroy_descriptor_pool(pool.raw, None) };
154 }
155 }
156 }
157
158 pub unsafe fn alloc(
159 &mut self,
160 device: &ash::Device,
161 layout: &super::BindGroupLayout,
162 ) -> Result<DescriptorSet, crate::DeviceError> {
163 let update_after_bind = layout.contains_binding_arrays;
164 let total_descriptors = layout.desc_count.total();
165
166 if update_after_bind
167 && self.max_update_after_bind_descriptors_in_all_pools
168 - self.update_after_bind_descriptors_in_all_pools
169 < total_descriptors
170 {
171 return Err(crate::DeviceError::OutOfMemory);
172 }
173
174 let key = layout.bucket_key();
175 let bucket = self.buckets.get_mut(&key).unwrap();
176 let capacity_hint = bucket.allocated_sets;
177
178 let mut candidate = bucket
182 .pools
183 .iter()
184 .position(|pool| !pool.fragmented && pool.available != 0);
185
186 let (raw, pool_index) = loop {
187 let index = match candidate {
188 Some(index) => index,
189 None => bucket.create_pool(device, &key, capacity_hint)?.0,
190 };
191 let created_pool = candidate.is_none();
192
193 let vk_info = vk::DescriptorSetAllocateInfo::default()
194 .descriptor_pool(bucket.pools[index].raw)
195 .set_layouts(core::slice::from_ref(&layout.raw));
196
197 match unsafe { device.allocate_descriptor_sets(&vk_info) } {
215 Ok(sets) => break (sets[0], index),
216 Err(vk::Result::ERROR_FRAGMENTED_POOL | vk::Result::ERROR_OUT_OF_POOL_MEMORY) => {
217 if created_pool {
218 let pool = bucket.pools.pop().unwrap();
221 bucket.available_sets -= pool.capacity;
222 unsafe { device.destroy_descriptor_pool(pool.raw, None) };
223 log::error!(
224 "descriptor set allocation from a fresh pool failed unexpectedly"
225 );
226 return Err(crate::DeviceError::OutOfMemory);
227 }
228
229 let pool = &bucket.pools[index];
234 if pool.available == pool.capacity {
235 let reset_result = unsafe {
236 device.reset_descriptor_pool(
237 pool.raw,
238 vk::DescriptorPoolResetFlags::empty(),
239 )
240 };
241 if reset_result.is_ok() {
242 match unsafe { device.allocate_descriptor_sets(&vk_info) } {
243 Ok(sets) => break (sets[0], index),
244 Err(
245 vk::Result::ERROR_FRAGMENTED_POOL
246 | vk::Result::ERROR_OUT_OF_POOL_MEMORY,
247 ) => {}
248 Err(err) => return Err(super::map_host_device_oom_err(err)),
249 }
250 }
251 }
252
253 let available = bucket.pools[index].available;
254 let capacity = bucket.pools[index].capacity;
255 bucket.pools[index].fragmented = true;
256 bucket.available_sets -= available;
257 log::debug!(
258 "Retiring fragmented descriptor pool ({available}/{capacity} sets free), retrying on another pool"
259 );
260 candidate = bucket
261 .pools
262 .iter()
263 .position(|pool| !pool.fragmented && pool.available != 0);
264 }
265 Err(err) => return Err(super::map_host_device_oom_err(err)),
266 }
267 };
268
269 let pool = &mut bucket.pools[pool_index];
270 pool.available -= 1;
271 bucket.available_sets -= 1;
272 bucket.allocated_sets += 1;
273 if update_after_bind {
274 self.update_after_bind_descriptors_in_all_pools += total_descriptors;
275 }
276
277 Ok(DescriptorSet {
278 raw,
279 bucket_key: key,
280 pool_index,
281 })
282 }
283
284 pub unsafe fn free(&mut self, device: &ash::Device, set: DescriptorSet) {
285 let bucket = self.buckets.get_mut(&set.bucket_key).unwrap();
286 let pool = bucket.pools.get_mut(set.pool_index).unwrap();
287
288 let result =
289 unsafe { device.free_descriptor_sets(pool.raw, core::slice::from_ref(&set.raw())) };
290 if let Err(err) = result {
291 panic!("vkFreeDescriptorSets error: {err}, please report this error");
298 }
299
300 pool.available += 1;
301
302 if !pool.fragmented {
303 bucket.available_sets += 1;
304 } else if pool.available == pool.capacity {
305 let reset_result = unsafe {
310 device.reset_descriptor_pool(pool.raw, vk::DescriptorPoolResetFlags::empty())
311 };
312 if reset_result.is_ok() {
313 pool.fragmented = false;
314 bucket.available_sets += pool.capacity;
315 }
316 }
317 bucket.allocated_sets -= 1;
318 if set.bucket_key.update_after_bind {
319 self.update_after_bind_descriptors_in_all_pools -= set.bucket_key.counts.total();
320 }
321
322 let last = bucket.pools.last().unwrap();
328 let last_available = last.available;
329 let last_capacity = last.capacity;
330 let last_contribution = if last.fragmented { 0 } else { last_capacity };
333 if last_available == last_capacity
334 && bucket.available_sets - last_contribution > last_capacity / 4
335 {
336 let pool = bucket.pools.pop().unwrap();
337 unsafe { device.destroy_descriptor_pool(pool.raw, None) };
338 bucket.available_sets -= last_contribution;
339 }
340 }
341}
342
343impl Drop for DescriptorAllocator {
344 fn drop(&mut self) {
345 if !std::thread::panicking() {
346 assert!(
347 self.buckets.is_empty(),
348 "buckets are not empty, at least one BGL has not been unregistered"
349 )
350 }
351 }
352}
353
354fn create_descriptor_pool(
355 device: &ash::Device,
356 key: &BucketKey,
357 capacity_hint: u32,
358) -> Result<Pool, crate::DeviceError> {
359 let counts = &key.counts;
360
361 const NR_OF_DESCRIPTOR_TYPES: usize = 8;
362
363 use vk::DescriptorType as Dt;
364 let counts: [_; NR_OF_DESCRIPTOR_TYPES] = [
365 (Dt::SAMPLER, counts.sampler),
366 (Dt::SAMPLED_IMAGE, counts.sampled_image),
367 (Dt::STORAGE_IMAGE, counts.storage_image),
368 (Dt::UNIFORM_BUFFER, counts.uniform_buffer),
369 (Dt::UNIFORM_BUFFER_DYNAMIC, counts.uniform_buffer_dynamic),
370 (Dt::STORAGE_BUFFER, counts.storage_buffer),
371 (Dt::STORAGE_BUFFER_DYNAMIC, counts.storage_buffer_dynamic),
372 (
373 Dt::ACCELERATION_STRUCTURE_KHR,
374 counts.acceleration_structure,
375 ),
376 ];
377
378 let mut capacity = capacity_hint
380 .clamp(POOL_MIN_SETS, POOL_MAX_SETS)
381 .next_power_of_two();
382
383 for (_, count) in counts {
387 capacity = (u32::MAX / count.max(1)).min(capacity);
388 }
389
390 let pool_sizes = counts
391 .into_iter()
392 .filter(|&(_, count)| count != 0)
393 .map(|(ty, count)| vk::DescriptorPoolSize {
394 ty,
395 descriptor_count: count * capacity,
396 })
397 .collect::<ArrayVec<_, NR_OF_DESCRIPTOR_TYPES>>();
398
399 let mut pool_sizes = pool_sizes;
403 if pool_sizes.is_empty() {
404 pool_sizes.push(vk::DescriptorPoolSize {
405 ty: Dt::SAMPLER,
406 descriptor_count: 1,
407 });
408 }
409
410 let mut flags = vk::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET;
411 if key.update_after_bind {
412 flags |= vk::DescriptorPoolCreateFlags::UPDATE_AFTER_BIND;
413 };
414
415 let vk_info = vk::DescriptorPoolCreateInfo::default()
416 .flags(flags)
417 .max_sets(capacity)
418 .pool_sizes(&pool_sizes);
419
420 let raw = unsafe { device.create_descriptor_pool(&vk_info, None) }
421 .map_err(super::map_host_device_oom_and_fragmentation_err)?;
422
423 Ok(Pool {
424 raw,
425 capacity,
426 available: capacity,
427 fragmented: false,
428 })
429}