Skip to content

Commit 573fb24

Browse files
authored
Merge branch 'main' into no-std-support
2 parents e0a278c + 1ad50b6 commit 573fb24

8 files changed

Lines changed: 117 additions & 64 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ objc2-metal = { version = "0.3", default-features = false, features = [
4343
"MTLBuffer",
4444
"MTLDevice",
4545
"MTLHeap",
46+
"MTLResidencySet",
4647
"MTLResource",
4748
"MTLTexture",
4849
"std",

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ let mut allocator = Allocator::new(&AllocatorCreateDesc {
131131
device: device.clone(),
132132
debug_settings: Default::default(),
133133
allocation_sizes: Default::default(),
134+
create_residency_set: false,
134135
});
135136
```
136137

examples/metal-buffer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ fn main() {
2121
device: device.clone(),
2222
debug_settings: Default::default(),
2323
allocation_sizes: Default::default(),
24+
create_residency_set: false,
2425
})
2526
.unwrap();
2627

src/allocator/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ pub(crate) trait SubAllocator: SubAllocatorBase + fmt::Debug + Sync + Send {
138138

139139
fn report_allocations(&self) -> Vec<AllocationReport>;
140140

141+
/// Returns [`true`] if this allocator allows sub-allocating multiple allocations, [`false`] if
142+
/// it is designed to only represent dedicated allocations.
141143
#[must_use]
142144
fn supports_general_allocations(&self) -> bool;
143145
#[must_use]

src/d3d12/mod.rs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -481,28 +481,22 @@ impl MemoryType {
481481

482482
mem_block.sub_allocator.free(allocation.chunk_id)?;
483483

484-
if mem_block.sub_allocator.is_empty() {
485-
if mem_block.sub_allocator.supports_general_allocations() {
486-
if self.active_general_blocks > 1 {
487-
let block = self.memory_blocks[block_idx].take();
488-
if block.is_none() {
489-
return Err(AllocationError::Internal(
490-
"Memory block must be Some.".into(),
491-
));
492-
}
493-
// Note that `block` will be destroyed on `drop` here
494-
495-
self.active_general_blocks -= 1;
496-
}
497-
} else {
498-
let block = self.memory_blocks[block_idx].take();
499-
if block.is_none() {
500-
return Err(AllocationError::Internal(
501-
"Memory block must be Some.".into(),
502-
));
503-
}
504-
// Note that `block` will be destroyed on `drop` here
484+
// We only want to destroy this now-empty block if it is either a dedicated/personal
485+
// allocation, or a block supporting sub-allocations that is not the last one (ensuring
486+
// there's always at least one block/allocator readily available).
487+
let is_dedicated_or_not_last_general_block =
488+
!mem_block.sub_allocator.supports_general_allocations()
489+
|| self.active_general_blocks > 1;
490+
if mem_block.sub_allocator.is_empty() && is_dedicated_or_not_last_general_block {
491+
let block = self.memory_blocks[block_idx]
492+
.take()
493+
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
494+
495+
if block.sub_allocator.supports_general_allocations() {
496+
self.active_general_blocks -= 1;
505497
}
498+
499+
// Note that `block` will be destroyed on `drop` here
506500
}
507501

508502
Ok(())

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
//! device: device.clone(),
161161
//! debug_settings: Default::default(),
162162
//! allocation_sizes: Default::default(),
163+
//! create_residency_set: false,
163164
//! });
164165
//! # }
165166
//! # #[cfg(not(feature = "metal"))]
@@ -178,6 +179,7 @@
178179
//! # device: device.clone(),
179180
//! # debug_settings: Default::default(),
180181
//! # allocation_sizes: Default::default(),
182+
//! # create_residency_set: false,
181183
//! # })
182184
//! # .unwrap();
183185
//! let allocation_desc = AllocationCreateDesc::buffer(

src/metal/mod.rs

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ use std::backtrace::Backtrace;
66

77
use log::debug;
88
use objc2::{rc::Retained, runtime::ProtocolObject};
9-
use objc2_foundation::NSString;
9+
use objc2_foundation::{ns_string, NSString};
10+
#[cfg(doc)]
11+
use objc2_metal::{MTLAllocation, MTLResource};
1012
use objc2_metal::{
11-
MTLCPUCacheMode, MTLDevice, MTLHeap, MTLHeapDescriptor, MTLHeapType, MTLResourceOptions,
12-
MTLStorageMode, MTLTextureDescriptor,
13+
MTLCPUCacheMode, MTLDevice, MTLHeap, MTLHeapDescriptor, MTLHeapType, MTLResidencySet,
14+
MTLResourceOptions, MTLStorageMode, MTLTextureDescriptor,
1315
};
1416

1517
#[cfg(feature = "visualizer")]
@@ -154,6 +156,7 @@ impl<'a> AllocationCreateDesc<'a> {
154156

155157
pub struct Allocator {
156158
device: Retained<ProtocolObject<dyn MTLDevice>>,
159+
global_residency_set: Option<Retained<ProtocolObject<dyn MTLResidencySet>>>,
157160
debug_settings: AllocatorDebugSettings,
158161
memory_types: Vec<MemoryType>,
159162
allocation_sizes: AllocationSizes,
@@ -170,6 +173,9 @@ pub struct AllocatorCreateDesc {
170173
pub device: Retained<ProtocolObject<dyn MTLDevice>>,
171174
pub debug_settings: AllocatorDebugSettings,
172175
pub allocation_sizes: AllocationSizes,
176+
/// Whether to create a [`MTLResidencySet`] containing all live heaps, that can be retrieved via
177+
/// [`Allocator::residency_set()`]. Only supported on `MacOS 15.0+` / `iOS 18.0+`.
178+
pub create_residency_set: bool,
173179
}
174180

175181
#[derive(Debug)]
@@ -219,6 +225,7 @@ impl MemoryBlock {
219225

220226
#[derive(Debug)]
221227
struct MemoryType {
228+
global_residency_set: Option<Retained<ProtocolObject<dyn MTLResidencySet>>>,
222229
memory_blocks: Vec<Option<MemoryBlock>>,
223230
_committed_allocations: CommittedAllocationStatistics,
224231
memory_location: MemoryLocation,
@@ -253,6 +260,10 @@ impl MemoryType {
253260
self.memory_location,
254261
)?;
255262

263+
if let Some(rs) = &self.global_residency_set {
264+
unsafe { rs.addAllocation(mem_block.heap.as_ref()) }
265+
}
266+
256267
let block_index = self.memory_blocks.iter().position(|block| block.is_none());
257268
let block_index = match block_index {
258269
Some(i) => {
@@ -323,19 +334,23 @@ impl MemoryType {
323334
}
324335
}
325336

326-
let new_memory_block = MemoryBlock::new(
337+
let mem_block = MemoryBlock::new(
327338
device,
328339
memblock_size,
329340
&self.heap_properties,
330341
false,
331342
self.memory_location,
332343
)?;
333344

345+
if let Some(rs) = &self.global_residency_set {
346+
unsafe { rs.addAllocation(mem_block.heap.as_ref()) }
347+
}
348+
334349
let new_block_index = if let Some(block_index) = empty_block_index {
335-
self.memory_blocks[block_index] = Some(new_memory_block);
350+
self.memory_blocks[block_index] = Some(mem_block);
336351
block_index
337352
} else {
338-
self.memory_blocks.push(Some(new_memory_block));
353+
self.memory_blocks.push(Some(mem_block));
339354
self.memory_blocks.len() - 1
340355
};
341356

@@ -380,28 +395,26 @@ impl MemoryType {
380395

381396
mem_block.sub_allocator.free(allocation.chunk_id)?;
382397

383-
if mem_block.sub_allocator.is_empty() {
384-
if mem_block.sub_allocator.supports_general_allocations() {
385-
if self.active_general_blocks > 1 {
386-
let block = self.memory_blocks[block_idx].take();
387-
if block.is_none() {
388-
return Err(AllocationError::Internal(
389-
"Memory block must be Some.".into(),
390-
));
391-
}
392-
// Note that `block` will be destroyed on `drop` here
398+
// We only want to destroy this now-empty block if it is either a dedicated/personal
399+
// allocation, or a block supporting sub-allocations that is not the last one (ensuring
400+
// there's always at least one block/allocator readily available).
401+
let is_dedicated_or_not_last_general_block =
402+
!mem_block.sub_allocator.supports_general_allocations()
403+
|| self.active_general_blocks > 1;
404+
if mem_block.sub_allocator.is_empty() && is_dedicated_or_not_last_general_block {
405+
let block = self.memory_blocks[block_idx]
406+
.take()
407+
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
408+
409+
if block.sub_allocator.supports_general_allocations() {
410+
self.active_general_blocks -= 1;
411+
}
393412

394-
self.active_general_blocks -= 1;
395-
}
396-
} else {
397-
let block = self.memory_blocks[block_idx].take();
398-
if block.is_none() {
399-
return Err(AllocationError::Internal(
400-
"Memory block must be Some.".into(),
401-
));
402-
}
403-
// Note that `block` will be destroyed on `drop` here
413+
if let Some(rs) = &self.global_residency_set {
414+
unsafe { rs.removeAllocation(block.heap.as_ref()) }
404415
}
416+
417+
// Note that `block` will be destroyed on `drop` here
405418
}
406419

407420
Ok(())
@@ -434,10 +447,23 @@ impl Allocator {
434447
}),
435448
];
436449

450+
let global_residency_set = if desc.create_residency_set {
451+
Some(unsafe {
452+
let rs_desc = objc2_metal::MTLResidencySetDescriptor::new();
453+
rs_desc.setLabel(Some(ns_string!("gpu-allocator global residency set")));
454+
desc.device
455+
.newResidencySetWithDescriptor_error(&rs_desc)
456+
.expect("Failed to create MTLResidencySet. Unsupported MacOS/iOS version?")
457+
})
458+
} else {
459+
None
460+
};
461+
437462
let memory_types = heap_types
438463
.into_iter()
439464
.enumerate()
440465
.map(|(i, (memory_location, heap_descriptor))| MemoryType {
466+
global_residency_set: global_residency_set.clone(),
441467
memory_blocks: vec![],
442468
_committed_allocations: CommittedAllocationStatistics {
443469
num_allocations: 0,
@@ -455,6 +481,7 @@ impl Allocator {
455481
debug_settings: desc.debug_settings,
456482
memory_types,
457483
allocation_sizes: desc.allocation_sizes,
484+
global_residency_set,
458485
})
459486
}
460487

@@ -573,4 +600,31 @@ impl Allocator {
573600

574601
total_capacity_bytes
575602
}
603+
604+
/// Optional residency set containing all heap allocations created/owned by this allocator to
605+
/// be made resident at once when its allocations are used on the GPU. The caller _must_ invoke
606+
/// [`MTLResidencySet::commit()`] whenever these resources are used to make sure the latest
607+
/// changes are visible to Metal, e.g. before committing a command buffer.
608+
///
609+
/// This residency set can be attached to individual command buffers or to a queue directly
610+
/// since usage of allocated resources is expected to be global.
611+
///
612+
/// Alternatively callers can build up their own residency set(s) based on individual
613+
/// [`MTLAllocation`]s [^heap-allocation] rather than making all heaps allocated via
614+
/// `gpu-allocator` resident at once.
615+
///
616+
/// [^heap-allocation]: Note that [`MTLHeap`]s returned by [`Allocator::heaps()`] are also
617+
/// allocations. If individual placed [`MTLResource`]s on a heap are made resident, the entire
618+
/// heap will be made resident.
619+
///
620+
/// Callers still need to be careful to make resources created outside of `gpu-allocator`
621+
/// resident on the GPU, such as indirect command buffers.
622+
///
623+
/// This residency set is only available when requested via
624+
/// [`AllocatorCreateDesc::create_residency_set`], otherwise this function returns [`None`].
625+
pub fn residency_set(&self) -> Option<&Retained<ProtocolObject<dyn MTLResidencySet>>> {
626+
// Return the retained object so that the caller also has a way to store it, since we will
627+
// keep using and updating the same object going forward.
628+
self.global_residency_set.as_ref()
629+
}
576630
}

src/vulkan/mod.rs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -665,24 +665,22 @@ impl MemoryType {
665665

666666
mem_block.sub_allocator.free(allocation.chunk_id)?;
667667

668-
if mem_block.sub_allocator.is_empty() {
669-
if mem_block.sub_allocator.supports_general_allocations() {
670-
if self.active_general_blocks > 1 {
671-
let block = self.memory_blocks[block_idx].take();
672-
let block = block.ok_or_else(|| {
673-
AllocationError::Internal("Memory block must be Some.".into())
674-
})?;
675-
block.destroy(device);
676-
677-
self.active_general_blocks -= 1;
678-
}
679-
} else {
680-
let block = self.memory_blocks[block_idx].take();
681-
let block = block.ok_or_else(|| {
682-
AllocationError::Internal("Memory block must be Some.".into())
683-
})?;
684-
block.destroy(device);
668+
// We only want to destroy this now-empty block if it is either a dedicated/personal
669+
// allocation, or a block supporting sub-allocations that is not the last one (ensuring
670+
// there's always at least one block/allocator readily available).
671+
let is_dedicated_or_not_last_general_block =
672+
!mem_block.sub_allocator.supports_general_allocations()
673+
|| self.active_general_blocks > 1;
674+
if mem_block.sub_allocator.is_empty() && is_dedicated_or_not_last_general_block {
675+
let block = self.memory_blocks[block_idx]
676+
.take()
677+
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
678+
679+
if block.sub_allocator.supports_general_allocations() {
680+
self.active_general_blocks -= 1;
685681
}
682+
683+
block.destroy(device);
686684
}
687685

688686
Ok(())

0 commit comments

Comments
 (0)