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(
126 &mut self,
127 device: &impl MemoryDevice<M>,
128 request: Request,
129 ) -> Result<MemoryBlock<M>, AllocationError>
130 {
131 self.alloc_internal(device, request, None)
132 }
133
134 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
145 pub unsafe fn alloc_with_dedicated(
146 &mut self,
147 device: &impl MemoryDevice<M>,
148 request: Request,
149 dedicated: Dedicated,
150 ) -> Result<MemoryBlock<M>, AllocationError>
151 {
152 self.alloc_internal(device, request, Some(dedicated))
153 }
154
155 unsafe fn alloc_internal(
156 &mut self,
157 device: &impl MemoryDevice<M>,
158 mut request: Request,
159 dedicated: Option<Dedicated>,
160 ) -> Result<MemoryBlock<M>, AllocationError> {
161 enum Strategy {
162 Buddy,
163 Dedicated,
164 FreeList,
165 }
166
167 request.usage = with_implicit_usage_flags(request.usage);
168
169 if request.usage.contains(UsageFlags::DEVICE_ADDRESS) {
170 assert!(self.buffer_device_address, "`DEVICE_ADDRESS` cannot be requested when `DeviceProperties::buffer_device_address` is false");
171 }
172
173 if request.size > self.max_memory_allocation_size {
174 return Err(AllocationError::OutOfDeviceMemory);
175 }
176
177 if let Some(Dedicated::Required) = dedicated {
178 if self.allocations_remains == 0 {
179 return Err(AllocationError::TooManyObjects);
180 }
181 }
182
183 if 0 == self.memory_for_usage.mask(request.usage) & request.memory_types {
184 #[cfg(feature = "tracing")]
185 tracing::error!(
186 "Cannot serve request {:?}, no memory among bitset `{}` support usage {:?}",
187 request,
188 request.memory_types,
189 request.usage
190 );
191
192 return Err(AllocationError::NoCompatibleMemoryTypes);
193 }
194
195 let transient = request.usage.contains(UsageFlags::TRANSIENT);
196
197 for &index in self.memory_for_usage.types(request.usage) {
198 if 0 == request.memory_types & (1 << index) {
199 continue;
201 }
202
203 let memory_type = &self.memory_types[index as usize];
204 let heap = memory_type.heap;
205 let heap = &mut self.memory_heaps[heap as usize];
206
207 if request.size > heap.size() {
208 continue;
210 }
211
212 let atom_mask = if host_visible_non_coherent(memory_type.props) {
213 self.non_coherent_atom_mask
214 } else {
215 0
216 };
217
218 let flags = if self.buffer_device_address {
219 AllocationFlags::DEVICE_ADDRESS
220 } else {
221 AllocationFlags::empty()
222 };
223
224 let strategy = match (dedicated, transient) {
225 (Some(Dedicated::Required), _) => Strategy::Dedicated,
226 (Some(Dedicated::Preferred), _)
227 if request.size >= self.preferred_dedicated_threshold =>
228 {
229 Strategy::Dedicated
230 }
231 (_, true) => {
232 let threshold = self.transient_dedicated_threshold.min(heap.size() / 32);
233
234 if request.size < threshold {
235 Strategy::FreeList
236 } else {
237 Strategy::Dedicated
238 }
239 }
240 (_, false) => {
241 let threshold = self.dedicated_threshold.min(heap.size() / 32);
242
243 if request.size < threshold {
244 Strategy::Buddy
245 } else {
246 Strategy::Dedicated
247 }
248 }
249 };
250
251 match strategy {
252 Strategy::Dedicated => {
253 #[cfg(feature = "tracing")]
254 tracing::debug!(
255 "Allocating memory object `{}@{:?}`",
256 request.size,
257 memory_type
258 );
259
260 match device.allocate_memory(request.size, index, flags) {
261 Ok(memory) => {
262 self.allocations_remains -= 1;
263 heap.alloc(request.size);
264
265 return Ok(MemoryBlock::new(
266 index,
267 memory_type.props,
268 0,
269 request.size,
270 atom_mask,
271 MemoryBlockFlavor::Dedicated { memory },
272 ));
273 }
274 Err(OutOfMemory::OutOfDeviceMemory) => continue,
275 Err(OutOfMemory::OutOfHostMemory) => {
276 return Err(AllocationError::OutOfHostMemory)
277 }
278 }
279 }
280 Strategy::FreeList => {
281 let allocator = match &mut self.freelist_allocators[index as usize] {
282 Some(allocator) => allocator,
283 slot => {
284 let starting_free_list_chunk = match align_down(
285 self.starting_free_list_chunk.min(heap.size() / 32),
286 atom_mask,
287 ) {
288 0 => atom_mask,
289 other => other,
290 };
291
292 let final_free_list_chunk = match align_down(
293 self.final_free_list_chunk
294 .max(self.starting_free_list_chunk)
295 .max(self.transient_dedicated_threshold)
296 .min(heap.size() / 32),
297 atom_mask,
298 ) {
299 0 => atom_mask,
300 other => other,
301 };
302
303 slot.get_or_insert(FreeListAllocator::new(
304 starting_free_list_chunk,
305 final_free_list_chunk,
306 index,
307 memory_type.props,
308 if host_visible_non_coherent(memory_type.props) {
309 self.non_coherent_atom_mask
310 } else {
311 0
312 },
313 ))
314 }
315 };
316 let result = allocator.alloc(
317 device,
318 request.size,
319 request.align_mask,
320 flags,
321 heap,
322 &mut self.allocations_remains,
323 );
324
325 match result {
326 Ok(block) => {
327 return Ok(MemoryBlock::new(
328 index,
329 memory_type.props,
330 block.offset,
331 block.size,
332 atom_mask,
333 MemoryBlockFlavor::FreeList {
334 chunk: block.chunk,
335 ptr: block.ptr,
336 memory: block.memory,
337 },
338 ))
339 }
340 Err(AllocationError::OutOfDeviceMemory) => continue,
341 Err(err) => return Err(err),
342 }
343 }
344
345 Strategy::Buddy => {
346 let allocator = match &mut self.buddy_allocators[index as usize] {
347 Some(allocator) => allocator,
348 slot => {
349 let minimal_buddy_size = self
350 .minimal_buddy_size
351 .min(heap.size() / 1024)
352 .next_power_of_two();
353
354 let initial_buddy_dedicated_size = self
355 .initial_buddy_dedicated_size
356 .min(heap.size() / 32)
357 .next_power_of_two();
358
359 slot.get_or_insert(BuddyAllocator::new(
360 minimal_buddy_size,
361 initial_buddy_dedicated_size,
362 index,
363 memory_type.props,
364 if host_visible_non_coherent(memory_type.props) {
365 self.non_coherent_atom_mask
366 } else {
367 0
368 },
369 ))
370 }
371 };
372 let result = allocator.alloc(
373 device,
374 request.size,
375 request.align_mask,
376 flags,
377 heap,
378 &mut self.allocations_remains,
379 );
380
381 match result {
382 Ok(block) => {
383 return Ok(MemoryBlock::new(
384 index,
385 memory_type.props,
386 block.offset,
387 block.size,
388 atom_mask,
389 MemoryBlockFlavor::Buddy {
390 chunk: block.chunk,
391 ptr: block.ptr,
392 index: block.index,
393 memory: block.memory,
394 },
395 ))
396 }
397 Err(AllocationError::OutOfDeviceMemory) => continue,
398 Err(err) => return Err(err),
399 }
400 }
401 }
402 }
403
404 Err(AllocationError::OutOfDeviceMemory)
405 }
406
407 pub unsafe fn import_memory(
432 &mut self,
433 memory: M,
434 memory_type: u32,
435 props: MemoryPropertyFlags,
436 offset: u64,
437 size: u64,
438 ) -> MemoryBlock<M> {
439 let heap = self
441 .memory_types
442 .get(memory_type as usize)
443 .expect("Invalid memory type specified when importing memory")
444 .heap;
445 let heap = &mut self.memory_heaps[heap as usize];
446
447 #[cfg(feature = "tracing")]
448 tracing::debug!(
449 "Importing memory object {:?} `{}@{:?}`",
450 memory,
451 size,
452 memory_type
453 );
454
455 assert_ne!(
456 self.allocations_remains, 0,
457 "Out of allocations when importing a memory block. Ensure you check GpuAllocator::remaining_allocations before import."
458 );
459 self.allocations_remains -= 1;
460
461 let atom_mask = if host_visible_non_coherent(props) {
462 self.non_coherent_atom_mask
463 } else {
464 0
465 };
466
467 heap.alloc(size);
468
469 MemoryBlock::new(
470 memory_type,
471 props,
472 offset,
473 size,
474 atom_mask,
475 MemoryBlockFlavor::Dedicated { memory },
476 )
477 }
478
479 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
488 pub unsafe fn dealloc(&mut self, device: &impl MemoryDevice<M>, block: MemoryBlock<M>)
489 {
490 let memory_type = block.memory_type();
491 let offset = block.offset();
492 let size = block.size();
493 let flavor = block.deallocate();
494 match flavor {
495 MemoryBlockFlavor::Dedicated { memory } => {
496 let heap = self.memory_types[memory_type as usize].heap;
497 device.deallocate_memory(memory);
498 self.allocations_remains += 1;
499 self.memory_heaps[heap as usize].dealloc(size);
500 }
501 MemoryBlockFlavor::Buddy {
502 chunk,
503 ptr,
504 index,
505 memory,
506 } => {
507 let heap = self.memory_types[memory_type as usize].heap;
508 let heap = &mut self.memory_heaps[heap as usize];
509
510 let allocator = self.buddy_allocators[memory_type as usize]
511 .as_mut()
512 .expect("Allocator should exist");
513
514 allocator.dealloc(
515 device,
516 BuddyBlock {
517 memory,
518 ptr,
519 offset,
520 size,
521 chunk,
522 index,
523 },
524 heap,
525 &mut self.allocations_remains,
526 );
527 }
528 MemoryBlockFlavor::FreeList { chunk, ptr, memory } => {
529 let heap = self.memory_types[memory_type as usize].heap;
530 let heap = &mut self.memory_heaps[heap as usize];
531
532 let allocator = self.freelist_allocators[memory_type as usize]
533 .as_mut()
534 .expect("Allocator should exist");
535
536 allocator.dealloc(
537 device,
538 FreeListBlock {
539 memory,
540 ptr,
541 chunk,
542 offset,
543 size,
544 },
545 heap,
546 &mut self.allocations_remains,
547 );
548 }
549 }
550 }
551
552 pub fn max_allocation_size(&self) -> u64 {
554 self.max_memory_allocation_size
555 }
556
557 pub fn remaining_allocations(&self) -> u32 {
563 self.allocations_remains
564 }
565
566 pub unsafe fn set_remaining_allocations(&mut self, remaining: u32) {
573 self.allocations_remains = remaining;
574 }
575
576 #[cfg_attr(feature = "tracing", tracing::instrument(skip(self, device)))]
585 pub unsafe fn cleanup(&mut self, device: &impl MemoryDevice<M>)
586 {
587 for (index, allocator) in self
588 .freelist_allocators
589 .iter_mut()
590 .enumerate()
591 .filter_map(|(index, allocator)| Some((index, allocator.as_mut()?)))
592 {
593 let memory_type = &self.memory_types[index];
594 let heap = memory_type.heap;
595 let heap = &mut self.memory_heaps[heap as usize];
596
597 allocator.cleanup(device, heap, &mut self.allocations_remains);
598 }
599 }
600}
601
602fn host_visible_non_coherent(props: MemoryPropertyFlags) -> bool {
603 (props & (MemoryPropertyFlags::HOST_COHERENT | MemoryPropertyFlags::HOST_VISIBLE))
604 == MemoryPropertyFlags::HOST_VISIBLE
605}
606
607fn with_implicit_usage_flags(usage: UsageFlags) -> UsageFlags {
608 if usage.is_empty() {
609 UsageFlags::FAST_DEVICE_ACCESS
610 } else if usage.intersects(UsageFlags::DOWNLOAD | UsageFlags::UPLOAD) {
611 usage | UsageFlags::HOST_ACCESS
612 } else {
613 usage
614 }
615}