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 ash::vk;
58#[cfg(feature = "tracing")]
59use ash::vk::Handle;
60use parking_lot::{Mutex, RwLock};
61#[cfg(feature = "tracing")]
62use tracing1::{debug, info};
63
64pub use error::AllocatorError;
65
66mod error;
67
68type Result<T> = std::result::Result<T, AllocatorError>;
69
70const MINIMAL_BUCKET_SIZE_LOG2: u32 = 8;
72
73pub trait Lifetime: Debug + Copy + Hash + Eq + PartialEq {}
75
76#[derive(Debug, Clone)]
78pub struct AllocatorDescriptor {
79 pub block_size: u8,
81}
82
83impl Default for AllocatorDescriptor {
84 fn default() -> Self {
85 Self { block_size: 26 }
86 }
87}
88
89#[derive(Debug)]
91pub struct Allocator<LT: Lifetime> {
92 driver_id: vk::DriverId,
93 is_integrated: bool,
94 pools: RwLock<HashMap<LT, Vec<Mutex<MemoryPool>>>>,
95 block_size: vk::DeviceSize,
96 memory_types: Vec<vk::MemoryType>,
97 memory_properties: vk::PhysicalDeviceMemoryProperties,
98 buffer_image_granularity: u64,
99}
100
101impl<LT: Lifetime> Allocator<LT> {
102 #[cfg_attr(feature = "profiling", profiling::function)]
107 pub unsafe fn new(
108 instance: &ash::Instance,
109 physical_device: vk::PhysicalDevice,
110 descriptor: &AllocatorDescriptor,
111 ) -> Result<Self> {
112 let (driver_id, is_integrated, buffer_image_granularity) =
113 query_driver(instance, physical_device);
114
115 #[cfg(feature = "tracing")]
116 debug!("Driver ID of the physical device: {:?}", driver_id);
117
118 let memory_properties = instance.get_physical_device_memory_properties(physical_device);
119
120 let memory_types_count: usize = (memory_properties.memory_type_count).try_into()?;
121 let memory_types = memory_properties.memory_types[..memory_types_count].to_owned();
122
123 #[cfg(feature = "tracing")]
124 print_memory_types(memory_properties, &memory_types)?;
125
126 let block_size: vk::DeviceSize = (2u64).pow(descriptor.block_size.into());
127
128 Ok(Self {
129 driver_id,
130 is_integrated,
131 pools: RwLock::default(),
132 block_size,
133 memory_types,
134 memory_properties,
135 buffer_image_granularity,
136 })
137 }
138
139 #[cfg_attr(feature = "profiling", profiling::function)]
144 pub unsafe fn allocate_memory_for_buffer(
145 &self,
146 device: &ash::Device,
147 buffer: vk::Buffer,
148 location: MemoryLocation,
149 lifetime: LT,
150 ) -> Result<Allocation<LT>> {
151 let info = vk::BufferMemoryRequirementsInfo2::default().buffer(buffer);
152 let mut dedicated_requirements = vk::MemoryDedicatedRequirements::default();
153 let mut requirements =
154 vk::MemoryRequirements2::default().push_next(&mut dedicated_requirements);
155
156 device.get_buffer_memory_requirements2(&info, &mut requirements);
157
158 let memory_requirements = requirements.memory_requirements;
159
160 let is_dedicated = dedicated_requirements.prefers_dedicated_allocation == 1
161 || dedicated_requirements.requires_dedicated_allocation == 1;
162
163 let alloc_decs = AllocationDescriptor {
164 requirements: memory_requirements,
165 location,
166 lifetime,
167 is_dedicated,
168 is_optimal: false,
169 };
170
171 self.allocate(device, &alloc_decs)
172 }
173
174 #[cfg_attr(feature = "profiling", profiling::function)]
179 pub unsafe fn allocate_memory_for_image(
180 &self,
181 device: &ash::Device,
182 image: vk::Image,
183 location: MemoryLocation,
184 lifetime: LT,
185 is_optimal: bool,
186 ) -> Result<Allocation<LT>> {
187 let info = vk::ImageMemoryRequirementsInfo2::default().image(image);
188 let mut dedicated_requirements = vk::MemoryDedicatedRequirements::default();
189 let mut requirements =
190 vk::MemoryRequirements2::default().push_next(&mut dedicated_requirements);
191
192 device.get_image_memory_requirements2(&info, &mut requirements);
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: &ash::Device,
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: &ash::Device,
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.as_raw(),
420 allocation.offset,
421 allocation.size
422 );
423 memory_pool.lock().free_chunk(chunk_key)?;
424 } else {
425 #[cfg(feature = "tracing")]
427 debug!(
428 "Deallocating dedicated device memory 0x{:02x} size {}",
429 allocation.device_memory.as_raw(),
430 allocation.size
431 );
432 memory_pool
433 .lock()
434 .free_block(device, allocation.block_key)?;
435 }
436
437 Ok(())
438 }
439
440 #[cfg_attr(feature = "profiling", profiling::function)]
446 pub unsafe fn cleanup(&self, device: &ash::Device) {
447 for (_, mut lifetime_pools) in self.pools.write().drain() {
448 lifetime_pools.drain(..).for_each(|pool| {
449 pool.lock().blocks.iter_mut().for_each(|block| {
450 if let Some(block) = block {
451 block.destroy(device)
452 }
453 })
454 });
455 }
456 }
457
458 #[cfg_attr(feature = "profiling", profiling::function)]
460 pub fn allocation_count(&self) -> usize {
461 let mut count = 0;
462 for (_, lifetime_pools) in self.pools.read().iter() {
463 lifetime_pools.iter().for_each(|pool| {
464 let pool = pool.lock();
465 for chunk in pool.chunks.iter().flatten() {
466 if chunk.chunk_type != ChunkType::Free {
467 count += 1;
468 }
469 }
470 });
471 }
472
473 for (_, lifetime_pools) in self.pools.read().iter() {
474 lifetime_pools.iter().for_each(|pool| {
475 let pool = pool.lock();
476 for block in pool.blocks.iter().flatten() {
477 if block.is_dedicated {
478 count += 1;
479 }
480 }
481 });
482 }
483
484 count
485 }
486
487 #[cfg_attr(feature = "profiling", profiling::function)]
489 pub fn unused_range_count(&self) -> usize {
490 let mut unused_count: usize = 0;
491
492 for (_, lifetime_pools) in self.pools.read().iter() {
493 lifetime_pools.iter().for_each(|pool| {
494 collect_start_chunks(pool).iter().for_each(|key| {
495 let mut next_key: NonZeroUsize = *key;
496 let mut previous_size: vk::DeviceSize = 0;
497 let mut previous_offset: vk::DeviceSize = 0;
498 loop {
499 let pool = pool.lock();
500 let chunk = pool.chunks[next_key.get()]
501 .as_ref()
502 .expect("can't find chunk in chunk list");
503 if chunk.offset != previous_offset + previous_size {
504 unused_count += 1;
505 }
506
507 if let Some(key) = chunk.next {
508 next_key = key
509 } else {
510 break;
511 }
512
513 previous_size = chunk.size;
514 previous_offset = chunk.offset
515 }
516 });
517 })
518 }
519
520 unused_count
521 }
522
523 #[cfg_attr(feature = "profiling", profiling::function)]
525 pub fn used_bytes(&self) -> vk::DeviceSize {
526 let mut bytes = 0;
527
528 for (_, lifetime_pools) in self.pools.read().iter() {
529 lifetime_pools.iter().for_each(|pool| {
530 let pool = pool.lock();
531 for chunk in pool.chunks.iter().flatten() {
532 if chunk.chunk_type != ChunkType::Free {
533 bytes += chunk.size;
534 }
535 }
536 });
537 }
538
539 for (_, lifetime_pools) in self.pools.read().iter() {
540 lifetime_pools.iter().for_each(|pool| {
541 let pool = pool.lock();
542 for block in pool.blocks.iter().flatten() {
543 if block.is_dedicated {
544 bytes += block.size;
545 }
546 }
547 });
548 }
549
550 bytes
551 }
552
553 #[cfg_attr(feature = "profiling", profiling::function)]
555 pub fn unused_bytes(&self) -> vk::DeviceSize {
556 let mut unused_bytes: vk::DeviceSize = 0;
557
558 for (_, lifetime_pools) in self.pools.read().iter() {
559 lifetime_pools.iter().for_each(|pool| {
560 collect_start_chunks(pool).iter().for_each(|key| {
561 let mut next_key: NonZeroUsize = *key;
562 let mut previous_size: vk::DeviceSize = 0;
563 let mut previous_offset: vk::DeviceSize = 0;
564 loop {
565 let pool = pool.lock();
566 let chunk = pool.chunks[next_key.get()]
567 .as_ref()
568 .expect("can't find chunk in chunk list");
569 if chunk.offset != previous_offset + previous_size {
570 unused_bytes += chunk.offset - (previous_offset + previous_size);
571 }
572
573 if let Some(key) = chunk.next {
574 next_key = key
575 } else {
576 break;
577 }
578
579 previous_size = chunk.size;
580 previous_offset = chunk.offset
581 }
582 });
583 });
584 }
585
586 unused_bytes
587 }
588
589 #[cfg_attr(feature = "profiling", profiling::function)]
591 pub fn block_count(&self) -> usize {
592 let mut count: usize = 0;
593
594 for (_, lifetime_pools) in self.pools.read().iter() {
595 count += lifetime_pools
596 .iter()
597 .map(|pool| pool.lock().blocks.len())
598 .sum::<usize>();
599 }
600
601 count
602 }
603}
604
605#[derive(Debug, Copy, Clone, Eq, PartialEq)]
606#[repr(u8)]
607enum ChunkType {
608 Free,
609 Linear,
610 Optimal,
611}
612
613impl ChunkType {
614 #[cfg_attr(feature = "profiling", profiling::function)]
618 fn granularity_conflict(self, other: ChunkType) -> bool {
619 if self == ChunkType::Free || other == ChunkType::Free {
620 return false;
621 }
622
623 self != other
624 }
625}
626
627#[derive(Debug, Clone, Copy, PartialEq, Eq)]
629pub enum MemoryLocation {
630 CpuToGpu,
632 GpuOnly,
634 GpuToCpu,
636}
637
638#[derive(Clone, Debug)]
640pub struct AllocationDescriptor<LT: Lifetime> {
641 pub location: MemoryLocation,
643 pub requirements: vk::MemoryRequirements,
645 pub lifetime: LT,
647 pub is_dedicated: bool,
649 pub is_optimal: bool,
652}
653
654#[derive(Clone, Debug)]
656pub struct Allocation<LT: Lifetime> {
657 memory_type_index: u32,
658 lifetime: LT,
659 block_key: NonZeroUsize,
660 chunk_key: Option<NonZeroUsize>,
661 mapped_ptr: Option<std::ptr::NonNull<c_void>>,
662
663 device_memory: vk::DeviceMemory,
664 offset: vk::DeviceSize,
665 size: vk::DeviceSize,
666}
667
668unsafe impl<LT: Lifetime> Send for Allocation<LT> {}
669
670unsafe impl<LT: Lifetime> Sync for Allocation<LT> {}
671
672impl<LT: Lifetime> Allocation<LT> {
673 #[inline]
675 pub fn device_memory(&self) -> vk::DeviceMemory {
676 self.device_memory
677 }
678
679 #[inline]
681 pub fn offset(&self) -> vk::DeviceSize {
682 self.offset
683 }
684
685 #[inline]
687 pub fn size(&self) -> vk::DeviceSize {
688 self.size
689 }
690
691 #[cfg_attr(feature = "profiling", profiling::function)]
697 pub unsafe fn mapped_slice(&self) -> Result<Option<&[u8]>> {
698 let slice = if let Some(ptr) = self.mapped_ptr {
699 let size = self.size.try_into()?;
700 #[allow(clippy::as_conversions)]
701 Some(std::slice::from_raw_parts(ptr.as_ptr() as *const _, size))
702 } else {
703 None
704 };
705 Ok(slice)
706 }
707
708 #[cfg_attr(feature = "profiling", profiling::function)]
714 pub unsafe fn mapped_slice_mut(&mut self) -> Result<Option<&mut [u8]>> {
715 let slice = if let Some(ptr) = self.mapped_ptr.as_mut() {
716 let size = self.size.try_into()?;
717 #[allow(clippy::as_conversions)]
718 Some(std::slice::from_raw_parts_mut(ptr.as_ptr() as *mut _, size))
719 } else {
720 None
721 };
722 Ok(slice)
723 }
724}
725
726#[derive(Clone, Debug)]
727struct BestFitCandidate {
728 aligned_offset: u64,
729 key: NonZeroUsize,
730 free_list_index: usize,
731 free_size: vk::DeviceSize,
732}
733
734#[derive(Debug)]
739struct MemoryPool {
740 memory_type_index: u32,
741 block_size: vk::DeviceSize,
742 is_mappable: bool,
743 blocks: Vec<Option<MemoryBlock>>,
744 chunks: Vec<Option<MemoryChunk>>,
745 free_chunks: Vec<Vec<NonZeroUsize>>,
746 max_bucket_index: u32,
747
748 free_block_slots: Vec<NonZeroUsize>,
750 free_chunk_slots: Vec<NonZeroUsize>,
751}
752
753impl MemoryPool {
754 #[cfg_attr(feature = "profiling", profiling::function)]
755 fn new(block_size: vk::DeviceSize, memory_type_index: u32, is_mappable: bool) -> Result<Self> {
756 let mut blocks = Vec::with_capacity(128);
757 let mut chunks = Vec::with_capacity(128);
758
759 blocks.push(None);
761 chunks.push(None);
762
763 let bucket_count = 64 - MINIMAL_BUCKET_SIZE_LOG2 - (block_size - 1).leading_zeros();
766
767 let mut free_chunks = Vec::with_capacity(bucket_count.try_into()?);
770 for i in 0..bucket_count {
771 let min_bucket_element_size = if i == 0 {
772 512
773 } else {
774 2u64.pow(MINIMAL_BUCKET_SIZE_LOG2 - 1 + i)
775 };
776 let max_elements: usize = (block_size / min_bucket_element_size).try_into()?;
777 free_chunks.push(Vec::with_capacity(512.min(max_elements)));
778 }
779
780 Ok(Self {
781 memory_type_index,
782 block_size,
783 is_mappable,
784 blocks,
785 chunks,
786 free_chunks,
787 free_block_slots: Vec::with_capacity(16),
788 free_chunk_slots: Vec::with_capacity(16),
789 max_bucket_index: bucket_count - 1,
790 })
791 }
792
793 #[cfg_attr(feature = "profiling", profiling::function)]
794 fn add_block(&mut self, block: MemoryBlock) -> NonZeroUsize {
795 if let Some(key) = self.free_block_slots.pop() {
796 self.blocks[key.get()] = Some(block);
797 key
798 } else {
799 let key = self.blocks.len();
800 self.blocks.push(Some(block));
801 NonZeroUsize::new(key).expect("new block key was zero")
802 }
803 }
804
805 #[cfg_attr(feature = "profiling", profiling::function)]
806 fn add_chunk(&mut self, chunk: MemoryChunk) -> NonZeroUsize {
807 if let Some(key) = self.free_chunk_slots.pop() {
808 self.chunks[key.get()] = Some(chunk);
809 key
810 } else {
811 let key = self.chunks.len();
812 self.chunks.push(Some(chunk));
813 NonZeroUsize::new(key).expect("new chunk key was zero")
814 }
815 }
816
817 #[cfg_attr(feature = "profiling", profiling::function)]
818 unsafe fn allocate_dedicated<LT: Lifetime>(
819 &mut self,
820 device: &ash::Device,
821 size: vk::DeviceSize,
822 lifetime: LT,
823 ) -> Result<Allocation<LT>> {
824 let block = MemoryBlock::new(device, size, self.memory_type_index, self.is_mappable, true)?;
825
826 let device_memory = block.device_memory;
827 let mapped_ptr = std::ptr::NonNull::new(block.mapped_ptr);
828
829 let key = self.add_block(block);
830
831 Ok(Allocation {
832 memory_type_index: self.memory_type_index,
833 lifetime,
834 block_key: key,
835 chunk_key: None,
836 device_memory,
837 offset: 0,
838 size,
839 mapped_ptr,
840 })
841 }
842
843 #[cfg_attr(feature = "profiling", profiling::function)]
844 unsafe fn allocate<LT: Lifetime>(
845 &mut self,
846 device: &ash::Device,
847 buffer_image_granularity: u64,
848 size: vk::DeviceSize,
849 alignment: vk::DeviceSize,
850 lifetime: LT,
851 is_optimal: bool,
852 ) -> Result<Allocation<LT>> {
853 let mut bucket_index = calculate_bucket_index(size);
854
855 debug_assert!(bucket_index <= self.max_bucket_index);
857
858 let chunk_type = if is_optimal {
859 ChunkType::Optimal
860 } else {
861 ChunkType::Linear
862 };
863
864 loop {
865 if bucket_index > self.max_bucket_index {
867 self.allocate_new_block(device)?;
868 bucket_index = self.max_bucket_index;
869 }
870
871 let index: usize = bucket_index.try_into()?;
872 let free_list = &self.free_chunks[index];
873
874 let mut best_fit_candidate: Option<BestFitCandidate> = None;
876 for (index, key) in free_list.iter().enumerate() {
877 let chunk = &self.chunks[key.get()]
878 .as_ref()
879 .expect("can't find chunk in chunk list");
880 debug_assert!(chunk.chunk_type == ChunkType::Free);
881
882 if chunk.size < size {
883 continue;
884 }
885
886 let mut aligned_offset = 0;
887
888 if let Some(previous) = chunk.previous {
891 let previous = self
892 .chunks
893 .get(previous.get())
894 .ok_or_else(|| {
895 AllocatorError::Internal("can't find previous chunk".into())
896 })?
897 .as_ref()
898 .ok_or_else(|| {
899 AllocatorError::Internal("previous chunk was empty".into())
900 })?;
901
902 aligned_offset = align_up(chunk.offset, alignment);
903
904 if previous.chunk_type.granularity_conflict(chunk_type)
905 && is_on_same_page(
906 previous.offset,
907 previous.size,
908 aligned_offset,
909 buffer_image_granularity,
910 )
911 {
912 aligned_offset = align_up(aligned_offset, buffer_image_granularity);
913 }
914 }
915
916 if let Some(next) = chunk.next {
917 let next = self
918 .chunks
919 .get(next.get())
920 .ok_or_else(|| AllocatorError::Internal("can't find next chunk".into()))?
921 .as_ref()
922 .ok_or_else(|| AllocatorError::Internal("next chunk was empty".into()))?;
923
924 if next.chunk_type.granularity_conflict(chunk_type)
925 && is_on_same_page(
926 next.offset,
927 next.size,
928 aligned_offset,
929 buffer_image_granularity,
930 )
931 {
932 continue;
933 }
934 }
935
936 let padding = aligned_offset - chunk.offset;
937 let aligned_size = padding + size;
938
939 if chunk.size >= aligned_size {
941 let free_size = chunk.size - aligned_size;
942
943 let best_fit_size = if let Some(best_fit) = &best_fit_candidate {
944 best_fit.free_size
945 } else {
946 u64::MAX
947 };
948
949 if free_size < best_fit_size {
950 best_fit_candidate = Some(BestFitCandidate {
951 aligned_offset,
952 key: *key,
953 free_list_index: index,
954 free_size,
955 })
956 }
957 }
958 }
959
960 if let Some(candidate) = &best_fit_candidate {
962 self.free_chunks
963 .get_mut(index)
964 .ok_or_else(|| AllocatorError::Internal("can't find free chunk".to_owned()))?
965 .remove(candidate.free_list_index);
966
967 let new_free_chunk_key = if candidate.free_size != 0 {
969 let candidate_chunk = self.chunks[candidate.key.get()]
970 .as_ref()
971 .expect("can't find candidate in chunk list")
972 .clone();
973
974 let new_free_offset = candidate.aligned_offset + size;
975 let new_free_size =
976 (candidate_chunk.offset + candidate_chunk.size) - new_free_offset;
977
978 let new_free_chunk = MemoryChunk {
979 block_key: candidate_chunk.block_key,
980 size: new_free_size,
981 offset: new_free_offset,
982 previous: Some(candidate.key),
983 next: candidate_chunk.next,
984 chunk_type: ChunkType::Free,
985 };
986
987 let new_free_chunk_key = self.add_chunk(new_free_chunk);
988
989 let rhs_bucket_index: usize =
990 calculate_bucket_index(new_free_size).try_into()?;
991 self.free_chunks[rhs_bucket_index].push(new_free_chunk_key);
992
993 Some(new_free_chunk_key)
994 } else {
995 None
996 };
997
998 let candidate_chunk = self.chunks[candidate.key.get()]
999 .as_mut()
1000 .expect("can't find chunk in chunk list");
1001 candidate_chunk.chunk_type = chunk_type;
1002 candidate_chunk.offset = candidate.aligned_offset;
1003 candidate_chunk.size = size;
1004
1005 let block = self.blocks[candidate_chunk.block_key.get()]
1006 .as_ref()
1007 .expect("can't find block in block list");
1008
1009 let mapped_ptr = if !block.mapped_ptr.is_null() {
1010 let offset: usize = candidate_chunk.offset.try_into()?;
1011 let offset_ptr = block.mapped_ptr.add(offset);
1012 std::ptr::NonNull::new(offset_ptr)
1013 } else {
1014 None
1015 };
1016
1017 let allocation = Allocation {
1018 memory_type_index: self.memory_type_index,
1019 lifetime,
1020 block_key: candidate_chunk.block_key,
1021 chunk_key: Some(candidate.key),
1022 device_memory: block.device_memory,
1023 offset: candidate_chunk.offset,
1024 size: candidate_chunk.size,
1025 mapped_ptr,
1026 };
1027
1028 let old_next_key = if let Some(new_free_chunk_key) = new_free_chunk_key {
1030 let old_next_key = candidate_chunk.next;
1031 candidate_chunk.next = Some(new_free_chunk_key);
1032 old_next_key
1033 } else {
1034 None
1035 };
1036
1037 if let Some(old_next_key) = old_next_key {
1038 let old_next = self.chunks[old_next_key.get()]
1039 .as_mut()
1040 .expect("can't find old next in chunk list");
1041 old_next.previous = new_free_chunk_key;
1042 }
1043
1044 return Ok(allocation);
1045 }
1046
1047 bucket_index += 1;
1048 }
1049 }
1050
1051 #[cfg_attr(feature = "profiling", profiling::function)]
1052 unsafe fn allocate_new_block(&mut self, device: &ash::Device) -> Result<()> {
1053 let block = MemoryBlock::new(
1054 device,
1055 self.block_size,
1056 self.memory_type_index,
1057 self.is_mappable,
1058 false,
1059 )?;
1060
1061 let block_key = self.add_block(block);
1062
1063 let chunk = MemoryChunk {
1064 block_key,
1065 size: self.block_size,
1066 offset: 0,
1067 previous: None,
1068 next: None,
1069 chunk_type: ChunkType::Free,
1070 };
1071
1072 let chunk_key = self.add_chunk(chunk);
1073
1074 let index: usize = self.max_bucket_index.try_into()?;
1075 self.free_chunks[index].push(chunk_key);
1076
1077 Ok(())
1078 }
1079
1080 #[cfg_attr(feature = "profiling", profiling::function)]
1081 fn free_chunk(&mut self, chunk_key: NonZeroUsize) -> Result<()> {
1082 let (previous_key, next_key, size) = {
1083 let chunk = self.chunks[chunk_key.get()]
1084 .as_mut()
1085 .ok_or(AllocatorError::CantFindChunk)?;
1086 chunk.chunk_type = ChunkType::Free;
1087 (chunk.previous, chunk.next, chunk.size)
1088 };
1089 self.add_to_free_list(chunk_key, size)?;
1090
1091 self.merge_free_neighbor(next_key, chunk_key, false)?;
1092 self.merge_free_neighbor(previous_key, chunk_key, true)?;
1093
1094 Ok(())
1095 }
1096
1097 #[cfg_attr(feature = "profiling", profiling::function)]
1098 fn merge_free_neighbor(
1099 &mut self,
1100 neighbor: Option<NonZeroUsize>,
1101 chunk_key: NonZeroUsize,
1102 neighbor_is_lhs: bool,
1103 ) -> Result<()> {
1104 if let Some(neighbor_key) = neighbor {
1105 if self.chunks[neighbor_key.get()]
1106 .as_ref()
1107 .expect("can't find chunk in chunk list")
1108 .chunk_type
1109 == ChunkType::Free
1110 {
1111 if neighbor_is_lhs {
1112 self.merge_rhs_into_lhs_chunk(neighbor_key, chunk_key)?;
1113 } else {
1114 self.merge_rhs_into_lhs_chunk(chunk_key, neighbor_key)?;
1115 }
1116 }
1117 }
1118 Ok(())
1119 }
1120
1121 #[cfg_attr(feature = "profiling", profiling::function)]
1122 fn merge_rhs_into_lhs_chunk(
1123 &mut self,
1124 lhs_chunk_key: NonZeroUsize,
1125 rhs_chunk_key: NonZeroUsize,
1126 ) -> Result<()> {
1127 let (rhs_size, rhs_offset, rhs_next) = {
1128 let chunk = self.chunks[rhs_chunk_key.get()]
1129 .take()
1130 .expect("can't find chunk in chunk list");
1131 self.free_chunk_slots.push(rhs_chunk_key);
1132 debug_assert!(chunk.previous == Some(lhs_chunk_key));
1133
1134 self.remove_from_free_list(rhs_chunk_key, chunk.size)?;
1135
1136 (chunk.size, chunk.offset, chunk.next)
1137 };
1138
1139 let lhs_previous_key = self.chunks[lhs_chunk_key.get()]
1140 .as_mut()
1141 .expect("can't find chunk in chunk list")
1142 .previous;
1143
1144 let lhs_offset = if let Some(lhs_previous_key) = lhs_previous_key {
1145 let lhs_previous = self.chunks[lhs_previous_key.get()]
1146 .as_mut()
1147 .expect("can't find chunk in chunk list");
1148 lhs_previous.offset + lhs_previous.size
1149 } else {
1150 0
1151 };
1152
1153 let lhs_chunk = self.chunks[lhs_chunk_key.get()]
1154 .as_mut()
1155 .expect("can't find chunk in chunk list");
1156
1157 debug_assert!(lhs_chunk.next == Some(rhs_chunk_key));
1158
1159 let old_size = lhs_chunk.size;
1160
1161 lhs_chunk.next = rhs_next;
1162 lhs_chunk.size = (rhs_offset + rhs_size) - lhs_offset;
1163 lhs_chunk.offset = lhs_offset;
1164
1165 let new_size = lhs_chunk.size;
1166
1167 self.remove_from_free_list(lhs_chunk_key, old_size)?;
1168 self.add_to_free_list(lhs_chunk_key, new_size)?;
1169
1170 if let Some(rhs_next) = rhs_next {
1171 let chunk = self.chunks[rhs_next.get()]
1172 .as_mut()
1173 .expect("previous memory chunk was None");
1174 chunk.previous = Some(lhs_chunk_key);
1175 }
1176
1177 Ok(())
1178 }
1179
1180 #[cfg_attr(feature = "profiling", profiling::function)]
1181 unsafe fn free_block(&mut self, device: &ash::Device, block_key: NonZeroUsize) -> Result<()> {
1182 let mut block = self.blocks[block_key.get()]
1183 .take()
1184 .ok_or(AllocatorError::CantFindBlock)?;
1185
1186 block.destroy(device);
1187
1188 self.free_block_slots.push(block_key);
1189
1190 Ok(())
1191 }
1192
1193 #[cfg_attr(feature = "profiling", profiling::function)]
1194 fn add_to_free_list(&mut self, chunk_key: NonZeroUsize, size: vk::DeviceSize) -> Result<()> {
1195 let chunk_bucket_index: usize = calculate_bucket_index(size).try_into()?;
1196 self.free_chunks[chunk_bucket_index].push(chunk_key);
1197 Ok(())
1198 }
1199
1200 #[cfg_attr(feature = "profiling", profiling::function)]
1201 fn remove_from_free_list(
1202 &mut self,
1203 chunk_key: NonZeroUsize,
1204 chunk_size: vk::DeviceSize,
1205 ) -> Result<()> {
1206 let bucket_index: usize = calculate_bucket_index(chunk_size).try_into()?;
1207 let free_list_index = self.free_chunks[bucket_index]
1208 .iter()
1209 .enumerate()
1210 .find(|(_, key)| **key == chunk_key)
1211 .map(|(index, _)| index)
1212 .expect("can't find chunk in chunk list");
1213 self.free_chunks[bucket_index].remove(free_list_index);
1214 Ok(())
1215 }
1216}
1217
1218#[derive(Clone, Debug)]
1220struct MemoryChunk {
1221 block_key: NonZeroUsize,
1222 size: vk::DeviceSize,
1223 offset: vk::DeviceSize,
1224 previous: Option<NonZeroUsize>,
1225 next: Option<NonZeroUsize>,
1226 chunk_type: ChunkType,
1227}
1228
1229#[derive(Debug)]
1231struct MemoryBlock {
1232 device_memory: vk::DeviceMemory,
1233 size: vk::DeviceSize,
1234 mapped_ptr: *mut c_void,
1235 is_dedicated: bool,
1236}
1237
1238unsafe impl Send for MemoryBlock {}
1239
1240impl MemoryBlock {
1241 #[cfg_attr(feature = "profiling", profiling::function)]
1242 unsafe fn new(
1243 device: &ash::Device,
1244 size: vk::DeviceSize,
1245 memory_type_index: u32,
1246 is_mappable: bool,
1247 is_dedicated: bool,
1248 ) -> Result<Self> {
1249 #[cfg(feature = "vk-buffer-device-address")]
1250 let device_memory = {
1251 let alloc_info = vk::MemoryAllocateInfo::default()
1252 .allocation_size(size)
1253 .memory_type_index(memory_type_index);
1254
1255 let allocation_flags = vk::MemoryAllocateFlags::DEVICE_ADDRESS;
1256 let mut flags_info = vk::MemoryAllocateFlagsInfo::default().flags(allocation_flags);
1257 let alloc_info = alloc_info.push_next(&mut flags_info);
1258
1259 device
1260 .allocate_memory(&alloc_info, None)
1261 .map_err(|_| AllocatorError::OutOfMemory)?
1262 };
1263
1264 #[cfg(not(feature = "vk-buffer-device-address"))]
1265 let device_memory = {
1266 let alloc_info = vk::MemoryAllocateInfo::default()
1267 .allocation_size(size)
1268 .memory_type_index(memory_type_index);
1269
1270 device
1271 .allocate_memory(&alloc_info, None)
1272 .map_err(|_| AllocatorError::OutOfMemory)?
1273 };
1274
1275 let mapped_ptr = if is_mappable {
1276 let mapped_ptr = device.map_memory(
1277 device_memory,
1278 0,
1279 vk::WHOLE_SIZE,
1280 vk::MemoryMapFlags::empty(),
1281 );
1282
1283 match mapped_ptr.ok() {
1284 Some(mapped_ptr) => mapped_ptr,
1285 None => {
1286 device.free_memory(device_memory, None);
1287 return Err(AllocatorError::FailedToMap);
1288 }
1289 }
1290 } else {
1291 ptr::null_mut()
1292 };
1293
1294 Ok(Self {
1295 device_memory,
1296 size,
1297 mapped_ptr,
1298 is_dedicated,
1299 })
1300 }
1301
1302 #[cfg_attr(feature = "profiling", profiling::function)]
1303 unsafe fn destroy(&mut self, device: &ash::Device) {
1304 if !self.mapped_ptr.is_null() {
1305 device.unmap_memory(self.device_memory);
1306 }
1307 device.free_memory(self.device_memory, None);
1308 self.device_memory = vk::DeviceMemory::null()
1309 }
1310}
1311
1312#[inline]
1313fn align_up(offset: vk::DeviceSize, alignment: vk::DeviceSize) -> vk::DeviceSize {
1314 (offset + (alignment - 1)) & !(alignment - 1)
1315}
1316
1317#[inline]
1318fn align_down(offset: vk::DeviceSize, alignment: vk::DeviceSize) -> vk::DeviceSize {
1319 offset & !(alignment - 1)
1320}
1321
1322fn is_on_same_page(offset_a: u64, size_a: u64, offset_b: u64, page_size: u64) -> bool {
1323 let end_a = offset_a + size_a - 1;
1324 let end_page_a = align_down(end_a, page_size);
1325 let start_b = offset_b;
1326 let start_page_b = align_down(start_b, page_size);
1327
1328 end_page_a == start_page_b
1329}
1330
1331#[cfg_attr(feature = "profiling", profiling::function)]
1332unsafe fn query_driver(
1333 instance: &ash::Instance,
1334 physical_device: vk::PhysicalDevice,
1335) -> (vk::DriverId, bool, u64) {
1336 let mut vulkan_12_properties = vk::PhysicalDeviceVulkan12Properties::default();
1337 let mut physical_device_properties =
1338 vk::PhysicalDeviceProperties2::default().push_next(&mut vulkan_12_properties);
1339
1340 instance.get_physical_device_properties2(physical_device, &mut physical_device_properties);
1341 let is_integrated =
1342 physical_device_properties.properties.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU;
1343
1344 let buffer_image_granularity = physical_device_properties
1345 .properties
1346 .limits
1347 .buffer_image_granularity;
1348
1349 (
1350 vulkan_12_properties.driver_id,
1351 is_integrated,
1352 buffer_image_granularity,
1353 )
1354}
1355
1356#[inline]
1357fn memory_type_is_compatible(memory_type_index: usize, memory_type_bits: u32) -> bool {
1358 (1 << memory_type_index) & memory_type_bits != 0
1359}
1360
1361#[cfg(feature = "tracing")]
1362fn print_memory_types(
1363 memory_properties: vk::PhysicalDeviceMemoryProperties,
1364 memory_types: &[vk::MemoryType],
1365) -> Result<()> {
1366 info!("Physical device memory heaps:");
1367 for heap_index in 0..memory_properties.memory_heap_count {
1368 let index: usize = heap_index.try_into()?;
1369 info!(
1370 "Heap {}: {:?}",
1371 heap_index, memory_properties.memory_heaps[index].flags
1372 );
1373 info!(
1374 "\tSize = {} MiB",
1375 memory_properties.memory_heaps[index].size / (1024 * 1024)
1376 );
1377 for (type_index, memory_type) in memory_types
1378 .iter()
1379 .enumerate()
1380 .filter(|(_, t)| t.heap_index == heap_index)
1381 {
1382 info!("\tType {}: {:?}", type_index, memory_type.property_flags);
1383 }
1384 }
1385 Ok(())
1386}
1387
1388#[inline]
1389fn calculate_bucket_index(size: vk::DeviceSize) -> u32 {
1390 if size <= 256 {
1391 0
1392 } else {
1393 64 - MINIMAL_BUCKET_SIZE_LOG2 - (size - 1).leading_zeros() - 1
1394 }
1395}
1396
1397#[inline]
1398fn collect_start_chunks(pool: &Mutex<MemoryPool>) -> Vec<NonZeroUsize> {
1399 pool.lock()
1400 .chunks
1401 .iter()
1402 .enumerate()
1403 .filter(|(_, chunk)| {
1404 if let Some(chunk) = chunk {
1405 chunk.previous.is_none()
1406 } else {
1407 false
1408 }
1409 })
1410 .map(|(id, _)| NonZeroUsize::new(id).expect("id was zero"))
1411 .collect()
1412}