1use {
2 crate::{
3 align_down,
4 block::{MemoryBlock, MemoryBlockFlavor},
5 buddy::{BuddyAllocator, BuddyBlock},
6 config::Config,
7 error::AllocationError,
8 freelist::{FreeListAllocator, FreeListBlock},
9 heap::Heap,
10 usage::{MemoryForUsage, UsageFlags},
11 MemoryBounds, Request,
12 },
13 alloc::boxed::Box,
14 core::convert::TryFrom as _,
15 gpu_alloc_types::{
16 AllocationFlags, DeviceProperties, MemoryDevice, MemoryPropertyFlags, MemoryType,
17 OutOfMemory,
18 },
19};
20
21#[derive(Debug)]
23pub struct GpuAllocator<M> {
24 dedicated_threshold: u64,
25 preferred_dedicated_threshold: u64,
26 transient_dedicated_threshold: u64,
27 max_memory_allocation_size: u64,
28 memory_for_usage: MemoryForUsage,
29 memory_types: Box<[MemoryType]>,
30 memory_heaps: Box<[Heap]>,
31 allocations_remains: u32,
32 non_coherent_atom_mask: u64,
33 starting_free_list_chunk: u64,
34 final_free_list_chunk: u64,
35 minimal_buddy_size: u64,
36 initial_buddy_dedicated_size: u64,
37 buffer_device_address: bool,
38
39 buddy_allocators: Box<[Option<BuddyAllocator<M>>]>,
40 freelist_allocators: Box<[Option<FreeListAllocator<M>>]>,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
45#[non_exhaustive]
46pub enum Dedicated {
47 Required,
53
54 Preferred,
59}
60
61impl<M> GpuAllocator<M>
62where
63 M: MemoryBounds + 'static,
64{
65 #[cfg_attr(feature = "tracing", tracing::instrument)]
69 pub fn new(config: Config, props: DeviceProperties<'_>) -> Self {
70 assert!(
71 props.non_coherent_atom_size.is_power_of_two(),
72 "`non_coherent_atom_size` must be power of two"
73 );
74
75 assert!(
76 isize::try_from(props.non_coherent_atom_size).is_ok(),
77 "`non_coherent_atom_size` must fit host address space"
78 );
79
80 GpuAllocator {
81 dedicated_threshold: config.dedicated_threshold,
82 preferred_dedicated_threshold: config
83 .preferred_dedicated_threshold
84 .min(config.dedicated_threshold),
85
86 transient_dedicated_threshold: config
87 .transient_dedicated_threshold
88 .max(config.dedicated_threshold),
89
90 max_memory_allocation_size: props.max_memory_allocation_size,
91
92 memory_for_usage: MemoryForUsage::new(props.memory_types.as_ref()),
93
94 memory_types: props.memory_types.as_ref().iter().copied().collect(),
95 memory_heaps: props
96 .memory_heaps
97 .as_ref()
98 .iter()
99 .map(|heap| Heap::new(heap.size))
100 .collect(),
101
102 buffer_device_address: props.buffer_device_address,
103
104 allocations_remains: props.max_memory_allocation_count,
105 non_coherent_atom_mask: props.non_coherent_atom_size - 1,
106
107 starting_free_list_chunk: config.starting_free_list_chunk,
108 final_free_list_chunk: config.final_free_list_chunk,
109 minimal_buddy_size: config.minimal_buddy_size,
110 initial_buddy_dedicated_size: config.initial_buddy_dedicated_size,
111
112 buddy_allocators: props.memory_types.as_ref().iter().map(|_| None).collect(),
113 freelist_allocators: props.memory_types.as_ref().iter().map(|_| None).collect(),
114 }
115 }
116
117 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
125 pub unsafe fn alloc<MD>(
126 &mut self,
127 device: &impl AsRef<MD>,
128 request: Request,
129 ) -> Result<MemoryBlock<M>, AllocationError>
130 where
131 MD: MemoryDevice<M>,
132 {
133 self.alloc_internal(device.as_ref(), request, None)
134 }
135
136 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
147 pub unsafe fn alloc_with_dedicated<MD>(
148 &mut self,
149 device: &impl AsRef<MD>,
150 request: Request,
151 dedicated: Dedicated,
152 ) -> Result<MemoryBlock<M>, AllocationError>
153 where
154 MD: MemoryDevice<M>,
155 {
156 self.alloc_internal(device.as_ref(), request, Some(dedicated))
157 }
158
159 unsafe fn alloc_internal(
160 &mut self,
161 device: &impl MemoryDevice<M>,
162 mut request: Request,
163 dedicated: Option<Dedicated>,
164 ) -> Result<MemoryBlock<M>, AllocationError> {
165 enum Strategy {
166 Buddy,
167 Dedicated,
168 FreeList,
169 }
170
171 request.usage = with_implicit_usage_flags(request.usage);
172
173 if request.usage.contains(UsageFlags::DEVICE_ADDRESS) {
174 assert!(self.buffer_device_address, "`DEVICE_ADDRESS` cannot be requested when `DeviceProperties::buffer_device_address` is false");
175 }
176
177 if request.size > self.max_memory_allocation_size {
178 return Err(AllocationError::OutOfDeviceMemory);
179 }
180
181 if let Some(Dedicated::Required) = dedicated {
182 if self.allocations_remains == 0 {
183 return Err(AllocationError::TooManyObjects);
184 }
185 }
186
187 if 0 == self.memory_for_usage.mask(request.usage) & request.memory_types {
188 #[cfg(feature = "tracing")]
189 tracing::error!(
190 "Cannot serve request {:?}, no memory among bitset `{}` support usage {:?}",
191 request,
192 request.memory_types,
193 request.usage
194 );
195
196 return Err(AllocationError::NoCompatibleMemoryTypes);
197 }
198
199 let transient = request.usage.contains(UsageFlags::TRANSIENT);
200
201 for &index in self.memory_for_usage.types(request.usage) {
202 if 0 == request.memory_types & (1 << index) {
203 continue;
205 }
206
207 let memory_type = &self.memory_types[index as usize];
208 let heap = memory_type.heap;
209 let heap = &mut self.memory_heaps[heap as usize];
210
211 if request.size > heap.size() {
212 continue;
214 }
215
216 let atom_mask = if host_visible_non_coherent(memory_type.props) {
217 self.non_coherent_atom_mask
218 } else {
219 0
220 };
221
222 let flags = if self.buffer_device_address {
223 AllocationFlags::DEVICE_ADDRESS
224 } else {
225 AllocationFlags::empty()
226 };
227
228 let strategy = match (dedicated, transient) {
229 (Some(Dedicated::Required), _) => Strategy::Dedicated,
230 (Some(Dedicated::Preferred), _)
231 if request.size >= self.preferred_dedicated_threshold =>
232 {
233 Strategy::Dedicated
234 }
235 (_, true) => {
236 let threshold = self.transient_dedicated_threshold.min(heap.size() / 32);
237
238 if request.size < threshold {
239 Strategy::FreeList
240 } else {
241 Strategy::Dedicated
242 }
243 }
244 (_, false) => {
245 let threshold = self.dedicated_threshold.min(heap.size() / 32);
246
247 if request.size < threshold {
248 Strategy::Buddy
249 } else {
250 Strategy::Dedicated
251 }
252 }
253 };
254
255 match strategy {
256 Strategy::Dedicated => {
257 #[cfg(feature = "tracing")]
258 tracing::debug!(
259 "Allocating memory object `{}@{:?}`",
260 request.size,
261 memory_type
262 );
263
264 match device.allocate_memory(request.size, index, flags) {
265 Ok(memory) => {
266 self.allocations_remains -= 1;
267 heap.alloc(request.size);
268
269 return Ok(MemoryBlock::new(
270 index,
271 memory_type.props,
272 0,
273 request.size,
274 atom_mask,
275 MemoryBlockFlavor::Dedicated { memory },
276 ));
277 }
278 Err(OutOfMemory::OutOfDeviceMemory) => continue,
279 Err(OutOfMemory::OutOfHostMemory) => {
280 return Err(AllocationError::OutOfHostMemory)
281 }
282 }
283 }
284 Strategy::FreeList => {
285 let allocator = match &mut self.freelist_allocators[index as usize] {
286 Some(allocator) => allocator,
287 slot => {
288 let starting_free_list_chunk = match align_down(
289 self.starting_free_list_chunk.min(heap.size() / 32),
290 atom_mask,
291 ) {
292 0 => atom_mask,
293 other => other,
294 };
295
296 let final_free_list_chunk = match align_down(
297 self.final_free_list_chunk
298 .max(self.starting_free_list_chunk)
299 .max(self.transient_dedicated_threshold)
300 .min(heap.size() / 32),
301 atom_mask,
302 ) {
303 0 => atom_mask,
304 other => other,
305 };
306
307 slot.get_or_insert(FreeListAllocator::new(
308 starting_free_list_chunk,
309 final_free_list_chunk,
310 index,
311 memory_type.props,
312 if host_visible_non_coherent(memory_type.props) {
313 self.non_coherent_atom_mask
314 } else {
315 0
316 },
317 ))
318 }
319 };
320 let result = allocator.alloc(
321 device,
322 request.size,
323 request.align_mask,
324 flags,
325 heap,
326 &mut self.allocations_remains,
327 );
328
329 match result {
330 Ok(block) => {
331 return Ok(MemoryBlock::new(
332 index,
333 memory_type.props,
334 block.offset,
335 block.size,
336 atom_mask,
337 MemoryBlockFlavor::FreeList {
338 chunk: block.chunk,
339 ptr: block.ptr,
340 memory: block.memory,
341 },
342 ))
343 }
344 Err(AllocationError::OutOfDeviceMemory) => continue,
345 Err(err) => return Err(err),
346 }
347 }
348
349 Strategy::Buddy => {
350 let allocator = match &mut self.buddy_allocators[index as usize] {
351 Some(allocator) => allocator,
352 slot => {
353 let minimal_buddy_size = self
354 .minimal_buddy_size
355 .min(heap.size() / 1024)
356 .next_power_of_two();
357
358 let initial_buddy_dedicated_size = self
359 .initial_buddy_dedicated_size
360 .min(heap.size() / 32)
361 .next_power_of_two();
362
363 slot.get_or_insert(BuddyAllocator::new(
364 minimal_buddy_size,
365 initial_buddy_dedicated_size,
366 index,
367 memory_type.props,
368 if host_visible_non_coherent(memory_type.props) {
369 self.non_coherent_atom_mask
370 } else {
371 0
372 },
373 ))
374 }
375 };
376 let result = allocator.alloc(
377 device,
378 request.size,
379 request.align_mask,
380 flags,
381 heap,
382 &mut self.allocations_remains,
383 );
384
385 match result {
386 Ok(block) => {
387 return Ok(MemoryBlock::new(
388 index,
389 memory_type.props,
390 block.offset,
391 block.size,
392 atom_mask,
393 MemoryBlockFlavor::Buddy {
394 chunk: block.chunk,
395 ptr: block.ptr,
396 index: block.index,
397 memory: block.memory,
398 },
399 ))
400 }
401 Err(AllocationError::OutOfDeviceMemory) => continue,
402 Err(err) => return Err(err),
403 }
404 }
405 }
406 }
407
408 Err(AllocationError::OutOfDeviceMemory)
409 }
410
411 pub unsafe fn import_memory(
436 &mut self,
437 memory: M,
438 memory_type: u32,
439 props: MemoryPropertyFlags,
440 offset: u64,
441 size: u64,
442 ) -> MemoryBlock<M> {
443 let heap = self
445 .memory_types
446 .get(memory_type as usize)
447 .expect("Invalid memory type specified when importing memory")
448 .heap;
449 let heap = &mut self.memory_heaps[heap as usize];
450
451 #[cfg(feature = "tracing")]
452 tracing::debug!(
453 "Importing memory object {:?} `{}@{:?}`",
454 memory,
455 size,
456 memory_type
457 );
458
459 assert_ne!(
460 self.allocations_remains, 0,
461 "Out of allocations when importing a memory block. Ensure you check GpuAllocator::remaining_allocations before import."
462 );
463 self.allocations_remains -= 1;
464
465 let atom_mask = if host_visible_non_coherent(props) {
466 self.non_coherent_atom_mask
467 } else {
468 0
469 };
470
471 heap.alloc(size);
472
473 MemoryBlock::new(
474 memory_type,
475 props,
476 offset,
477 size,
478 atom_mask,
479 MemoryBlockFlavor::Dedicated { memory },
480 )
481 }
482
483 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
492 pub unsafe fn dealloc<MD>(&mut self, device: &impl AsRef<MD>, block: MemoryBlock<M>)
493 where
494 MD: MemoryDevice<M>,
495 {
496 let device = device.as_ref();
497 let memory_type = block.memory_type();
498 let offset = block.offset();
499 let size = block.size();
500 let flavor = block.deallocate();
501 match flavor {
502 MemoryBlockFlavor::Dedicated { memory } => {
503 let heap = self.memory_types[memory_type as usize].heap;
504 device.deallocate_memory(memory);
505 self.allocations_remains += 1;
506 self.memory_heaps[heap as usize].dealloc(size);
507 }
508 MemoryBlockFlavor::Buddy {
509 chunk,
510 ptr,
511 index,
512 memory,
513 } => {
514 let heap = self.memory_types[memory_type as usize].heap;
515 let heap = &mut self.memory_heaps[heap as usize];
516
517 let allocator = self.buddy_allocators[memory_type as usize]
518 .as_mut()
519 .expect("Allocator should exist");
520
521 allocator.dealloc(
522 device,
523 BuddyBlock {
524 memory,
525 ptr,
526 offset,
527 size,
528 chunk,
529 index,
530 },
531 heap,
532 &mut self.allocations_remains,
533 );
534 }
535 MemoryBlockFlavor::FreeList { chunk, ptr, memory } => {
536 let heap = self.memory_types[memory_type as usize].heap;
537 let heap = &mut self.memory_heaps[heap as usize];
538
539 let allocator = self.freelist_allocators[memory_type as usize]
540 .as_mut()
541 .expect("Allocator should exist");
542
543 allocator.dealloc(
544 device,
545 FreeListBlock {
546 memory,
547 ptr,
548 chunk,
549 offset,
550 size,
551 },
552 heap,
553 &mut self.allocations_remains,
554 );
555 }
556 }
557 }
558
559 pub fn max_allocation_size(&self) -> u64 {
561 self.max_memory_allocation_size
562 }
563
564 pub fn remaining_allocations(&self) -> u32 {
570 self.allocations_remains
571 }
572
573 pub unsafe fn set_remaining_allocations(&mut self, remaining: u32) {
580 self.allocations_remains = remaining;
581 }
582
583 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
592 pub unsafe fn cleanup<MD>(&mut self, device: &impl AsRef<MD>)
593 where
594 MD: MemoryDevice<M>,
595 {
596 for (index, allocator) in self
597 .freelist_allocators
598 .iter_mut()
599 .enumerate()
600 .filter_map(|(index, allocator)| Some((index, allocator.as_mut()?)))
601 {
602 let device = device.as_ref();
603 let memory_type = &self.memory_types[index];
604 let heap = memory_type.heap;
605 let heap = &mut self.memory_heaps[heap as usize];
606
607 allocator.cleanup(device, heap, &mut self.allocations_remains);
608 }
609 }
610}
611
612fn host_visible_non_coherent(props: MemoryPropertyFlags) -> bool {
613 (props & (MemoryPropertyFlags::HOST_COHERENT | MemoryPropertyFlags::HOST_VISIBLE))
614 == MemoryPropertyFlags::HOST_VISIBLE
615}
616
617fn with_implicit_usage_flags(usage: UsageFlags) -> UsageFlags {
618 if usage.is_empty() {
619 UsageFlags::FAST_DEVICE_ACCESS
620 } else if usage.intersects(UsageFlags::DOWNLOAD | UsageFlags::UPLOAD) {
621 usage | UsageFlags::HOST_ACCESS
622 } else {
623 usage
624 }
625}