#[cfg(feature = "std")] use alloc::sync::Arc; use alloc::{boxed::Box, string::String, vec::Vec}; use core::{
fmt, // TODO: Remove when bumping MSRV to 1.80
mem::size_of_val,
}; #[cfg(feature = "std")] use std::backtrace::Backtrace;
use log::{debug, warn, Level}; use windows::Win32::{
Foundation::E_OUTOFMEMORY,
Graphics::{
Direct3D12::*,
Dxgi::{Common::DXGI_FORMAT, DXGI_ERROR_DEVICE_REMOVED},
},
};
#[cfg(feature = "visualizer")] mod visualizer; #[cfg(feature = "visualizer")] pubuse visualizer::AllocatorVisualizer;
/// [`ResourceCategory`] is used for supporting [`D3D12_RESOURCE_HEAP_TIER_1`]. /// [`ResourceCategory`] will be ignored if device supports [`D3D12_RESOURCE_HEAP_TIER_2`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pubenum ResourceCategory {
Buffer,
RtvDsvTexture,
OtherTexture,
}
#[derive(Clone, Debug)] pubstruct AllocationCreateDesc<'a> { /// Name of the allocation, for tracking and debugging purposes pub name: &'a str, /// Location where the memory allocation should be stored pub location: MemoryLocation,
/// Size of allocation, should be queried using [`ID3D12Device::GetResourceAllocationInfo()`] pub size: u64, /// Alignment of allocation, should be queried using [`ID3D12Device::GetResourceAllocationInfo()`] pub alignment: u64, /// Resource category based on resource dimension and flags. Can be created from a [`D3D12_RESOURCE_DESC`] /// using the [helper `into()` function]. The resource category is ignored when Resource Heap Tier 2 or higher /// is supported. /// /// [helper `into()` function]: ResourceCategory::from() pub resource_category: ResourceCategory,
}
impl<'a> AllocationCreateDesc<'a> { /// Helper function to construct an [`AllocationCreateDesc`] from an existing /// [`D3D12_RESOURCE_DESC`] utilizing [`ID3D12Device::GetResourceAllocationInfo()`]. pubfn from_d3d12_resource_desc(
device: &ID3D12Device,
desc: &D3D12_RESOURCE_DESC,
name: &'a str,
location: MemoryLocation,
) -> Self { // SAFETY: `device` is a valid device handle, and no arguments (like pointers) are passed // that could induce UB. let allocation_info = unsafe { device.GetResourceAllocationInfo(0, core::slice::from_ref(desc)) }; let resource_category: ResourceCategory = desc.into();
#[derive(Clone, Debug)] pubenum ID3D12DeviceVersion { /// Basic device compatible with legacy barriers only, i.e. can only be used in conjunction /// with [`ResourceStateOrBarrierLayout::ResourceState`].
Device(ID3D12Device), /// Required for enhanced barrier support, i.e. when using /// [`ResourceStateOrBarrierLayout::BarrierLayout`].
Device10(ID3D12Device10), /// Required for castable formats support, implies use of enhanced barriers
Device12(ID3D12Device12),
}
impl core::ops::Deref for ID3D12DeviceVersion { type Target = ID3D12Device;
impl Drop for Resource { fn drop(&mutself) { ifself.resource.is_some() {
warn!("Dropping resource `{}` that was not freed. Call `Allocator::free_resource(resource)` instead.", self.name);
}
}
}
/// Returns the [`ID3D12Heap`] object that is backing this allocation. /// /// This heap object can be shared with multiple other allocations and shouldn't be allocated from /// without this library, because that will lead to undefined behavior. /// /// # Safety /// The result of this function can safely be passed into [`ID3D12Device::CreatePlacedResource()`]. /// It is exposed for this reason. Keep in mind to also pass [`Self::offset()`] along to it. /// /// Also, this [`Allocation`] must not be [`Allocator::free()`]d while such a created resource /// on this [`ID3D12Heap`] is still live. pubunsafefn heap(&self) -> &ID3D12Heap {
&self.heap
}
/// Returns the offset of the allocation on the [`ID3D12Heap`]. /// When creating a placed resource, this offset needs to be supplied as well. pubfn offset(&self) -> u64 { self.offset
}
/// Returns the size of the allocation pubfn size(&self) -> u64 { self.size
}
let is_host = self.heap_properties.Type != D3D12_HEAP_TYPE_DEFAULT; let memblock_size = allocation_sizes.get_memblock_size(is_host, self.active_general_blocks);
let size = desc.size; let alignment = desc.alignment;
// Create a dedicated block for large memory allocations if size > memblock_size { let mem_block = MemoryBlock::new(
device,
size,
&self.heap_properties, self.heap_category, true,
)?;
let block_index = self.memory_blocks.iter().position(|block| block.is_none()); let block_index = match block_index {
Some(i) => { self.memory_blocks[i].replace(mem_block);
i
}
None => { self.memory_blocks.push(Some(mem_block)); self.memory_blocks.len() - 1
}
};
let mem_block = self.memory_blocks[block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?;
let mem_block = self.memory_blocks[new_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?; let allocation = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type, 1,
desc.name, #[cfg(feature = "std")]
backtrace,
); let (offset, chunk_id) = match allocation {
Err(AllocationError::OutOfMemory) => Err(AllocationError::Internal( "Allocation that must succeed failed. This is a bug in the allocator.".into(),
)),
a => a,
}?;
// We only want to destroy this now-empty block if it is either a dedicated/personal // allocation, or a block supporting sub-allocations that is not the last one (ensuring // there's always at least one block/allocator readily available). let is_dedicated_or_not_last_general_block =
!mem_block.sub_allocator.supports_general_allocations()
|| self.active_general_blocks > 1; if mem_block.sub_allocator.is_empty() && is_dedicated_or_not_last_general_block { let block = self.memory_blocks[block_idx]
.take()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
if block.sub_allocator.supports_general_allocations() { self.active_general_blocks -= 1;
}
// Note that `block` will be destroyed on `drop` here
}
let mem_type = &mutself.memory_types[allocation.memory_type_index]; let mem_block = mem_type.memory_blocks[allocation.memory_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
// We always have one resource desc, hence we only have one mapping castable format array let num_castable_formats = desc.castable_formats.len() as u32; let num_castable_formats_array = &[num_castable_formats];
let castable_formats_array = &[desc.castable_formats.as_ptr()];
let (num_castable_formats_opt, castable_formats_opt) = if num_castable_formats > 0 {
(
Some(num_castable_formats_array.as_ptr()),
Some(castable_formats_array.as_ptr()),
)
} else {
(None, None)
};
/// Create a resource according to the provided parameters. /// Created resources should be freed at the end of their lifetime by calling [`Self::free_resource()`]. pubfn create_resource(&mutself, desc: &ResourceCreateDesc<'_>) -> Result<Resource> { match desc.resource_type {
ResourceType::Committed {
heap_properties,
heap_flags,
} => { letmut result: Option<ID3D12Resource> = None;
let clear_value: Option<*const D3D12_CLEAR_VALUE> =
desc.clear_value.map(|v| -> *const _ { v });
let resource = result.expect("Allocation succeeded but no resource was returned?"); let size = allocation.size();
Ok(Resource {
name: desc.name.into(),
allocation: Some(allocation),
resource: Some(resource),
size,
memory_location: desc.memory_location,
memory_type_index: None,
})
}
}
}
/// Free a resource and its memory. pubfn free_resource(&mutself, mut resource: Resource) -> Result<()> { // Explicitly drop the resource (which is backed by a refcounted COM object) // before freeing allocated memory. Windows-rs performs a Release() on drop(). let _ = resource
.resource
.take()
.expect("Resource was already freed.");
iflet Some(allocation) = resource.allocation.take() { self.free(allocation)
} else { // Dx12 CommittedResources do not have an application managed allocation. // We only have to update the tracked allocation count and memory usage. iflet Some(memory_type_index) = resource.memory_type_index { let memory_type = &mutself.memory_types[memory_type_index];
impl Drop for Allocator { fn drop(&mutself) { ifself.debug_settings.log_leaks_on_shutdown { self.report_memory_leaks(Level::Warn);
}
// Because Rust drop rules drop members in source-code order (that would be the // ID3D12Device before the ID3D12Heaps nested in these memory blocks), free // all remaining memory blocks manually first by dropping. for mem_type inself.memory_types.iter_mut() {
mem_type.memory_blocks.clear();
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.