rendy_memory/allocator/
dynamic.rs

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/// Memory block allocated from `DynamicAllocator`
21#[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        // Memory won't be freed until last block created from it deallocated.
41        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            //TODO: invalid access error
84            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/// Config for `DynamicAllocator`.
104#[derive(Clone, Copy, Debug)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct DynamicConfig {
107    /// All requests are rounded up to multiple of this value.
108    pub block_size_granularity: u64,
109
110    /// Maximum chunk of blocks size.
111    /// Actual chunk size is `min(max_chunk_size, block_size * blocks_per_chunk)`
112    pub max_chunk_size: u64,
113
114    /// Minimum size of device allocation.
115    pub min_device_allocation: u64,
116}
117
118/// No-fragmentation allocator.
119/// Suitable for any type of small allocations.
120/// Every freed block can be reused.
121#[derive(Debug)]
122pub struct DynamicAllocator<B: Backend> {
123    /// Memory type that this allocator allocates.
124    memory_type: gfx_hal::MemoryTypeId,
125
126    /// Memory properties of the memory type.
127    memory_properties: gfx_hal::memory::Properties,
128
129    /// All requests are rounded up to multiple of this value.
130    block_size_granularity: u64,
131
132    /// Maximum chunk of blocks size.
133    max_chunk_size: u64,
134
135    /// Minimum size of device allocation.
136    min_device_allocation: u64,
137
138    /// Chunk lists.
139    sizes: HashMap<u64, SizeEntry<B>>,
140
141    /// Ordered set of sizes that have allocated chunks.
142    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 count of allocated blocks with size corresponding to this entry.
151    total_blocks: u64,
152
153    /// Bits per ready (non-exhausted) chunks with free blocks.
154    ready_chunks: BitSet,
155
156    /// List of chunks.
157    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    /// Create new `DynamicAllocator`
181    /// for `memory_type` with `memory_properties` specified,
182    /// with `DynamicConfig` provided.
183    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    /// Maximum allocation size.
234    pub fn max_allocation(&self) -> u64 {
235        self.max_chunk_size / MIN_BLOCKS_PER_CHUNK as u64
236    }
237
238    /// Allocate memory chunk from device.
239    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        // Allocate from device.
252        let (memory, mapping) = unsafe {
253            // Valid memory type specified.
254            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    /// Allocate memory chunk for given block size.
279    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 smallest possible chunk size is larger then this allocator max allocation
296        if min_size > self.max_allocation()
297            || (total_blocks < MIN_BLOCKS_PER_CHUNK as u64
298                && min_size >= self.min_device_allocation)
299        {
300            // Allocate memory block from device.
301            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            // Allocate block for the chunk.
311            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    /// Allocate blocks from particular chunk.
324    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    /// Allocate blocks from size entry.
359    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    /// Allocate block.
413    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    /// Perform full cleanup of the memory allocated.
496    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/// Block allocated for chunk.
548#[derive(Debug)]
549enum ChunkFlavor<B: Backend> {
550    /// Allocated from device.
551    Dedicated(Box<Memory<B>>, Option<NonNull<u8>>),
552
553    /// Allocated from chunk of bigger blocks.
554    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    // Get block bytes range
610    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    /// Check if there are free blocks.
619    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    /// Check if there are free blocks.
630    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        // Holds a bit-array of all positions with `count` free blocks.
638        let mut blocks = !0;
639        for i in 0..count {
640            blocks &= self.blocks >> i;
641        }
642        // Find a position in `blocks` that is aligned.
643        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}