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