1#![warn(missing_docs)]
2#![deny(clippy::as_conversions)]
3#![deny(clippy::panic)]
4#![deny(clippy::unwrap_used)]
5
6use std::collections::HashMap;
50use std::convert::TryInto;
51use std::ffi::c_void;
52use std::fmt::Debug;
53use std::hash::Hash;
54use std::num::NonZeroUsize;
55use std::ptr;
56
57use erupt::{vk, ExtendableFrom};
58use parking_lot::{Mutex, RwLock};
59#[cfg(feature = "tracing")]
60use tracing1::{debug, info};
61
62pub use error::AllocatorError;
63
64mod error;
65
66type Result<T> = std::result::Result<T, AllocatorError>;
67
68const MINIMAL_BUCKET_SIZE_LOG2: u32 = 8;
70
71pub trait Lifetime: Debug + Copy + Hash + Eq + PartialEq {}
73
74#[derive(Debug, Clone)]
76pub struct AllocatorDescriptor {
77 pub block_size: u8,
79}
80
81impl Default for AllocatorDescriptor {
82 fn default() -> Self {
83 Self { block_size: 26 }
84 }
85}
86
87#[derive(Debug)]
89pub struct Allocator<LT: Lifetime> {
90 driver_id: vk::DriverId,
91 is_integrated: bool,
92 pools: RwLock<HashMap<LT, Vec<Mutex<MemoryPool>>>>,
93 block_size: vk::DeviceSize,
94 memory_types: Vec<vk::MemoryType>,
95 memory_properties: vk::PhysicalDeviceMemoryProperties,
96 buffer_image_granularity: u64,
97}
98
99impl<LT: Lifetime> Allocator<LT> {
100 #[cfg_attr(feature = "profiling", profiling::function)]
105 pub unsafe fn new(
106 instance: &erupt::InstanceLoader,
107 physical_device: vk::PhysicalDevice,
108 descriptor: &AllocatorDescriptor,
109 ) -> Result<Self> {
110 let (driver_id, is_integrated, buffer_image_granularity) =
111 query_driver(instance, physical_device);
112
113 #[cfg(feature = "tracing")]
114 debug!("Driver ID of the physical device: {:?}", driver_id);
115
116 let memory_properties = instance.get_physical_device_memory_properties(physical_device);
117
118 let memory_types_count: usize = (memory_properties.memory_type_count).try_into()?;
119 let memory_types = memory_properties.memory_types[..memory_types_count].to_owned();
120
121 #[cfg(feature = "tracing")]
122 print_memory_types(memory_properties, &memory_types)?;
123
124 let block_size: vk::DeviceSize = (2u64).pow(descriptor.block_size.into()).into();
125
126 Ok(Self {
127 driver_id,
128 is_integrated,
129 pools: RwLock::default(),
130 block_size,
131 memory_types,
132 memory_properties,
133 buffer_image_granularity,
134 })
135 }
136
137 #[cfg_attr(feature = "profiling", profiling::function)]
142 pub unsafe fn allocate_memory_for_buffer(
143 &self,
144 device: &erupt::DeviceLoader,
145 buffer: vk::Buffer,
146 location: MemoryLocation,
147 lifetime: LT,
148 ) -> Result<Allocation<LT>> {
149 let info = vk::BufferMemoryRequirementsInfo2Builder::new().buffer(buffer);
150 let mut dedicated_requirements = vk::MemoryDedicatedRequirementsBuilder::new();
151 let requirements =
152 vk::MemoryRequirements2Builder::new().extend_from(&mut dedicated_requirements);
153
154 let requirements =
155 device.get_buffer_memory_requirements2(&info, Some(requirements.build_dangling()));
156
157 let memory_requirements = requirements.memory_requirements;
158
159 let is_dedicated = dedicated_requirements.prefers_dedicated_allocation == 1
160 || dedicated_requirements.requires_dedicated_allocation == 1;
161
162 let alloc_decs = AllocationDescriptor {
163 requirements: memory_requirements,
164 location,
165 lifetime,
166 is_dedicated,
167 is_optimal: false,
168 };
169
170 self.allocate(device, &alloc_decs)
171 }
172
173 #[cfg_attr(feature = "profiling", profiling::function)]
178 pub unsafe fn allocate_memory_for_image(
179 &self,
180 device: &erupt::DeviceLoader,
181 image: vk::Image,
182 location: MemoryLocation,
183 lifetime: LT,
184 is_optimal: bool,
185 ) -> Result<Allocation<LT>> {
186 let info = vk::ImageMemoryRequirementsInfo2Builder::new().image(image);
187 let mut dedicated_requirements = vk::MemoryDedicatedRequirementsBuilder::new();
188 let requirements =
189 vk::MemoryRequirements2Builder::new().extend_from(&mut dedicated_requirements);
190
191 let requirements =
192 device.get_image_memory_requirements2(&info, Some(requirements.build_dangling()));
193
194 let memory_requirements = requirements.memory_requirements;
195
196 let is_dedicated = dedicated_requirements.prefers_dedicated_allocation == 1
197 || dedicated_requirements.requires_dedicated_allocation == 1;
198
199 let alloc_decs = AllocationDescriptor {
200 requirements: memory_requirements,
201 location,
202 lifetime,
203 is_dedicated,
204 is_optimal,
205 };
206
207 self.allocate(device, &alloc_decs)
208 }
209
210 #[cfg_attr(feature = "profiling", profiling::function)]
215 pub unsafe fn allocate(
216 &self,
217 device: &erupt::DeviceLoader,
218 descriptor: &AllocationDescriptor<LT>,
219 ) -> Result<Allocation<LT>> {
220 let size = descriptor.requirements.size;
221 let alignment = descriptor.requirements.alignment;
222
223 #[cfg(feature = "tracing")]
224 debug!(
225 "Allocating {} bytes with an alignment of {}.",
226 size, alignment
227 );
228
229 if size == 0 || !alignment.is_power_of_two() {
230 return Err(AllocatorError::InvalidAlignment);
231 }
232
233 let memory_type_index = self.find_memory_type_index(
234 descriptor.location,
235 descriptor.requirements.memory_type_bits,
236 )?;
237
238 let has_key = self.pools.read().contains_key(&descriptor.lifetime);
239 if !has_key {
240 let mut pools = Vec::with_capacity(self.memory_types.len());
241 for (i, memory_type) in self.memory_types.iter().enumerate() {
242 let pool = MemoryPool::new(
243 self.block_size,
244 i.try_into()?,
245 memory_type
246 .property_flags
247 .contains(vk::MemoryPropertyFlags::HOST_VISIBLE),
248 )?;
249 pools.push(Mutex::new(pool));
250 }
251
252 self.pools.write().insert(descriptor.lifetime, pools);
253 }
254
255 let lifetime_pools = self.pools.read();
256
257 let pool = &lifetime_pools
258 .get(&descriptor.lifetime)
259 .ok_or_else(|| {
260 AllocatorError::Internal(format!(
261 "can't find pool for lifetime {:?}",
262 descriptor.lifetime
263 ))
264 })?
265 .get(memory_type_index)
266 .ok_or_else(|| {
267 AllocatorError::Internal(format!(
268 "can't find memory_type {} in pool {:?}",
269 memory_type_index, descriptor.lifetime
270 ))
271 })?;
272
273 if descriptor.is_dedicated || size >= self.block_size {
274 #[cfg(feature = "tracing")]
275 debug!(
276 "Allocating as dedicated block on memory type {}",
277 memory_type_index
278 );
279 pool.lock()
280 .allocate_dedicated(device, size, descriptor.lifetime)
281 } else {
282 #[cfg(feature = "tracing")]
283 debug!("Sub allocating on memory type {}", memory_type_index);
284 pool.lock().allocate(
285 device,
286 self.buffer_image_granularity,
287 size,
288 alignment,
289 descriptor.lifetime,
290 descriptor.is_optimal,
291 )
292 }
293 }
294
295 #[cfg_attr(feature = "profiling", profiling::function)]
296 fn find_memory_type_index(
297 &self,
298 location: MemoryLocation,
299 memory_type_bits: u32,
300 ) -> Result<usize> {
301 let memory_property_flags = if (self.driver_id == vk::DriverId::AMD_OPEN_SOURCE
303 || self.driver_id == vk::DriverId::AMD_PROPRIETARY
304 || self.driver_id == vk::DriverId::MESA_RADV)
305 && self.is_integrated
306 {
307 match location {
308 MemoryLocation::GpuOnly => {
309 vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT
310 }
311 MemoryLocation::CpuToGpu => {
312 vk::MemoryPropertyFlags::DEVICE_LOCAL
313 | vk::MemoryPropertyFlags::HOST_VISIBLE
314 | vk::MemoryPropertyFlags::HOST_COHERENT
315 }
316 MemoryLocation::GpuToCpu => {
317 vk::MemoryPropertyFlags::HOST_VISIBLE
318 | vk::MemoryPropertyFlags::HOST_COHERENT
319 | vk::MemoryPropertyFlags::HOST_CACHED
320 }
321 }
322 } else {
323 match location {
324 MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
325 MemoryLocation::CpuToGpu => {
326 vk::MemoryPropertyFlags::DEVICE_LOCAL
327 | vk::MemoryPropertyFlags::HOST_VISIBLE
328 | vk::MemoryPropertyFlags::HOST_COHERENT
329 }
330 MemoryLocation::GpuToCpu => {
331 vk::MemoryPropertyFlags::HOST_VISIBLE
332 | vk::MemoryPropertyFlags::HOST_COHERENT
333 | vk::MemoryPropertyFlags::HOST_CACHED
334 }
335 }
336 };
337
338 let memory_type_index_optional =
339 self.query_memory_type_index(memory_type_bits, memory_property_flags)?;
340
341 if let Some(index) = memory_type_index_optional {
342 return Ok(index);
343 }
344
345 let memory_property_flags = match location {
347 MemoryLocation::GpuOnly => vk::MemoryPropertyFlags::DEVICE_LOCAL,
348 MemoryLocation::CpuToGpu => {
349 vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT
350 }
351 MemoryLocation::GpuToCpu => {
352 vk::MemoryPropertyFlags::HOST_VISIBLE
353 | vk::MemoryPropertyFlags::HOST_COHERENT
354 | vk::MemoryPropertyFlags::HOST_CACHED
355 }
356 };
357
358 let memory_type_index_optional =
359 self.query_memory_type_index(memory_type_bits, memory_property_flags)?;
360
361 match memory_type_index_optional {
362 Some(index) => Ok(index),
363 None => Err(AllocatorError::NoCompatibleMemoryTypeFound),
364 }
365 }
366
367 #[cfg_attr(feature = "profiling", profiling::function)]
368 fn query_memory_type_index(
369 &self,
370 memory_type_bits: u32,
371 memory_property_flags: vk::MemoryPropertyFlags,
372 ) -> Result<Option<usize>> {
373 let memory_properties = &self.memory_properties;
374 let memory_type_count: usize = memory_properties.memory_type_count.try_into()?;
375 let index = memory_properties.memory_types[..memory_type_count]
376 .iter()
377 .enumerate()
378 .find(|(index, memory_type)| {
379 memory_type_is_compatible(*index, memory_type_bits)
380 && memory_type.property_flags.contains(memory_property_flags)
381 })
382 .map(|(index, _)| index);
383 Ok(index)
384 }
385
386 #[cfg_attr(feature = "profiling", profiling::function)]
392 pub unsafe fn deallocate(
393 &self,
394 device: &erupt::DeviceLoader,
395 allocation: &Allocation<LT>,
396 ) -> Result<()> {
397 let memory_type_index: usize = allocation.memory_type_index.try_into()?;
398 let pools = &self.pools.read();
399 let memory_pool = &pools
400 .get(&allocation.lifetime)
401 .ok_or_else(|| {
402 AllocatorError::Internal(format!(
403 "can't find pool for lifetime {:?}",
404 allocation.lifetime
405 ))
406 })?
407 .get(memory_type_index)
408 .ok_or_else(|| {
409 AllocatorError::Internal(format!(
410 "can't find memory_type {} in pool {:?}",
411 memory_type_index, allocation.lifetime
412 ))
413 })?;
414
415 if let Some(chunk_key) = allocation.chunk_key {
416 #[cfg(feature = "tracing")]
417 debug!(
418 "Deallocating chunk on device memory 0x{:02x}, offset {}, size {}",
419 allocation.device_memory.0, allocation.offset, allocation.size
420 );
421 memory_pool.lock().free_chunk(chunk_key)?;
422 } else {
423 #[cfg(feature = "tracing")]
425 debug!(
426 "Deallocating dedicated device memory 0x{:02x} size {}",
427 allocation.device_memory.0, allocation.size
428 );
429 memory_pool
430 .lock()
431 .free_block(&device, allocation.block_key)?;
432 }
433
434 Ok(())
435 }
436
437 #[cfg_attr(feature = "profiling", profiling::function)]
443 pub unsafe fn cleanup(&self, device: &erupt::DeviceLoader) {
444 for (_, mut lifetime_pools) in self.pools.write().drain() {
445 lifetime_pools.drain(..).for_each(|pool| {
446 pool.lock().blocks.iter_mut().for_each(|block| {
447 if let Some(block) = block {
448 block.destroy(&device)
449 }
450 })
451 });
452 }
453 }
454
455 #[cfg_attr(feature = "profiling", profiling::function)]
457 pub fn allocation_count(&self) -> usize {
458 let mut count = 0;
459 for (_, lifetime_pools) in self.pools.read().iter() {
460 lifetime_pools.iter().for_each(|pool| {
461 let pool = pool.lock();
462 for chunk in &pool.chunks {
463 if let Some(chunk) = chunk {
464 if chunk.chunk_type != ChunkType::Free {
465 count += 1;
466 }
467 }
468 }
469 });
470 }
471
472 for (_, lifetime_pools) in self.pools.read().iter() {
473 lifetime_pools.iter().for_each(|pool| {
474 let pool = pool.lock();
475 for block in &pool.blocks {
476 if let Some(block) = block {
477 if block.is_dedicated {
478 count += 1;
479 }
480 }
481 }
482 });
483 }
484
485 count
486 }
487
488 #[cfg_attr(feature = "profiling", profiling::function)]
490 pub fn unused_range_count(&self) -> usize {
491 let mut unused_count: usize = 0;
492
493 for (_, lifetime_pools) in self.pools.read().iter() {
494 lifetime_pools.iter().for_each(|pool| {
495 collect_start_chunks(&pool).iter().for_each(|key| {
496 let mut next_key: NonZeroUsize = *key;
497 let mut previous_size: vk::DeviceSize = 0;
498 let mut previous_offset: vk::DeviceSize = 0;
499 loop {
500 let pool = pool.lock();
501 let chunk = pool.chunks[next_key.get()]
502 .as_ref()
503 .expect("can't find chunk in chunk list");
504 if chunk.offset != previous_offset + previous_size {
505 unused_count += 1;
506 }
507
508 if let Some(key) = chunk.next {
509 next_key = key
510 } else {
511 break;
512 }
513
514 previous_size = chunk.size;
515 previous_offset = chunk.offset
516 }
517 });
518 })
519 }
520
521 unused_count
522 }
523
524 #[cfg_attr(feature = "profiling", profiling::function)]
526 pub fn used_bytes(&self) -> vk::DeviceSize {
527 let mut bytes = 0;
528
529 for (_, lifetime_pools) in self.pools.read().iter() {
530 lifetime_pools.iter().for_each(|pool| {
531 let pool = pool.lock();
532 for chunk in &pool.chunks {
533 if let Some(chunk) = chunk {
534 if chunk.chunk_type != ChunkType::Free {
535 bytes += chunk.size;
536 }
537 }
538 }
539 });
540 }
541
542 for (_, lifetime_pools) in self.pools.read().iter() {
543 lifetime_pools.iter().for_each(|pool| {
544 let pool = pool.lock();
545 for block in &pool.blocks {
546 if let Some(block) = block {
547 if block.is_dedicated {
548 bytes += block.size;
549 }
550 }
551 }
552 });
553 }
554
555 bytes
556 }
557
558 #[cfg_attr(feature = "profiling", profiling::function)]
560 pub fn unused_bytes(&self) -> vk::DeviceSize {
561 let mut unused_bytes: vk::DeviceSize = 0;
562
563 for (_, lifetime_pools) in self.pools.read().iter() {
564 lifetime_pools.iter().for_each(|pool| {
565 collect_start_chunks(&pool).iter().for_each(|key| {
566 let mut next_key: NonZeroUsize = *key;
567 let mut previous_size: vk::DeviceSize = 0;
568 let mut previous_offset: vk::DeviceSize = 0;
569 loop {
570 let pool = pool.lock();
571 let chunk = pool.chunks[next_key.get()]
572 .as_ref()
573 .expect("can't find chunk in chunk list");
574 if chunk.offset != previous_offset + previous_size {
575 unused_bytes += chunk.offset - (previous_offset + previous_size);
576 }
577
578 if let Some(key) = chunk.next {
579 next_key = key
580 } else {
581 break;
582 }
583
584 previous_size = chunk.size;
585 previous_offset = chunk.offset
586 }
587 });
588 });
589 }
590
591 unused_bytes
592 }
593
594 #[cfg_attr(feature = "profiling", profiling::function)]
596 pub fn block_count(&self) -> usize {
597 let mut count: usize = 0;
598
599 for (_, lifetime_pools) in self.pools.read().iter() {
600 count += lifetime_pools
601 .iter()
602 .map(|pool| pool.lock().blocks.len())
603 .sum::<usize>();
604 }
605
606 count
607 }
608}
609
610#[derive(Debug, Copy, Clone, Eq, PartialEq)]
611#[repr(u8)]
612enum ChunkType {
613 Free,
614 Linear,
615 Optimal,
616}
617
618impl ChunkType {
619 #[cfg_attr(feature = "profiling", profiling::function)]
623 fn granularity_conflict(self, other: ChunkType) -> bool {
624 if self == ChunkType::Free || other == ChunkType::Free {
625 return false;
626 }
627
628 self != other
629 }
630}
631
632#[derive(Debug, Clone, Copy, PartialEq, Eq)]
634pub enum MemoryLocation {
635 CpuToGpu,
637 GpuOnly,
639 GpuToCpu,
641}
642
643#[derive(Clone, Debug)]
645pub struct AllocationDescriptor<LT: Lifetime> {
646 pub location: MemoryLocation,
648 pub requirements: vk::MemoryRequirements,
650 pub lifetime: LT,
652 pub is_dedicated: bool,
654 pub is_optimal: bool,
657}
658
659#[derive(Clone, Debug)]
661pub struct Allocation<LT: Lifetime> {
662 memory_type_index: u32,
663 lifetime: LT,
664 block_key: NonZeroUsize,
665 chunk_key: Option<NonZeroUsize>,
666 mapped_ptr: Option<std::ptr::NonNull<c_void>>,
667
668 device_memory: vk::DeviceMemory,
669 offset: vk::DeviceSize,
670 size: vk::DeviceSize,
671}
672
673unsafe impl<LT: Lifetime> Send for Allocation<LT> {}
674
675unsafe impl<LT: Lifetime> Sync for Allocation<LT> {}
676
677impl<LT: Lifetime> Allocation<LT> {
678 #[inline]
680 pub fn device_memory(&self) -> vk::DeviceMemory {
681 self.device_memory
682 }
683
684 #[inline]
686 pub fn offset(&self) -> vk::DeviceSize {
687 self.offset
688 }
689
690 #[inline]
692 pub fn size(&self) -> vk::DeviceSize {
693 self.size
694 }
695
696 #[cfg_attr(feature = "profiling", profiling::function)]
702 pub unsafe fn mapped_slice(&self) -> Result<Option<&[u8]>> {
703 let slice = if let Some(ptr) = self.mapped_ptr {
704 let size = self.size.try_into()?;
705 #[allow(clippy::as_conversions)]
706 Some(std::slice::from_raw_parts(ptr.as_ptr() as *const _, size))
707 } else {
708 None
709 };
710 Ok(slice)
711 }
712
713 #[cfg_attr(feature = "profiling", profiling::function)]
719 pub unsafe fn mapped_slice_mut(&mut self) -> Result<Option<&mut [u8]>> {
720 let slice = if let Some(ptr) = self.mapped_ptr.as_mut() {
721 let size = self.size.try_into()?;
722 #[allow(clippy::as_conversions)]
723 Some(std::slice::from_raw_parts_mut(ptr.as_ptr() as *mut _, size))
724 } else {
725 None
726 };
727 Ok(slice)
728 }
729}
730
731#[derive(Clone, Debug)]
732struct BestFitCandidate {
733 aligned_offset: u64,
734 key: NonZeroUsize,
735 free_list_index: usize,
736 free_size: vk::DeviceSize,
737}
738
739#[derive(Debug)]
744struct MemoryPool {
745 memory_type_index: u32,
746 block_size: vk::DeviceSize,
747 is_mappable: bool,
748 blocks: Vec<Option<MemoryBlock>>,
749 chunks: Vec<Option<MemoryChunk>>,
750 free_chunks: Vec<Vec<NonZeroUsize>>,
751 max_bucket_index: u32,
752
753 free_block_slots: Vec<NonZeroUsize>,
755 free_chunk_slots: Vec<NonZeroUsize>,
756}
757
758impl MemoryPool {
759 #[cfg_attr(feature = "profiling", profiling::function)]
760 fn new(block_size: vk::DeviceSize, memory_type_index: u32, is_mappable: bool) -> Result<Self> {
761 let mut blocks = Vec::with_capacity(128);
762 let mut chunks = Vec::with_capacity(128);
763
764 blocks.push(None);
766 chunks.push(None);
767
768 let bucket_count = 64 - MINIMAL_BUCKET_SIZE_LOG2 - (block_size - 1).leading_zeros();
771
772 let mut free_chunks = Vec::with_capacity(bucket_count.try_into()?);
775 for i in 0..bucket_count {
776 let min_bucket_element_size = if i == 0 {
777 512
778 } else {
779 2u64.pow(MINIMAL_BUCKET_SIZE_LOG2 - 1 + i).into()
780 };
781 let max_elements: usize = (block_size / min_bucket_element_size).try_into()?;
782 free_chunks.push(Vec::with_capacity(512.min(max_elements)));
783 }
784
785 Ok(Self {
786 memory_type_index,
787 block_size,
788 is_mappable,
789 blocks,
790 chunks,
791 free_chunks,
792 free_block_slots: Vec::with_capacity(16),
793 free_chunk_slots: Vec::with_capacity(16),
794 max_bucket_index: bucket_count - 1,
795 })
796 }
797
798 #[cfg_attr(feature = "profiling", profiling::function)]
799 fn add_block(&mut self, block: MemoryBlock) -> NonZeroUsize {
800 if let Some(key) = self.free_block_slots.pop() {
801 self.blocks[key.get()] = Some(block);
802 key
803 } else {
804 let key = self.blocks.len();
805 self.blocks.push(Some(block));
806 NonZeroUsize::new(key).expect("new block key was zero")
807 }
808 }
809
810 #[cfg_attr(feature = "profiling", profiling::function)]
811 fn add_chunk(&mut self, chunk: MemoryChunk) -> NonZeroUsize {
812 if let Some(key) = self.free_chunk_slots.pop() {
813 self.chunks[key.get()] = Some(chunk);
814 key
815 } else {
816 let key = self.chunks.len();
817 self.chunks.push(Some(chunk));
818 NonZeroUsize::new(key).expect("new chunk key was zero")
819 }
820 }
821
822 #[cfg_attr(feature = "profiling", profiling::function)]
823 unsafe fn allocate_dedicated<LT: Lifetime>(
824 &mut self,
825 device: &erupt::DeviceLoader,
826 size: vk::DeviceSize,
827 lifetime: LT,
828 ) -> Result<Allocation<LT>> {
829 let block = MemoryBlock::new(device, size, self.memory_type_index, self.is_mappable, true)?;
830
831 let device_memory = block.device_memory;
832 let mapped_ptr = std::ptr::NonNull::new(block.mapped_ptr);
833
834 let key = self.add_block(block);
835
836 Ok(Allocation {
837 memory_type_index: self.memory_type_index,
838 lifetime,
839 block_key: key,
840 chunk_key: None,
841 device_memory,
842 offset: 0,
843 size,
844 mapped_ptr,
845 })
846 }
847
848 #[cfg_attr(feature = "profiling", profiling::function)]
849 unsafe fn allocate<LT: Lifetime>(
850 &mut self,
851 device: &erupt::DeviceLoader,
852 buffer_image_granularity: u64,
853 size: vk::DeviceSize,
854 alignment: vk::DeviceSize,
855 lifetime: LT,
856 is_optimal: bool,
857 ) -> Result<Allocation<LT>> {
858 let mut bucket_index = calculate_bucket_index(size);
859
860 debug_assert!(bucket_index <= self.max_bucket_index);
862
863 let chunk_type = if is_optimal {
864 ChunkType::Optimal
865 } else {
866 ChunkType::Linear
867 };
868
869 loop {
870 if bucket_index > self.max_bucket_index {
872 self.allocate_new_block(device)?;
873 bucket_index = self.max_bucket_index;
874 }
875
876 let index: usize = bucket_index.try_into()?;
877 let free_list = &self.free_chunks[index];
878
879 let mut best_fit_candidate: Option<BestFitCandidate> = None;
881 for (index, key) in free_list.iter().enumerate() {
882 let chunk = &self.chunks[key.get()]
883 .as_ref()
884 .expect("can't find chunk in chunk list");
885 debug_assert!(chunk.chunk_type == ChunkType::Free);
886
887 if chunk.size < size {
888 continue;
889 }
890
891 let mut aligned_offset = 0;
892
893 if let Some(previous) = chunk.previous {
896 let previous = self
897 .chunks
898 .get(previous.get())
899 .ok_or_else(|| {
900 AllocatorError::Internal("can't find previous chunk".into())
901 })?
902 .as_ref()
903 .ok_or_else(|| {
904 AllocatorError::Internal("previous chunk was empty".into())
905 })?;
906
907 aligned_offset = align_up(chunk.offset, alignment);
908
909 if previous.chunk_type.granularity_conflict(chunk_type)
910 && is_on_same_page(
911 previous.offset,
912 previous.size,
913 aligned_offset,
914 buffer_image_granularity,
915 )
916 {
917 aligned_offset = align_up(aligned_offset, buffer_image_granularity);
918 }
919 }
920
921 if let Some(next) = chunk.next {
922 let next = self
923 .chunks
924 .get(next.get())
925 .ok_or_else(|| AllocatorError::Internal("can't find next chunk".into()))?
926 .as_ref()
927 .ok_or_else(|| AllocatorError::Internal("next chunk was empty".into()))?;
928
929 if next.chunk_type.granularity_conflict(chunk_type)
930 && is_on_same_page(
931 next.offset,
932 next.size,
933 aligned_offset,
934 buffer_image_granularity,
935 )
936 {
937 continue;
938 }
939 }
940
941 let padding = aligned_offset - chunk.offset;
942 let aligned_size = padding + size;
943
944 if chunk.size >= aligned_size {
946 let free_size = chunk.size - aligned_size;
947
948 let best_fit_size = if let Some(best_fit) = &best_fit_candidate {
949 best_fit.free_size
950 } else {
951 u64::MAX
952 };
953
954 if free_size < best_fit_size {
955 best_fit_candidate = Some(BestFitCandidate {
956 aligned_offset,
957 key: *key,
958 free_list_index: index,
959 free_size,
960 })
961 }
962 }
963 }
964
965 if let Some(candidate) = &best_fit_candidate {
967 self.free_chunks
968 .get_mut(index)
969 .ok_or_else(|| AllocatorError::Internal("can't find free chunk".to_owned()))?
970 .remove(candidate.free_list_index);
971
972 let new_free_chunk_key = if candidate.free_size != 0 {
974 let candidate_chunk = self.chunks[candidate.key.get()]
975 .as_ref()
976 .expect("can't find candidate in chunk list")
977 .clone();
978
979 let new_free_offset = candidate.aligned_offset + size;
980 let new_free_size =
981 (candidate_chunk.offset + candidate_chunk.size) - new_free_offset;
982
983 let new_free_chunk = MemoryChunk {
984 block_key: candidate_chunk.block_key,
985 size: new_free_size,
986 offset: new_free_offset,
987 previous: Some(candidate.key),
988 next: candidate_chunk.next,
989 chunk_type: ChunkType::Free,
990 };
991
992 let new_free_chunk_key = self.add_chunk(new_free_chunk);
993
994 let rhs_bucket_index: usize =
995 calculate_bucket_index(new_free_size).try_into()?;
996 self.free_chunks[rhs_bucket_index].push(new_free_chunk_key);
997
998 Some(new_free_chunk_key)
999 } else {
1000 None
1001 };
1002
1003 let candidate_chunk = self.chunks[candidate.key.get()]
1004 .as_mut()
1005 .expect("can't find chunk in chunk list");
1006 candidate_chunk.chunk_type = chunk_type;
1007 candidate_chunk.offset = candidate.aligned_offset;
1008 candidate_chunk.size = size;
1009
1010 let block = self.blocks[candidate_chunk.block_key.get()]
1011 .as_ref()
1012 .expect("can't find block in block list");
1013
1014 let mapped_ptr = if !block.mapped_ptr.is_null() {
1015 let offset: usize = candidate_chunk.offset.try_into()?;
1016 let offset_ptr = block.mapped_ptr.add(offset);
1017 std::ptr::NonNull::new(offset_ptr)
1018 } else {
1019 None
1020 };
1021
1022 let allocation = Allocation {
1023 memory_type_index: self.memory_type_index,
1024 lifetime,
1025 block_key: candidate_chunk.block_key,
1026 chunk_key: Some(candidate.key),
1027 device_memory: block.device_memory,
1028 offset: candidate_chunk.offset,
1029 size: candidate_chunk.size,
1030 mapped_ptr,
1031 };
1032
1033 let old_next_key = if let Some(new_free_chunk_key) = new_free_chunk_key {
1035 let old_next_key = candidate_chunk.next;
1036 candidate_chunk.next = Some(new_free_chunk_key);
1037 old_next_key
1038 } else {
1039 None
1040 };
1041
1042 if let Some(old_next_key) = old_next_key {
1043 let old_next = self.chunks[old_next_key.get()]
1044 .as_mut()
1045 .expect("can't find old next in chunk list");
1046 old_next.previous = new_free_chunk_key;
1047 }
1048
1049 return Ok(allocation);
1050 }
1051
1052 bucket_index += 1;
1053 }
1054 }
1055
1056 #[cfg_attr(feature = "profiling", profiling::function)]
1057 unsafe fn allocate_new_block(&mut self, device: &erupt::DeviceLoader) -> Result<()> {
1058 let block = MemoryBlock::new(
1059 device,
1060 self.block_size,
1061 self.memory_type_index,
1062 self.is_mappable,
1063 false,
1064 )?;
1065
1066 let block_key = self.add_block(block);
1067
1068 let chunk = MemoryChunk {
1069 block_key,
1070 size: self.block_size,
1071 offset: 0,
1072 previous: None,
1073 next: None,
1074 chunk_type: ChunkType::Free,
1075 };
1076
1077 let chunk_key = self.add_chunk(chunk);
1078
1079 let index: usize = self.max_bucket_index.try_into()?;
1080 self.free_chunks[index].push(chunk_key);
1081
1082 Ok(())
1083 }
1084
1085 #[cfg_attr(feature = "profiling", profiling::function)]
1086 fn free_chunk(&mut self, chunk_key: NonZeroUsize) -> Result<()> {
1087 let (previous_key, next_key, size) = {
1088 let chunk = self.chunks[chunk_key.get()]
1089 .as_mut()
1090 .ok_or(AllocatorError::CantFindChunk)?;
1091 chunk.chunk_type = ChunkType::Free;
1092 (chunk.previous, chunk.next, chunk.size)
1093 };
1094 self.add_to_free_list(chunk_key, size)?;
1095
1096 self.merge_free_neighbor(next_key, chunk_key, false)?;
1097 self.merge_free_neighbor(previous_key, chunk_key, true)?;
1098
1099 Ok(())
1100 }
1101
1102 #[cfg_attr(feature = "profiling", profiling::function)]
1103 fn merge_free_neighbor(
1104 &mut self,
1105 neighbor: Option<NonZeroUsize>,
1106 chunk_key: NonZeroUsize,
1107 neighbor_is_lhs: bool,
1108 ) -> Result<()> {
1109 if let Some(neighbor_key) = neighbor {
1110 if self.chunks[neighbor_key.get()]
1111 .as_ref()
1112 .expect("can't find chunk in chunk list")
1113 .chunk_type
1114 == ChunkType::Free
1115 {
1116 if neighbor_is_lhs {
1117 self.merge_rhs_into_lhs_chunk(neighbor_key, chunk_key)?;
1118 } else {
1119 self.merge_rhs_into_lhs_chunk(chunk_key, neighbor_key)?;
1120 }
1121 }
1122 }
1123 Ok(())
1124 }
1125
1126 #[cfg_attr(feature = "profiling", profiling::function)]
1127 fn merge_rhs_into_lhs_chunk(
1128 &mut self,
1129 lhs_chunk_key: NonZeroUsize,
1130 rhs_chunk_key: NonZeroUsize,
1131 ) -> Result<()> {
1132 let (rhs_size, rhs_offset, rhs_next) = {
1133 let chunk = self.chunks[rhs_chunk_key.get()]
1134 .take()
1135 .expect("can't find chunk in chunk list");
1136 self.free_chunk_slots.push(rhs_chunk_key);
1137 debug_assert!(chunk.previous == Some(lhs_chunk_key));
1138
1139 self.remove_from_free_list(rhs_chunk_key, chunk.size)?;
1140
1141 (chunk.size, chunk.offset, chunk.next)
1142 };
1143
1144 let lhs_previous_key = self.chunks[lhs_chunk_key.get()]
1145 .as_mut()
1146 .expect("can't find chunk in chunk list")
1147 .previous;
1148
1149 let lhs_offset = if let Some(lhs_previous_key) = lhs_previous_key {
1150 let lhs_previous = self.chunks[lhs_previous_key.get()]
1151 .as_mut()
1152 .expect("can't find chunk in chunk list");
1153 lhs_previous.offset + lhs_previous.size
1154 } else {
1155 0
1156 };
1157
1158 let lhs_chunk = self.chunks[lhs_chunk_key.get()]
1159 .as_mut()
1160 .expect("can't find chunk in chunk list");
1161
1162 debug_assert!(lhs_chunk.next == Some(rhs_chunk_key));
1163
1164 let old_size = lhs_chunk.size;
1165
1166 lhs_chunk.next = rhs_next;
1167 lhs_chunk.size = (rhs_offset + rhs_size) - lhs_offset;
1168 lhs_chunk.offset = lhs_offset;
1169
1170 let new_size = lhs_chunk.size;
1171
1172 self.remove_from_free_list(lhs_chunk_key, old_size)?;
1173 self.add_to_free_list(lhs_chunk_key, new_size)?;
1174
1175 if let Some(rhs_next) = rhs_next {
1176 let chunk = self.chunks[rhs_next.get()]
1177 .as_mut()
1178 .expect("previous memory chunk was None");
1179 chunk.previous = Some(lhs_chunk_key);
1180 }
1181
1182 Ok(())
1183 }
1184
1185 #[cfg_attr(feature = "profiling", profiling::function)]
1186 unsafe fn free_block(
1187 &mut self,
1188 device: &erupt::DeviceLoader,
1189 block_key: NonZeroUsize,
1190 ) -> Result<()> {
1191 let mut block = self.blocks[block_key.get()]
1192 .take()
1193 .ok_or(AllocatorError::CantFindBlock)?;
1194
1195 block.destroy(device);
1196
1197 self.free_block_slots.push(block_key);
1198
1199 Ok(())
1200 }
1201
1202 #[cfg_attr(feature = "profiling", profiling::function)]
1203 fn add_to_free_list(&mut self, chunk_key: NonZeroUsize, size: vk::DeviceSize) -> Result<()> {
1204 let chunk_bucket_index: usize = calculate_bucket_index(size).try_into()?;
1205 self.free_chunks[chunk_bucket_index].push(chunk_key);
1206 Ok(())
1207 }
1208
1209 #[cfg_attr(feature = "profiling", profiling::function)]
1210 fn remove_from_free_list(
1211 &mut self,
1212 chunk_key: NonZeroUsize,
1213 chunk_size: vk::DeviceSize,
1214 ) -> Result<()> {
1215 let bucket_index: usize = calculate_bucket_index(chunk_size).try_into()?;
1216 let free_list_index = self.free_chunks[bucket_index]
1217 .iter()
1218 .enumerate()
1219 .find(|(_, key)| **key == chunk_key)
1220 .map(|(index, _)| index)
1221 .expect("can't find chunk in chunk list");
1222 self.free_chunks[bucket_index].remove(free_list_index);
1223 Ok(())
1224 }
1225}
1226
1227#[derive(Clone, Debug)]
1229struct MemoryChunk {
1230 block_key: NonZeroUsize,
1231 size: vk::DeviceSize,
1232 offset: vk::DeviceSize,
1233 previous: Option<NonZeroUsize>,
1234 next: Option<NonZeroUsize>,
1235 chunk_type: ChunkType,
1236}
1237
1238#[derive(Debug)]
1240struct MemoryBlock {
1241 device_memory: vk::DeviceMemory,
1242 size: vk::DeviceSize,
1243 mapped_ptr: *mut c_void,
1244 is_dedicated: bool,
1245}
1246
1247unsafe impl Send for MemoryBlock {}
1248
1249impl MemoryBlock {
1250 #[cfg_attr(feature = "profiling", profiling::function)]
1251 unsafe fn new(
1252 device: &erupt::DeviceLoader,
1253 size: vk::DeviceSize,
1254 memory_type_index: u32,
1255 is_mappable: bool,
1256 is_dedicated: bool,
1257 ) -> Result<Self> {
1258 #[cfg(feature = "vk-buffer-device-address")]
1259 let device_memory = {
1260 let alloc_info = vk::MemoryAllocateInfoBuilder::new()
1261 .allocation_size(size)
1262 .memory_type_index(memory_type_index);
1263
1264 let allocation_flags = vk::MemoryAllocateFlags::DEVICE_ADDRESS;
1265 let mut flags_info = vk::MemoryAllocateFlagsInfoBuilder::new().flags(allocation_flags);
1266 let alloc_info = alloc_info.extend_from(&mut flags_info);
1267
1268 device
1269 .allocate_memory(&alloc_info, None)
1270 .map_err(|_| AllocatorError::OutOfMemory)?
1271 };
1272
1273 #[cfg(not(feature = "vk-buffer-device-address"))]
1274 let device_memory = {
1275 let alloc_info = vk::MemoryAllocateInfoBuilder::new()
1276 .allocation_size(size)
1277 .memory_type_index(memory_type_index);
1278
1279 device
1280 .allocate_memory(&alloc_info, None)
1281 .map_err(|_| AllocatorError::OutOfMemory)?
1282 };
1283
1284 let mapped_ptr = if is_mappable {
1285 let mapped_ptr = device.map_memory(
1286 device_memory,
1287 0,
1288 vk::WHOLE_SIZE,
1289 vk::MemoryMapFlags::empty(),
1290 );
1291
1292 match mapped_ptr.ok() {
1293 Some(mapped_ptr) => mapped_ptr,
1294 None => {
1295 device.free_memory(device_memory, None);
1296 return Err(AllocatorError::FailedToMap);
1297 }
1298 }
1299 } else {
1300 ptr::null_mut()
1301 };
1302
1303 Ok(Self {
1304 device_memory,
1305 size,
1306 mapped_ptr,
1307 is_dedicated,
1308 })
1309 }
1310
1311 #[cfg_attr(feature = "profiling", profiling::function)]
1312 unsafe fn destroy(&mut self, device: &erupt::DeviceLoader) {
1313 if !self.mapped_ptr.is_null() {
1314 device.unmap_memory(self.device_memory);
1315 }
1316 device.free_memory(self.device_memory, None);
1317 self.device_memory = vk::DeviceMemory::null()
1318 }
1319}
1320
1321#[inline]
1322fn align_up(offset: vk::DeviceSize, alignment: vk::DeviceSize) -> vk::DeviceSize {
1323 (offset + (alignment - 1)) & !(alignment - 1)
1324}
1325
1326#[inline]
1327fn align_down(offset: vk::DeviceSize, alignment: vk::DeviceSize) -> vk::DeviceSize {
1328 offset & !(alignment - 1)
1329}
1330
1331fn is_on_same_page(offset_a: u64, size_a: u64, offset_b: u64, page_size: u64) -> bool {
1332 let end_a = offset_a + size_a - 1;
1333 let end_page_a = align_down(end_a, page_size);
1334 let start_b = offset_b;
1335 let start_page_b = align_down(start_b, page_size);
1336
1337 end_page_a == start_page_b
1338}
1339
1340#[cfg_attr(feature = "profiling", profiling::function)]
1341unsafe fn query_driver(
1342 instance: &erupt::InstanceLoader,
1343 physical_device: vk::PhysicalDevice,
1344) -> (vk::DriverId, bool, u64) {
1345 let mut vulkan_12_properties = vk::PhysicalDeviceVulkan12Properties::default();
1346 let physical_device_properties =
1347 vk::PhysicalDeviceProperties2Builder::new().extend_from(&mut vulkan_12_properties);
1348
1349 let physical_device_properties = instance.get_physical_device_properties2(
1350 physical_device,
1351 Some(physical_device_properties.build_dangling()),
1352 );
1353 let is_integrated =
1354 physical_device_properties.properties.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU;
1355
1356 let buffer_image_granularity = physical_device_properties
1357 .properties
1358 .limits
1359 .buffer_image_granularity;
1360
1361 (
1362 vulkan_12_properties.driver_id,
1363 is_integrated,
1364 buffer_image_granularity,
1365 )
1366}
1367
1368#[inline]
1369fn memory_type_is_compatible(memory_type_index: usize, memory_type_bits: u32) -> bool {
1370 (1 << memory_type_index) & memory_type_bits != 0
1371}
1372
1373#[cfg(feature = "tracing")]
1374fn print_memory_types(
1375 memory_properties: vk::PhysicalDeviceMemoryProperties,
1376 memory_types: &[vk::MemoryType],
1377) -> Result<()> {
1378 info!("Physical device memory heaps:");
1379 for heap_index in 0..memory_properties.memory_heap_count {
1380 let index: usize = heap_index.try_into()?;
1381 info!(
1382 "Heap {}: {:?}",
1383 heap_index, memory_properties.memory_heaps[index].flags
1384 );
1385 info!(
1386 "\tSize = {} MiB",
1387 memory_properties.memory_heaps[index].size / (1024 * 1024)
1388 );
1389 for (type_index, memory_type) in memory_types
1390 .iter()
1391 .enumerate()
1392 .filter(|(_, t)| t.heap_index == heap_index)
1393 {
1394 info!("\tType {}: {:?}", type_index, memory_type.property_flags);
1395 }
1396 }
1397 Ok(())
1398}
1399
1400#[inline]
1401fn calculate_bucket_index(size: vk::DeviceSize) -> u32 {
1402 if size <= 256 {
1403 0
1404 } else {
1405 64 - MINIMAL_BUCKET_SIZE_LOG2 - (size - 1).leading_zeros() - 1
1406 }
1407}
1408
1409#[inline]
1410fn collect_start_chunks(pool: &Mutex<MemoryPool>) -> Vec<NonZeroUsize> {
1411 pool.lock()
1412 .chunks
1413 .iter()
1414 .enumerate()
1415 .filter(|(_, chunk)| {
1416 if let Some(chunk) = chunk {
1417 chunk.previous.is_none()
1418 } else {
1419 false
1420 }
1421 })
1422 .map(|(id, _)| NonZeroUsize::new(id).expect("id was zero"))
1423 .collect()
1424}