use crate::directx::descriptor_slot::SrvSlot;
use concinnity_core::render::error::{RenderError, RenderResult};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use windows::Win32::Graphics::Direct3D12::*;
pub(in crate::directx) const POST_TARGET_SLOTS: usize = 48;
const _: () = assert!(POST_TARGET_SLOTS <= u64::BITS as usize);
pub(in crate::directx) struct PostTargetDescriptors {
pub srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub srv_gpu: SrvSlot,
pub rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
_lease: SlotLease,
}
struct SlotLease {
used: Arc<AtomicU64>,
index: usize,
}
impl Drop for SlotLease {
fn drop(&mut self) {
self.used
.fetch_and(!(1u64 << self.index), Ordering::Relaxed);
}
}
pub(in crate::directx) struct PostDescriptors {
srv_cpu_base: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu_base: SrvSlot,
srv_size: usize,
rtv_base: D3D12_CPU_DESCRIPTOR_HANDLE,
rtv_size: usize,
used: Arc<AtomicU64>,
}
impl PostDescriptors {
pub(in crate::directx) fn new(
srv_cpu_base: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu_base: SrvSlot,
srv_size: usize,
rtv_base: D3D12_CPU_DESCRIPTOR_HANDLE,
rtv_size: usize,
) -> Self {
Self {
srv_cpu_base,
srv_gpu_base,
srv_size,
rtv_base,
rtv_size,
used: Arc::new(AtomicU64::new(0)),
}
}
pub(in crate::directx) fn allocate(&self) -> RenderResult<PostTargetDescriptors> {
let mut current = self.used.load(Ordering::Relaxed);
let i = loop {
let i = (!current).trailing_zeros() as usize;
if i >= POST_TARGET_SLOTS {
return Err(RenderError::Other(format!(
"the shared post passes asked for more than {POST_TARGET_SLOTS} targets"
)));
}
match self.used.compare_exchange_weak(
current,
current | (1u64 << i),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break i,
Err(actual) => current = actual,
}
};
Ok(PostTargetDescriptors {
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: self.srv_cpu_base.ptr + i * self.srv_size,
},
srv_gpu: self.srv_gpu_base.offset(i, self.srv_size),
rtv: D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: self.rtv_base.ptr + i * self.rtv_size,
},
_lease: SlotLease {
used: Arc::clone(&self.used),
index: i,
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn block() -> PostDescriptors {
PostDescriptors::new(
D3D12_CPU_DESCRIPTOR_HANDLE { ptr: 4096 },
SrvSlot::for_test(8192),
32,
D3D12_CPU_DESCRIPTOR_HANDLE { ptr: 512 },
16,
)
}
#[test]
fn slots_are_handed_out_in_order_at_the_heap_stride() {
let b = block();
let a = b.allocate().expect("first slot");
let c = b.allocate().expect("second slot");
assert_eq!(a.srv_cpu.ptr, 4096);
assert_eq!(a.srv_gpu, SrvSlot::for_test(8192));
assert_eq!(a.rtv.ptr, 512);
assert_eq!(c.srv_cpu.ptr, 4096 + 32);
assert_eq!(c.srv_gpu, SrvSlot::for_test(8192 + 32));
assert_eq!(c.rtv.ptr, 512 + 16);
}
#[test]
fn a_dropped_set_is_reissued_on_the_same_slots() {
let b = block();
let first: Vec<_> = (0..3).map(|_| b.allocate().expect("slot")).collect();
let ptrs: Vec<_> = first.iter().map(|d| d.srv_gpu).collect();
drop(first);
let again: Vec<_> = (0..3).map(|_| b.allocate().expect("slot")).collect();
assert_eq!(ptrs, again.iter().map(|d| d.srv_gpu).collect::<Vec<_>>());
}
#[test]
fn a_held_slot_is_never_reissued() {
let b = block();
let taa = [b.allocate().expect("slot"), b.allocate().expect("slot")];
let ssr = b.allocate().expect("slot");
let ssr_slot = ssr.srv_gpu;
drop(taa);
let rebuilt = [b.allocate().expect("slot"), b.allocate().expect("slot")];
assert!(rebuilt.iter().all(|d| d.srv_gpu != ssr_slot));
assert_eq!(ssr.srv_gpu, ssr_slot);
}
#[test]
fn a_freed_middle_slot_is_the_next_one_handed_out() {
let b = block();
let _first = b.allocate().expect("slot");
let middle = b.allocate().expect("slot");
let _last = b.allocate().expect("slot");
let middle_ptr = middle.rtv.ptr;
drop(middle);
assert_eq!(b.allocate().expect("slot").rtv.ptr, middle_ptr);
}
#[test]
fn running_out_of_slots_is_an_error_not_an_overrun() {
let b = block();
let held: Vec<_> = (0..POST_TARGET_SLOTS)
.map(|_| b.allocate().expect("reserved slot"))
.collect();
assert!(b.allocate().is_err());
drop(held);
assert!(b.allocate().is_ok());
}
}