use std::cell::RefCell;
use windows::Win32::Graphics::Direct3D12::*;
use crate::directx::allocator::{DeviceAllocator, PooledBuffer};
use crate::directx::com;
use crate::directx::texture::create_buffer;
pub(in crate::directx) use crate::gfx::fullscreen::align_up;
pub(in crate::directx) const UPLOAD_ALIGN: u64 = 16;
const UPLOAD_MIN_CAPACITY: u64 = 64 * 1024;
fn grow_capacity(capacity: u64, needed: u64) -> u64 {
let mut cap = capacity.max(UPLOAD_MIN_CAPACITY);
while cap < needed {
cap *= 2;
}
cap
}
struct Slot {
buffer: Option<PooledBuffer>,
base: *mut u8,
gpu_va: u64,
capacity: u64,
cursor: u64,
}
impl Slot {
fn empty() -> Self {
Slot {
buffer: None,
base: std::ptr::null_mut(),
gpu_va: 0,
capacity: 0,
cursor: 0,
}
}
}
pub(in crate::directx) struct UploadRing {
slots: Vec<RefCell<Slot>>,
}
impl UploadRing {
pub(in crate::directx) fn new(frames: usize) -> Self {
UploadRing {
slots: (0..frames).map(|_| RefCell::new(Slot::empty())).collect(),
}
}
pub(in crate::directx) fn reserve(
&self,
alloc: &DeviceAllocator,
frame: usize,
needed: u64,
) -> Result<(), String> {
let mut slot = self.slots[frame].borrow_mut();
slot.cursor = 0;
if needed <= slot.capacity {
return Ok(());
}
let new_cap = grow_capacity(slot.capacity, needed);
let buffer = create_buffer(
alloc,
new_cap,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut base = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buffer.Map(0, None, Some(&mut base)) }.map_err(|e| format!("upload map: {e}"))?;
let gpu_va = com::gpu_va(&buffer);
slot.buffer = Some(buffer);
slot.base = base as *mut u8;
slot.gpu_va = gpu_va;
slot.capacity = new_cap;
Ok(())
}
pub(in crate::directx) fn push(&self, frame: usize, bytes: &[u8]) -> Result<u64, String> {
let mut slot = self.slots[frame].borrow_mut();
let offset = align_up(slot.cursor, UPLOAD_ALIGN);
let end = offset + bytes.len() as u64;
if end > slot.capacity {
return Err(format!(
"upload ring overflow: need {end} bytes, reserved {}",
slot.capacity
));
}
unsafe {
std::ptr::copy_nonoverlapping(
bytes.as_ptr(),
slot.base.add(offset as usize),
bytes.len(),
);
}
slot.cursor = end;
Ok(slot.gpu_va + offset)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grow_capacity_starts_at_minimum() {
assert_eq!(grow_capacity(0, 1), UPLOAD_MIN_CAPACITY);
assert_eq!(grow_capacity(0, 0), UPLOAD_MIN_CAPACITY);
}
#[test]
fn grow_capacity_doubles_until_it_fits() {
let need = UPLOAD_MIN_CAPACITY * 3 + 1;
let cap = grow_capacity(0, need);
assert!(cap >= need);
assert_eq!(cap, UPLOAD_MIN_CAPACITY * 4);
}
#[test]
fn grow_capacity_never_shrinks_below_existing() {
let cap = grow_capacity(UPLOAD_MIN_CAPACITY * 8, 10);
assert_eq!(cap, UPLOAD_MIN_CAPACITY * 8);
}
}