use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use crate::suballoc::block_alloc::{BlockAllocator, Placement};
const HEAP_GRANULARITY: u64 = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT as u64;
const MAX_BLOCK_BYTES: u64 = 64 * 1024 * 1024;
const FIRST_BLOCK_BYTES: u64 = 4 * 1024 * 1024;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum HeapKind {
Default,
Upload,
Readback,
}
impl HeapKind {
fn from_d3d12(heap_type: D3D12_HEAP_TYPE) -> Result<Self, String> {
if heap_type == D3D12_HEAP_TYPE_DEFAULT {
Ok(Self::Default)
} else if heap_type == D3D12_HEAP_TYPE_UPLOAD {
Ok(Self::Upload)
} else if heap_type == D3D12_HEAP_TYPE_READBACK {
Ok(Self::Readback)
} else {
Err(format!(
"allocator: heap type {} cannot back a pool",
heap_type.0
))
}
}
fn to_d3d12(self) -> D3D12_HEAP_TYPE {
match self {
Self::Default => D3D12_HEAP_TYPE_DEFAULT,
Self::Upload => D3D12_HEAP_TYPE_UPLOAD,
Self::Readback => D3D12_HEAP_TYPE_READBACK,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum HeapClass {
All,
Buffers,
NonRtDsTextures,
RtDsTextures,
}
impl HeapClass {
fn for_desc(desc: &D3D12_RESOURCE_DESC, tier: D3D12_RESOURCE_HEAP_TIER) -> Self {
if tier.0 >= D3D12_RESOURCE_HEAP_TIER_2.0 {
return Self::All;
}
if desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER {
return Self::Buffers;
}
let rt_ds =
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET.0 | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL.0;
if desc.Flags.0 & rt_ds != 0 {
Self::RtDsTextures
} else {
Self::NonRtDsTextures
}
}
fn heap_flags(self) -> D3D12_HEAP_FLAGS {
match self {
Self::All => D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES,
Self::Buffers => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
Self::NonRtDsTextures => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
Self::RtDsTextures => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct PoolKey {
kind: HeapKind,
class: HeapClass,
}
struct Pool {
placement: BlockAllocator,
heaps: Vec<Option<ID3D12Heap>>,
}
impl Pool {
fn new() -> Self {
Self {
placement: BlockAllocator::new(MAX_BLOCK_BYTES),
heaps: Vec::new(),
}
}
fn next_block_bytes(&self, size: u64, align: u64) -> u64 {
let grown = FIRST_BLOCK_BYTES
.saturating_mul(1 << self.placement.block_count().min(4))
.min(MAX_BLOCK_BYTES);
let needed = size.saturating_add(align.max(1).saturating_sub(1));
round_up(needed.max(grown), HEAP_GRANULARITY)
}
}
fn round_up(value: u64, granularity: u64) -> u64 {
value.div_ceil(granularity).saturating_mul(granularity)
}
struct ParkedList {
#[expect(
dead_code,
reason = "held until the GPU retires the list; dropping the entry releases the handle"
)]
allocator: ID3D12CommandAllocator,
#[expect(
dead_code,
reason = "held until the GPU retires the list; dropping the entry releases the handle"
)]
cmd: ID3D12GraphicsCommandList,
retire_at: u64,
}
struct Inner {
pools: HashMap<PoolKey, Pool>,
parked: Vec<ParkedList>,
frame: u64,
retire_depth: u64,
}
impl Inner {
fn free(&mut self, key: PoolKey, placement: Placement, size: u64) {
let retire = self.frame + self.retire_depth;
if let Some(pool) = self.pools.get_mut(&key) {
pool.placement.free(placement, size, retire);
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct AllocatorStats {
pub reserved_bytes: u64,
pub in_use_bytes: u64,
pub block_count: usize,
}
impl std::fmt::Display for AllocatorStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} heap(s), {} KiB reserved for {} KiB of resources",
self.block_count,
self.reserved_bytes / 1024,
self.in_use_bytes / 1024,
)
}
}
struct Reservation {
heap: ID3D12Heap,
key: PoolKey,
placement: Placement,
size: u64,
recycled: bool,
}
struct Lease {
owner: Weak<RefCell<Inner>>,
key: PoolKey,
placement: Placement,
size: u64,
}
impl Drop for Lease {
fn drop(&mut self) {
if let Some(inner) = self.owner.upgrade() {
inner.borrow_mut().free(self.key, self.placement, self.size);
}
}
}
#[derive(Clone)]
pub(super) struct PooledBuffer {
resource: ID3D12Resource,
#[expect(
dead_code,
reason = "a placed resource does not keep its heap alive, so the heap is held to outlive it"
)]
heap: ID3D12Heap,
_lease: Rc<Lease>,
}
impl Deref for PooledBuffer {
type Target = ID3D12Resource;
fn deref(&self) -> &Self::Target {
&self.resource
}
}
impl AsRef<ID3D12Resource> for PooledBuffer {
fn as_ref(&self) -> &ID3D12Resource {
&self.resource
}
}
#[derive(Clone)]
pub(super) struct PooledTexture {
resource: ID3D12Resource,
#[expect(
dead_code,
reason = "a placed resource does not keep its heap alive, so the heap is held to outlive it"
)]
heap: ID3D12Heap,
_lease: Rc<Lease>,
}
impl Deref for PooledTexture {
type Target = ID3D12Resource;
fn deref(&self) -> &Self::Target {
&self.resource
}
}
impl AsRef<ID3D12Resource> for PooledTexture {
fn as_ref(&self) -> &ID3D12Resource {
&self.resource
}
}
pub(super) struct DeviceAllocator {
device: ID3D12Device,
queue: ID3D12CommandQueue,
heap_tier: D3D12_RESOURCE_HEAP_TIER,
inner: Rc<RefCell<Inner>>,
}
impl DeviceAllocator {
pub(super) fn new(
device: &ID3D12Device,
queue: &ID3D12CommandQueue,
frames_in_flight: usize,
) -> Self {
Self {
device: device.clone(),
queue: queue.clone(),
heap_tier: resource_heap_tier(device),
inner: Rc::new(RefCell::new(Inner {
pools: HashMap::new(),
parked: Vec::new(),
frame: 0,
retire_depth: frames_in_flight as u64 + 1,
})),
}
}
pub(super) fn device(&self) -> &ID3D12Device {
&self.device
}
pub(super) fn queue(&self) -> &ID3D12CommandQueue {
&self.queue
}
pub(super) fn alloc_buffer(
&self,
size: u64,
heap_type: D3D12_HEAP_TYPE,
initial_state: D3D12_RESOURCE_STATES,
) -> Result<PooledBuffer, String> {
let desc = buffer_desc(size.max(1));
let (resource, heap, lease) = self.place(&desc, heap_type, initial_state)?;
Ok(PooledBuffer {
resource,
heap,
_lease: lease,
})
}
pub(super) fn alloc_texture(
&self,
desc: &D3D12_RESOURCE_DESC,
heap_type: D3D12_HEAP_TYPE,
initial_state: D3D12_RESOURCE_STATES,
) -> Result<PooledTexture, String> {
if is_gpu_written(desc) {
return Err(format!(
"allocator: resource flags {:#x} are GPU-written and stay committed",
desc.Flags.0
));
}
let (resource, heap, lease) = self.place(desc, heap_type, initial_state)?;
Ok(PooledTexture {
resource,
heap,
_lease: lease,
})
}
pub(super) fn begin_frame(&self) {
let mut inner = self.inner.borrow_mut();
inner.frame += 1;
let frame = inner.frame;
for pool in inner.pools.values_mut() {
pool.placement.reclaim(frame);
for index in pool.placement.take_empty_blocks() {
if let Some(slot) = pool.heaps.get_mut(index) {
*slot = None;
}
}
}
inner.parked.retain(|p| p.retire_at > frame);
}
pub(super) fn stats(&self) -> AllocatorStats {
let inner = self.inner.borrow();
let mut stats = AllocatorStats::default();
for pool in inner.pools.values() {
stats.reserved_bytes += pool.placement.reserved_bytes();
stats.in_use_bytes += pool.placement.in_use_bytes();
stats.block_count += pool.placement.block_count();
}
stats
}
fn place(
&self,
desc: &D3D12_RESOURCE_DESC,
heap_type: D3D12_HEAP_TYPE,
initial_state: D3D12_RESOURCE_STATES,
) -> Result<(ID3D12Resource, ID3D12Heap, Rc<Lease>), String> {
let key = PoolKey {
kind: HeapKind::from_d3d12(heap_type)?,
class: HeapClass::for_desc(desc, self.heap_tier),
};
let (desc, info) = self.allocation_info(desc)?;
let reservation = self.reserve(key, info.SizeInBytes, info.Alignment)?;
let mut placed: Option<ID3D12Resource> = None;
let result = unsafe {
self.device.CreatePlacedResource(
&reservation.heap,
reservation.placement.offset,
&desc,
initial_state,
None,
&mut placed,
)
};
let resource = match result.map(|()| placed) {
Ok(Some(resource)) => resource,
Ok(None) => {
self.release(reservation);
return Err("allocator: CreatePlacedResource returned None".to_string());
}
Err(e) => {
self.release(reservation);
return Err(format!("allocator: place {} bytes: {e}", info.SizeInBytes));
}
};
if reservation.recycled {
self.activate(&resource)?;
}
let heap = reservation.heap.clone();
Ok((resource, heap, Rc::new(self.lease(reservation))))
}
fn allocation_info(
&self,
desc: &D3D12_RESOURCE_DESC,
) -> Result<(D3D12_RESOURCE_DESC, D3D12_RESOURCE_ALLOCATION_INFO), String> {
let mut standard = *desc;
standard.Alignment = 0;
let info = unsafe { self.device.GetResourceAllocationInfo(0, &[standard]) };
if info.SizeInBytes == u64::MAX {
return Err("allocator: resource has no valid allocation size".to_string());
}
if small_alignment_eligible(desc) && info.SizeInBytes <= HEAP_GRANULARITY {
let mut small = *desc;
small.Alignment = D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT as u64;
let small_info = unsafe { self.device.GetResourceAllocationInfo(0, &[small]) };
if small_info.Alignment == D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT as u64
&& small_info.SizeInBytes != u64::MAX
{
return Ok((small, small_info));
}
}
standard.Alignment = info.Alignment;
Ok((standard, info))
}
fn reserve(&self, key: PoolKey, size: u64, align: u64) -> Result<Reservation, String> {
let mut inner = self.inner.borrow_mut();
let pool = inner.pools.entry(key).or_insert_with(Pool::new);
if let Some(placement) = pool.placement.alloc(size, align) {
let heap = pool.heaps[placement.block]
.clone()
.ok_or("allocator: placement named a released heap")?;
return Ok(Reservation {
heap,
key,
placement,
size,
recycled: true,
});
}
let block_bytes = pool.next_block_bytes(size, align);
let heap = new_heap(&self.device, key, block_bytes)?;
let index = pool.placement.add_block(block_bytes);
if index == pool.heaps.len() {
pool.heaps.push(Some(heap.clone()));
} else {
pool.heaps[index] = Some(heap.clone());
}
let placement = pool
.placement
.alloc_in(index, size, align)
.ok_or("allocator: a block sized for a request failed to host it")?;
Ok(Reservation {
heap,
key,
placement,
size,
recycled: false,
})
}
fn activate(&self, resource: &ID3D12Resource) -> Result<(), String> {
let (allocator, cmd) =
super::texture::one_shot_submit_nowait(&self.device, &self.queue, |cmd| unsafe {
cmd.ResourceBarrier(&[super::texture::aliasing_barrier(resource)]);
})?;
let mut inner = self.inner.borrow_mut();
let retire_at = inner.frame + inner.retire_depth;
inner.parked.push(ParkedList {
allocator,
cmd,
retire_at,
});
Ok(())
}
fn lease(&self, reservation: Reservation) -> Lease {
Lease {
owner: Rc::downgrade(&self.inner),
key: reservation.key,
placement: reservation.placement,
size: reservation.size,
}
}
fn release(&self, reservation: Reservation) {
let mut inner = self.inner.borrow_mut();
if let Some(pool) = inner.pools.get_mut(&reservation.key) {
pool.placement
.free(reservation.placement, reservation.size, 0);
}
}
}
fn buffer_desc(size: u64) -> D3D12_RESOURCE_DESC {
D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Width: size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
..Default::default()
}
}
fn is_gpu_written(desc: &D3D12_RESOURCE_DESC) -> bool {
let written = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET.0
| D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL.0
| D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS.0;
desc.Flags.0 & written != 0
}
fn small_alignment_eligible(desc: &D3D12_RESOURCE_DESC) -> bool {
desc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER
&& desc.SampleDesc.Count <= 1
&& !is_gpu_written(desc)
}
fn resource_heap_tier(device: &ID3D12Device) -> D3D12_RESOURCE_HEAP_TIER {
let mut options = D3D12_FEATURE_DATA_D3D12_OPTIONS::default();
let ok = unsafe {
device.CheckFeatureSupport(
D3D12_FEATURE_D3D12_OPTIONS,
&mut options as *mut _ as *mut std::ffi::c_void,
std::mem::size_of::<D3D12_FEATURE_DATA_D3D12_OPTIONS>() as u32,
)
};
if ok.is_ok() {
options.ResourceHeapTier
} else {
D3D12_RESOURCE_HEAP_TIER_1
}
}
fn new_heap(device: &ID3D12Device, key: PoolKey, size: u64) -> Result<ID3D12Heap, String> {
let desc = D3D12_HEAP_DESC {
SizeInBytes: size.max(HEAP_GRANULARITY),
Properties: D3D12_HEAP_PROPERTIES {
Type: key.kind.to_d3d12(),
..Default::default()
},
Alignment: HEAP_GRANULARITY,
Flags: key.class.heap_flags(),
};
let mut heap: Option<ID3D12Heap> = None;
unsafe { device.CreateHeap(&desc, &mut heap) }
.map_err(|e| format!("allocator: create a {size}-byte heap: {e}"))?;
heap.ok_or_else(|| "allocator: CreateHeap returned None".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_11_0;
fn device() -> Option<ID3D12Device> {
let mut device: Option<ID3D12Device> = None;
unsafe { D3D12CreateDevice(None, D3D_FEATURE_LEVEL_11_0, &mut device) }.ok()?;
device
}
fn allocator() -> Option<DeviceAllocator> {
let device = device()?;
let queue_desc = D3D12_COMMAND_QUEUE_DESC {
Type: D3D12_COMMAND_LIST_TYPE_DIRECT,
..Default::default()
};
let queue: ID3D12CommandQueue = unsafe { device.CreateCommandQueue(&queue_desc) }.ok()?;
Some(DeviceAllocator::new(&device, &queue, 3))
}
fn texture_desc(width: u32) -> D3D12_RESOURCE_DESC {
D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width as u64,
Height: width,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_R8G8B8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..Default::default()
}
}
#[test]
fn pooled_heap_types_round_trip() {
for kind in [HeapKind::Default, HeapKind::Upload, HeapKind::Readback] {
assert_eq!(HeapKind::from_d3d12(kind.to_d3d12()), Ok(kind));
}
}
#[test]
fn custom_heaps_are_rejected() {
assert!(HeapKind::from_d3d12(D3D12_HEAP_TYPE_CUSTOM).is_err());
}
#[test]
fn tier_one_separates_the_three_heap_classes() {
let buffer = buffer_desc(1024);
let plain = texture_desc(64);
let mut target = texture_desc(64);
target.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
let mut depth = texture_desc(64);
depth.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
let tier = D3D12_RESOURCE_HEAP_TIER_1;
assert_eq!(HeapClass::for_desc(&buffer, tier), HeapClass::Buffers);
assert_eq!(
HeapClass::for_desc(&plain, tier),
HeapClass::NonRtDsTextures
);
assert_eq!(HeapClass::for_desc(&target, tier), HeapClass::RtDsTextures);
assert_eq!(HeapClass::for_desc(&depth, tier), HeapClass::RtDsTextures);
}
#[test]
fn tier_two_collapses_every_class_into_one_pool() {
let tier = D3D12_RESOURCE_HEAP_TIER_2;
assert_eq!(
HeapClass::for_desc(&buffer_desc(1024), tier),
HeapClass::All
);
assert_eq!(HeapClass::for_desc(&texture_desc(64), tier), HeapClass::All);
assert_eq!(
HeapClass::All.heap_flags(),
D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES
);
}
#[test]
fn each_class_asks_for_the_heap_flag_its_tier_one_pool_needs() {
assert_eq!(
HeapClass::Buffers.heap_flags(),
D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS
);
assert_eq!(
HeapClass::NonRtDsTextures.heap_flags(),
D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES
);
assert_eq!(
HeapClass::RtDsTextures.heap_flags(),
D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES
);
}
#[test]
fn gpu_written_descs_stay_committed() {
for flag in [
D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
] {
let mut desc = texture_desc(64);
desc.Flags = flag;
assert!(is_gpu_written(&desc), "{:#x}", flag.0);
assert!(!small_alignment_eligible(&desc));
}
assert!(!is_gpu_written(&texture_desc(64)));
assert!(!is_gpu_written(&buffer_desc(1024)));
}
#[test]
fn only_plain_single_sample_textures_ask_for_small_alignment() {
assert!(small_alignment_eligible(&texture_desc(64)));
assert!(!small_alignment_eligible(&buffer_desc(1024)));
let mut msaa = texture_desc(64);
msaa.SampleDesc.Count = 4;
assert!(!small_alignment_eligible(&msaa));
}
#[test]
fn blocks_grow_from_the_first_size_up_to_the_cap() {
let mut pool = Pool::new();
let mut sizes = Vec::new();
for _ in 0..6 {
let bytes = pool.next_block_bytes(1024, 256);
sizes.push(bytes);
pool.placement.add_block(bytes);
}
assert_eq!(
sizes,
vec![
FIRST_BLOCK_BYTES,
FIRST_BLOCK_BYTES * 2,
FIRST_BLOCK_BYTES * 4,
FIRST_BLOCK_BYTES * 8,
MAX_BLOCK_BYTES,
MAX_BLOCK_BYTES,
]
);
}
#[test]
fn an_oversized_request_sizes_its_own_block() {
let pool = Pool::new();
let huge = MAX_BLOCK_BYTES * 3;
let bytes = pool.next_block_bytes(huge, HEAP_GRANULARITY);
assert!(bytes >= huge);
assert_eq!(bytes % HEAP_GRANULARITY, 0);
}
#[test]
fn block_sizes_stay_on_the_placement_granularity() {
let pool = Pool::new();
for size in [1u64, 100, HEAP_GRANULARITY + 1, MAX_BLOCK_BYTES * 2 + 7] {
assert_eq!(pool.next_block_bytes(size, 256) % HEAP_GRANULARITY, 0);
}
}
#[test]
fn the_small_alignment_is_asked_for_only_when_it_can_be_granted() {
let Some(alloc) = allocator() else {
return;
};
let (desc, info) = alloc
.allocation_info(&texture_desc(64))
.expect("small texture sizes");
assert_eq!(
info.Alignment,
D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT as u64
);
assert_eq!(desc.Alignment, info.Alignment, "placement must match");
let (desc, info) = alloc
.allocation_info(&texture_desc(512))
.expect("large texture sizes");
assert_eq!(info.Alignment, HEAP_GRANULARITY);
assert_eq!(desc.Alignment, info.Alignment, "placement must match");
}
#[test]
fn many_buffers_share_few_heaps() {
let Some(alloc) = allocator() else {
return;
};
let buffers: Vec<PooledBuffer> = (0..512)
.map(|_| {
alloc
.alloc_buffer(
4096,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)
.expect("upload buffer places")
})
.collect();
let stats = alloc.stats();
assert!(stats.block_count <= 8, "{stats:?}");
assert!(stats.in_use_bytes >= 512 * HEAP_GRANULARITY, "{stats:?}");
assert!(stats.reserved_bytes >= stats.in_use_bytes, "{stats:?}");
drop(buffers);
}
#[test]
fn placed_buffers_get_distinct_non_overlapping_storage() {
let Some(alloc) = allocator() else {
return;
};
let write = |buffer: &PooledBuffer, byte: u8| {
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buffer.Map(0, None, Some(&mut ptr)) }.expect("upload buffer maps");
unsafe { std::ptr::write_bytes(ptr as *mut u8, byte, 256) };
};
let read = |buffer: &PooledBuffer| {
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buffer.Map(0, None, Some(&mut ptr)) }.expect("upload buffer maps");
unsafe { std::slice::from_raw_parts(ptr as *const u8, 256).to_vec() }
};
let a = alloc
.alloc_buffer(
256,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)
.expect("upload buffer places");
let b = alloc
.alloc_buffer(
256,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)
.expect("upload buffer places");
write(&a, 0xAA);
write(&b, 0x55);
assert!(read(&a).iter().all(|&x| x == 0xAA));
assert!(read(&b).iter().all(|&x| x == 0x55));
}
#[test]
fn a_dropped_lease_is_withheld_until_its_retire_frame() {
let Some(alloc) = allocator() else {
return;
};
let first = alloc
.alloc_buffer(
4096,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)
.expect("upload buffer places");
assert_eq!(alloc.stats().block_count, 1);
drop(first);
assert_eq!(alloc.stats().in_use_bytes, 0);
assert_eq!(alloc.stats().block_count, 1);
for _ in 0..5 {
alloc.begin_frame();
}
let stats = alloc.stats();
assert_eq!(stats.block_count, 0, "emptied heap is released");
assert_eq!(stats.reserved_bytes, 0);
}
#[test]
fn a_texture_larger_than_the_cap_gets_its_own_heap() {
let Some(alloc) = allocator() else {
return;
};
let big = alloc
.alloc_texture(
&texture_desc(8192),
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COPY_DEST,
)
.expect("oversized texture places");
let small = alloc
.alloc_texture(
&texture_desc(64),
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COPY_DEST,
)
.expect("small texture places");
assert_eq!(alloc.stats().block_count, 2, "{:?}", alloc.stats());
drop((big, small));
}
#[test]
fn gpu_written_textures_are_refused_by_the_pool() {
let Some(alloc) = allocator() else {
return;
};
let mut desc = texture_desc(64);
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
assert!(
alloc
.alloc_texture(
&desc,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_RENDER_TARGET
)
.is_err()
);
}
#[test]
fn a_recycled_range_is_handed_out_again_after_it_retires() {
let Some(alloc) = allocator() else {
return;
};
let first = alloc
.alloc_buffer(
4096,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)
.expect("upload buffer places");
let keep = alloc
.alloc_buffer(
4096,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)
.expect("upload buffer places");
drop(first);
for _ in 0..5 {
alloc.begin_frame();
}
let reused = alloc
.alloc_buffer(
4096,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)
.expect("recycled range places and activates");
assert_eq!(alloc.stats().block_count, 1, "{:?}", alloc.stats());
drop((keep, reused));
}
}