1use std::{
2 collections::{BTreeSet, HashMap},
3 ops::Range,
4 ptr::NonNull,
5 thread,
6};
7
8use {
9 crate::{
10 allocator::{Allocator, Kind},
11 block::Block,
12 mapping::*,
13 memory::*,
14 util::*,
15 },
16 gfx_hal::{device::Device as _, Backend},
17 hibitset::{BitSet, BitSetLike as _},
18};
19
20#[derive(Debug)]
22pub struct DynamicBlock<B: Backend> {
23 block_index: u32,
24 chunk_index: u32,
25 count: u32,
26 memory: *const Memory<B>,
27 ptr: Option<NonNull<u8>>,
28 range: Range<u64>,
29 relevant: relevant::Relevant,
30}
31
32unsafe impl<B> Send for DynamicBlock<B> where B: Backend {}
33unsafe impl<B> Sync for DynamicBlock<B> where B: Backend {}
34
35impl<B> DynamicBlock<B>
36where
37 B: Backend,
38{
39 fn shared_memory(&self) -> &Memory<B> {
40 unsafe { &*self.memory }
42 }
43
44 fn size(&self) -> u64 {
45 self.range.end - self.range.start
46 }
47
48 fn dispose(self) {
49 self.relevant.dispose();
50 }
51}
52
53impl<B> Block<B> for DynamicBlock<B>
54where
55 B: Backend,
56{
57 #[inline]
58 fn properties(&self) -> gfx_hal::memory::Properties {
59 self.shared_memory().properties()
60 }
61
62 #[inline]
63 fn memory(&self) -> &B::Memory {
64 self.shared_memory().raw()
65 }
66
67 #[inline]
68 fn range(&self) -> Range<u64> {
69 self.range.clone()
70 }
71
72 #[inline]
73 fn map<'a>(
74 &'a mut self,
75 _device: &B::Device,
76 range: Range<u64>,
77 ) -> Result<MappedRange<'a, B>, gfx_hal::device::MapError> {
78 debug_assert!(
79 range.start < range.end,
80 "Memory mapping region must have valid size"
81 );
82 if !self.shared_memory().host_visible() {
83 return Err(gfx_hal::device::MapError::MappingFailed);
85 }
86
87 if let Some(ptr) = self.ptr {
88 if let Some((ptr, range)) = mapped_sub_range(ptr, self.range.clone(), range) {
89 let mapping = unsafe { MappedRange::from_raw(self.shared_memory(), ptr, range) };
90 Ok(mapping)
91 } else {
92 Err(gfx_hal::device::MapError::OutOfBounds)
93 }
94 } else {
95 Err(gfx_hal::device::MapError::MappingFailed)
96 }
97 }
98
99 #[inline]
100 fn unmap(&mut self, _device: &B::Device) {}
101}
102
103#[derive(Clone, Copy, Debug)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct DynamicConfig {
107 pub block_size_granularity: u64,
109
110 pub max_chunk_size: u64,
113
114 pub min_device_allocation: u64,
116}
117
118#[derive(Debug)]
122pub struct DynamicAllocator<B: Backend> {
123 memory_type: gfx_hal::MemoryTypeId,
125
126 memory_properties: gfx_hal::memory::Properties,
128
129 block_size_granularity: u64,
131
132 max_chunk_size: u64,
134
135 min_device_allocation: u64,
137
138 sizes: HashMap<u64, SizeEntry<B>>,
140
141 chunks: BTreeSet<u64>,
143}
144
145unsafe impl<B> Send for DynamicAllocator<B> where B: Backend {}
146unsafe impl<B> Sync for DynamicAllocator<B> where B: Backend {}
147
148#[derive(Debug)]
149struct SizeEntry<B: Backend> {
150 total_blocks: u64,
152
153 ready_chunks: BitSet,
155
156 chunks: slab::Slab<Chunk<B>>,
158}
159
160impl<B> Default for SizeEntry<B>
161where
162 B: Backend,
163{
164 fn default() -> Self {
165 SizeEntry {
166 chunks: Default::default(),
167 total_blocks: 0,
168 ready_chunks: Default::default(),
169 }
170 }
171}
172
173const MAX_BLOCKS_PER_CHUNK: u32 = 64;
174const MIN_BLOCKS_PER_CHUNK: u32 = 8;
175
176impl<B> DynamicAllocator<B>
177where
178 B: Backend,
179{
180 pub fn new(
184 memory_type: gfx_hal::MemoryTypeId,
185 memory_properties: gfx_hal::memory::Properties,
186 config: DynamicConfig,
187 ) -> Self {
188 log::trace!(
189 "Create new allocator: type: '{:?}', properties: '{:#?}' config: '{:#?}'",
190 memory_type,
191 memory_properties,
192 config
193 );
194
195 assert!(
196 config.block_size_granularity.is_power_of_two(),
197 "Allocation granularity must be power of two"
198 );
199
200 assert!(
201 config.max_chunk_size.is_power_of_two(),
202 "Max chunk size must be power of two"
203 );
204
205 assert!(
206 config.min_device_allocation.is_power_of_two(),
207 "Min device allocation must be power of two"
208 );
209
210 assert!(
211 config.min_device_allocation <= config.max_chunk_size,
212 "Min device allocation must be less than or equalt to max chunk size"
213 );
214
215 if memory_properties.contains(gfx_hal::memory::Properties::CPU_VISIBLE) {
216 debug_assert!(
217 fits_usize(config.max_chunk_size),
218 "Max chunk size must fit usize for mapping"
219 );
220 }
221
222 DynamicAllocator {
223 memory_type,
224 memory_properties,
225 block_size_granularity: config.block_size_granularity,
226 max_chunk_size: config.max_chunk_size,
227 min_device_allocation: config.min_device_allocation,
228 sizes: HashMap::new(),
229 chunks: BTreeSet::new(),
230 }
231 }
232
233 pub fn max_allocation(&self) -> u64 {
235 self.max_chunk_size / MIN_BLOCKS_PER_CHUNK as u64
236 }
237
238 fn alloc_chunk_from_device(
240 &self,
241 device: &B::Device,
242 block_size: u64,
243 chunk_size: u64,
244 ) -> Result<Chunk<B>, gfx_hal::device::AllocationError> {
245 log::trace!(
246 "Allocate chunk of size: {} for blocks of size {} from device",
247 chunk_size,
248 block_size
249 );
250
251 let (memory, mapping) = unsafe {
253 let raw = device.allocate_memory(self.memory_type, chunk_size)?;
255
256 let mapping = if self
257 .memory_properties
258 .contains(gfx_hal::memory::Properties::CPU_VISIBLE)
259 {
260 log::trace!("Map new memory object");
261 match device.map_memory(&raw, 0..chunk_size) {
262 Ok(mapping) => Some(NonNull::new_unchecked(mapping)),
263 Err(gfx_hal::device::MapError::OutOfMemory(error)) => {
264 device.free_memory(raw);
265 return Err(error.into());
266 }
267 Err(_) => panic!("Unexpected mapping failure"),
268 }
269 } else {
270 None
271 };
272 let memory = Memory::from_raw(raw, chunk_size, self.memory_properties);
273 (memory, mapping)
274 };
275 Ok(Chunk::from_memory(block_size, memory, mapping))
276 }
277
278 fn alloc_chunk(
280 &mut self,
281 device: &B::Device,
282 block_size: u64,
283 total_blocks: u64,
284 ) -> Result<(Chunk<B>, u64), gfx_hal::device::AllocationError> {
285 log::trace!(
286 "Allocate chunk for blocks of size {} ({} total blocks allocated)",
287 block_size,
288 total_blocks
289 );
290
291 let min_chunk_size = MIN_BLOCKS_PER_CHUNK as u64 * block_size;
292 let min_size = min_chunk_size.min(total_blocks * block_size);
293 let max_chunk_size = MAX_BLOCKS_PER_CHUNK as u64 * block_size;
294
295 if min_size > self.max_allocation()
297 || (total_blocks < MIN_BLOCKS_PER_CHUNK as u64
298 && min_size >= self.min_device_allocation)
299 {
300 let chunk = self.alloc_chunk_from_device(device, block_size, min_size)?;
302 return Ok((chunk, min_size));
303 }
304
305 if let Some(&chunk_size) = self
306 .chunks
307 .range(min_chunk_size..=max_chunk_size)
308 .next_back()
309 {
310 let (block, allocated) = self.alloc_from_entry(device, chunk_size, 1, block_size)?;
312 Ok((Chunk::from_block(block_size, block), allocated))
313 } else {
314 let total_blocks = self.sizes[&block_size].total_blocks;
315 let chunk_size =
316 (max_chunk_size.min(min_chunk_size.max(total_blocks * block_size)) / 2 + 1)
317 .next_power_of_two();
318 let (block, allocated) = self.alloc_block(device, chunk_size, block_size)?;
319 Ok((Chunk::from_block(block_size, block), allocated))
320 }
321 }
322
323 fn alloc_from_chunk(
325 chunks: &mut slab::Slab<Chunk<B>>,
326 chunk_index: u32,
327 block_size: u64,
328 count: u32,
329 align: u64,
330 ) -> Option<DynamicBlock<B>> {
331 log::trace!(
332 "Allocate {} consecutive blocks of size {} from chunk {}",
333 count,
334 block_size,
335 chunk_index
336 );
337
338 let ref mut chunk = chunks[chunk_index as usize];
339 let block_index = chunk.acquire_blocks(count, block_size, align)?;
340 let block_range = chunk.blocks_range(block_size, block_index, count);
341
342 debug_assert_eq!((block_range.end - block_range.start) % count as u64, 0);
343
344 Some(DynamicBlock {
345 range: block_range.clone(),
346 memory: chunk.shared_memory(),
347 block_index,
348 chunk_index,
349 count,
350 ptr: chunk.mapping_ptr().map(|ptr| {
351 mapped_fitting_range(ptr, chunk.range(), block_range)
352 .expect("Block must be sub-range of chunk")
353 }),
354 relevant: relevant::Relevant,
355 })
356 }
357
358 fn alloc_from_entry(
360 &mut self,
361 device: &B::Device,
362 block_size: u64,
363 count: u32,
364 align: u64,
365 ) -> Result<(DynamicBlock<B>, u64), gfx_hal::device::AllocationError> {
366 log::trace!(
367 "Allocate {} consecutive blocks for size {} from the entry",
368 count,
369 block_size
370 );
371
372 debug_assert!(count < MIN_BLOCKS_PER_CHUNK);
373 let size_entry = self.sizes.entry(block_size).or_default();
374
375 for chunk_index in (&size_entry.ready_chunks).iter() {
376 if let Some(block) = Self::alloc_from_chunk(
377 &mut size_entry.chunks,
378 chunk_index,
379 block_size,
380 count,
381 align,
382 ) {
383 return Ok((block, 0));
384 }
385 }
386
387 if size_entry.chunks.vacant_entry().key() > max_chunks_per_size() {
388 return Err(gfx_hal::device::OutOfMemory::Host.into());
389 }
390
391 let total_blocks = size_entry.total_blocks;
392 let (chunk, allocated) = self.alloc_chunk(device, block_size, total_blocks)?;
393 let size_entry = self.sizes.entry(block_size).or_default();
394 let chunk_index = size_entry.chunks.insert(chunk) as u32;
395
396 let block = Self::alloc_from_chunk(
397 &mut size_entry.chunks,
398 chunk_index,
399 block_size,
400 count,
401 align,
402 )
403 .expect("New chunk should yield blocks");
404
405 if !size_entry.chunks[chunk_index as usize].is_exhausted() {
406 size_entry.ready_chunks.add(chunk_index);
407 }
408
409 Ok((block, allocated))
410 }
411
412 fn alloc_block(
414 &mut self,
415 device: &B::Device,
416 block_size: u64,
417 align: u64,
418 ) -> Result<(DynamicBlock<B>, u64), gfx_hal::device::AllocationError> {
419 log::trace!("Allocate block of size {}", block_size);
420
421 debug_assert_eq!(block_size % self.block_size_granularity, 0);
422 let size_entry = self.sizes.entry(block_size).or_default();
423 size_entry.total_blocks += 1;
424
425 let overhead = (MIN_BLOCKS_PER_CHUNK as u64 - 1) / size_entry.total_blocks;
426
427 if overhead >= 1 {
428 if let Some(&size) = self
429 .chunks
430 .range(block_size / 4..block_size * overhead)
431 .next()
432 {
433 return self.alloc_from_entry(
434 device,
435 size,
436 ((block_size - 1) / size + 1) as u32,
437 align,
438 );
439 }
440 }
441
442 if size_entry.total_blocks == MIN_BLOCKS_PER_CHUNK as u64 {
443 self.chunks.insert(block_size);
444 }
445
446 self.alloc_from_entry(device, block_size, 1, align)
447 }
448
449 fn free_chunk(&mut self, device: &B::Device, chunk: Chunk<B>, block_size: u64) -> u64 {
450 log::trace!("Free chunk: {:#?}", chunk);
451 assert!(chunk.is_unused(block_size));
452 match chunk.flavor {
453 ChunkFlavor::Dedicated(boxed, _) => {
454 let size = boxed.size();
455 unsafe {
456 if self
457 .memory_properties
458 .contains(gfx_hal::memory::Properties::CPU_VISIBLE)
459 {
460 log::trace!("Unmap memory: {:#?}", boxed);
461 device.unmap_memory(boxed.raw());
462 }
463 device.free_memory(boxed.into_raw());
464 }
465 size
466 }
467 ChunkFlavor::Dynamic(dynamic_block) => self.free(device, dynamic_block),
468 }
469 }
470
471 fn free_block(&mut self, device: &B::Device, block: DynamicBlock<B>) -> u64 {
472 log::trace!("Free block: {:#?}", block);
473
474 let block_size = block.size() / block.count as u64;
475 let ref mut size_entry = self
476 .sizes
477 .get_mut(&block_size)
478 .expect("Unable to get size entry from which block was allocated");
479 let chunk_index = block.chunk_index;
480 let ref mut chunk = size_entry.chunks[chunk_index as usize];
481 let block_index = block.block_index;
482 let count = block.count;
483 block.dispose();
484 chunk.release_blocks(block_index, count);
485 if chunk.is_unused(block_size) {
486 size_entry.ready_chunks.remove(chunk_index);
487 let chunk = size_entry.chunks.remove(chunk_index as usize);
488 self.free_chunk(device, chunk, block_size)
489 } else {
490 size_entry.ready_chunks.add(chunk_index);
491 0
492 }
493 }
494
495 pub fn dispose(self) {
497 if !thread::panicking() {
498 for (index, size) in self.sizes {
499 assert_eq!(size.chunks.len(), 0, "SizeEntry({}) is still used", index);
500 }
501 } else {
502 for (index, size) in self.sizes {
503 if size.chunks.len() != 0 {
504 log::error!("Memory leak: SizeEntry({}) is still used", index);
505 }
506 }
507 }
508 }
509}
510
511impl<B> Allocator<B> for DynamicAllocator<B>
512where
513 B: Backend,
514{
515 type Block = DynamicBlock<B>;
516
517 fn kind() -> Kind {
518 Kind::Dynamic
519 }
520
521 fn alloc(
522 &mut self,
523 device: &B::Device,
524 size: u64,
525 align: u64,
526 ) -> Result<(DynamicBlock<B>, u64), gfx_hal::device::AllocationError> {
527 debug_assert!(size <= self.max_allocation());
528 debug_assert!(align.is_power_of_two());
529 let aligned_size = ((size - 1) | (align - 1) | (self.block_size_granularity - 1)) + 1;
530
531 log::trace!(
532 "Allocate dynamic block: size: {}, align: {}, aligned size: {}, type: {}",
533 size,
534 align,
535 aligned_size,
536 self.memory_type.0
537 );
538
539 self.alloc_block(device, aligned_size, align)
540 }
541
542 fn free(&mut self, device: &B::Device, block: DynamicBlock<B>) -> u64 {
543 self.free_block(device, block)
544 }
545}
546
547#[derive(Debug)]
549enum ChunkFlavor<B: Backend> {
550 Dedicated(Box<Memory<B>>, Option<NonNull<u8>>),
552
553 Dynamic(DynamicBlock<B>),
555}
556
557#[derive(Debug)]
558struct Chunk<B: Backend> {
559 flavor: ChunkFlavor<B>,
560 blocks: u64,
561}
562
563impl<B> Chunk<B>
564where
565 B: Backend,
566{
567 fn from_memory(block_size: u64, memory: Memory<B>, mapping: Option<NonNull<u8>>) -> Self {
568 let blocks = memory.size() / block_size;
569 debug_assert!(blocks <= MAX_BLOCKS_PER_CHUNK as u64);
570
571 let high_bit = 1 << (blocks - 1);
572
573 Chunk {
574 flavor: ChunkFlavor::Dedicated(Box::new(memory), mapping),
575 blocks: (high_bit - 1) | high_bit,
576 }
577 }
578
579 fn from_block(block_size: u64, chunk_block: DynamicBlock<B>) -> Self {
580 let blocks = (chunk_block.size() / block_size).min(MAX_BLOCKS_PER_CHUNK as u64);
581
582 let high_bit = 1 << (blocks - 1);
583
584 Chunk {
585 flavor: ChunkFlavor::Dynamic(chunk_block),
586 blocks: (high_bit - 1) | high_bit,
587 }
588 }
589
590 fn shared_memory(&self) -> &Memory<B> {
591 match &self.flavor {
592 ChunkFlavor::Dedicated(boxed, _) => &*boxed,
593 ChunkFlavor::Dynamic(chunk_block) => chunk_block.shared_memory(),
594 }
595 }
596
597 fn range(&self) -> Range<u64> {
598 match &self.flavor {
599 ChunkFlavor::Dedicated(boxed, _) => 0..boxed.size(),
600 ChunkFlavor::Dynamic(chunk_block) => chunk_block.range(),
601 }
602 }
603
604 fn size(&self) -> u64 {
605 let range = self.range();
606 range.end - range.start
607 }
608
609 fn blocks_range(&self, block_size: u64, block_index: u32, count: u32) -> Range<u64> {
611 let range = self.range();
612 let start = range.start + block_size * block_index as u64;
613 let end = start + block_size * count as u64;
614 debug_assert!(end <= range.end);
615 start..end
616 }
617
618 fn is_unused(&self, block_size: u64) -> bool {
620 let blocks = (self.size() / block_size).min(MAX_BLOCKS_PER_CHUNK as u64);
621
622 let high_bit = 1 << (blocks - 1);
623 let mask = (high_bit - 1) | high_bit;
624
625 debug_assert!(self.blocks <= mask);
626 self.blocks == mask
627 }
628
629 fn is_exhausted(&self) -> bool {
631 self.blocks == 0
632 }
633
634 fn acquire_blocks(&mut self, count: u32, block_size: u64, align: u64) -> Option<u32> {
635 debug_assert!(count > 0 && count <= MAX_BLOCKS_PER_CHUNK);
636
637 let mut blocks = !0;
639 for i in 0..count {
640 blocks &= self.blocks >> i;
641 }
642 while blocks != 0 {
644 let index = blocks.trailing_zeros();
645 blocks &= !(1 << index);
646
647 if (index as u64 * block_size) & (align - 1) == 0 {
648 let mask = ((1 << count) - 1) << index;
649 self.blocks &= !mask;
650 return Some(index);
651 }
652 }
653 None
654 }
655
656 fn release_blocks(&mut self, index: u32, count: u32) {
657 let mask = ((1 << count) - 1) << index;
658 debug_assert_eq!(self.blocks & mask, 0);
659 self.blocks |= mask;
660 }
661
662 fn mapping_ptr(&self) -> Option<NonNull<u8>> {
663 match &self.flavor {
664 ChunkFlavor::Dedicated(_, ptr) => *ptr,
665 ChunkFlavor::Dynamic(chunk_block) => chunk_block.ptr,
666 }
667 }
668}
669
670fn max_chunks_per_size() -> usize {
671 let value = (std::mem::size_of::<usize>() * 8).pow(4);
672 debug_assert!(fits_u32(value));
673 value
674}