use super::barriers;
use super::tiles;
use super::types::{BufferState, Dx12State};
use super::{BufferHandle, DeviceHandle};
use crate::backend::BufferKind;
use crate::types::BufferFlags;
use anyhow::{Context, Result};
use windows::core::Interface;
use windows::Win32::Graphics::{Direct3D12::*, Dxgi::Common::*};
use super::types::LogicalDevice;
#[inline]
fn uniform_buffer_allocation_width(logical_size: u64, requested_width: u64) -> u64 {
debug_assert!(logical_size <= requested_width);
let cbv_range = (logical_size + 255) & !255;
requested_width.max(cbv_range)
}
fn alloc_committed_buffer_pair(
logical_device: &LogicalDevice,
size: u64,
is_storage: bool,
cpu_readable: bool,
) -> Result<(ID3D12Resource, Option<ID3D12Resource>, Option<usize>)> {
if cpu_readable && !is_storage {
anyhow::bail!("BufferFlags::CPU_READABLE is only valid for storage (UAV) buffers on resize allocation");
}
let (heap_type, resource_flags) = if is_storage {
(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
} else {
(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_FLAG_NONE)
};
let heap_properties = D3D12_HEAP_PROPERTIES {
Type: heap_type,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let resource_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: resource_flags,
};
let initial_state = if heap_type == D3D12_HEAP_TYPE_UPLOAD {
D3D12_RESOURCE_STATE_GENERIC_READ
} else {
D3D12_RESOURCE_STATE_COMMON
};
let mut resource: Option<ID3D12Resource> = None;
unsafe {
logical_device.device.CreateCommittedResource(
&heap_properties,
D3D12_HEAP_FLAG_NONE,
&resource_desc,
initial_state,
None,
&mut resource,
)
}
.context("resize: CreateCommittedResource main buffer")?;
let resource = resource.context("resize: main buffer null")?;
let (coherent_readback, coherent_readback_mapped) = if cpu_readable && is_storage {
let readback_heap = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_READBACK,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let readback_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut rb: Option<ID3D12Resource> = None;
unsafe {
logical_device.device.CreateCommittedResource(
&readback_heap,
D3D12_HEAP_FLAG_NONE,
&readback_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
None,
&mut rb,
)
}
.context("resize: CreateCommittedResource readback")?;
let rb = rb.context("resize: readback null")?;
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { rb.Map(0, Some(&no_read), Some(&mut mapped)) }.context("resize: map readback")?;
let p = mapped as *mut u8;
if p.is_null() {
anyhow::bail!("resize: Map readback returned null");
}
(Some(rb), Some(p as usize))
} else {
(None, None)
};
Ok((resource, coherent_readback, coherent_readback_mapped))
}
fn create_cpu_writable_upload(logical_device: &LogicalDevice, allocation_size: u64) -> Result<(ID3D12Resource, usize)> {
let upload_heap = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_UPLOAD,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let upload_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: allocation_size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut upload: Option<ID3D12Resource> = None;
unsafe {
logical_device.device.CreateCommittedResource(
&upload_heap,
D3D12_HEAP_FLAG_NONE,
&upload_desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
None,
&mut upload,
)
}
.context("Failed to create CPU_WRITABLE upload buffer")?;
let upload = upload.context("CreateCommittedResource upload returned null")?;
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { upload.Map(0, Some(&no_read), Some(&mut mapped)) }.context("Failed to map CPU_WRITABLE upload buffer")?;
let p = mapped as *mut u8;
if p.is_null() {
anyhow::bail!("Map returned null for CPU_WRITABLE upload");
}
Ok((upload, p as usize))
}
fn rewrite_root_buffer_descriptors(
logical_device: &LogicalDevice,
new_resource: &ID3D12Resource,
new_size: u64,
old: &BufferState,
) -> Result<()> {
if old.is_storage {
let stride = old.element_stride.unwrap_or(4);
let num_elements = (new_size as u32) / stride;
if let Some(uav_off) = old.bindless_offset {
let uav_desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_UAV_DIMENSION_BUFFER,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_UAV {
FirstElement: 0,
NumElements: num_elements,
StructureByteStride: stride,
CounterOffsetInBytes: 0,
Flags: D3D12_BUFFER_UAV_FLAG_NONE,
},
},
};
let uav_cpu_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (uav_off * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateUnorderedAccessView(new_resource, None, Some(&uav_desc), uav_cpu_handle);
}
}
if let Some(srv_off) = old.bindless_srv_offset {
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_SRV_DIMENSION_BUFFER,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_SRV {
FirstElement: 0,
NumElements: num_elements,
StructureByteStride: stride,
Flags: D3D12_BUFFER_SRV_FLAG_NONE,
},
},
};
let srv_cpu_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (srv_off * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateShaderResourceView(new_resource, Some(&srv_desc), srv_cpu_handle);
}
}
} else if let Some(cbv_off) = old.bindless_offset {
let aligned_size = (new_size + 255) & !255;
let cbv_desc = D3D12_CONSTANT_BUFFER_VIEW_DESC {
BufferLocation: unsafe { new_resource.GetGPUVirtualAddress() },
SizeInBytes: aligned_size as u32,
};
let cbv_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (cbv_off * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateConstantBufferView(Some(&cbv_desc), cbv_handle);
}
}
Ok(())
}
fn patch_buffer_views_after_parent_resize(state: &mut Dx12State, parent_handle: BufferHandle) -> Result<()> {
let new_resource = {
let buffers_read = state.buffers.read().unwrap();
buffers_read
.entries
.get(&parent_handle)
.context("patch_buffer_views: parent missing")?
.resource
.clone()
};
let view_handles: Vec<BufferHandle> = state
.buffers
.read()
.unwrap()
.entries
.iter()
.filter(|(_, b)| b.is_view && b.parent_for_view == Some(parent_handle))
.map(|(&h, _)| h)
.collect();
for vh in view_handles {
let (device_handle, stride, byte_off, view_size, uav_off, srv_off) = {
let buffers_read = state.buffers.read().unwrap();
let v = buffers_read.entries.get(&vh).context("patch_buffer_views: view")?;
(
v.device_handle,
v.element_stride.unwrap_or(4),
v.view_byte_offset.context("patch_buffer_views: view offset")?,
v.size,
v.bindless_offset,
v.bindless_srv_offset,
)
};
if stride == 0 {
anyhow::bail!("patch_buffer_views: stride 0");
}
if !byte_off.is_multiple_of(stride as u64) {
anyhow::bail!("patch_buffer_views: offset not stride-aligned");
}
let first_element = (byte_off / stride as u64) as u32;
let num_elements = (view_size as u32) / stride;
if let (Some(uav_off), Some(srv_off)) = (uav_off, srv_off) {
if num_elements > 0 {
let logical_device = state
.devices
.get(&device_handle)
.context("patch_buffer_views: device")?;
let uav_desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_UAV_DIMENSION_BUFFER,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_UAV {
FirstElement: first_element as u64,
NumElements: num_elements,
StructureByteStride: stride,
CounterOffsetInBytes: 0,
Flags: D3D12_BUFFER_UAV_FLAG_NONE,
},
},
};
let uav_cpu_handle = unsafe {
let mut h = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
h.ptr += (uav_off * logical_device.cbv_srv_uav_descriptor_size) as usize;
h
};
unsafe {
logical_device.device.CreateUnorderedAccessView(
&new_resource,
None,
Some(&uav_desc),
uav_cpu_handle,
);
}
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_SRV_DIMENSION_BUFFER,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_SRV {
FirstElement: first_element as u64,
NumElements: num_elements,
StructureByteStride: stride,
Flags: D3D12_BUFFER_SRV_FLAG_NONE,
},
},
};
let srv_cpu_handle = unsafe {
let mut h = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
h.ptr += (srv_off * logical_device.cbv_srv_uav_descriptor_size) as usize;
h
};
unsafe {
logical_device
.device
.CreateShaderResourceView(&new_resource, Some(&srv_desc), srv_cpu_handle);
}
}
}
state
.buffers
.write()
.unwrap()
.entries
.get_mut(&vh)
.context("patch_buffer_views: view mut")?
.resource = new_resource.clone();
}
Ok(())
}
pub(super) fn resize(
state: &mut Dx12State,
device_handle: DeviceHandle,
buffer_handle: BufferHandle,
new_size: u64,
preserve_contents: bool,
) -> Result<()> {
let old = {
let buffers_read = state.buffers.read().unwrap();
buffers_read
.entries
.get(&buffer_handle)
.cloned()
.context("resize_buffer: invalid buffer")?
};
if old.device_handle != device_handle {
anyhow::bail!("resize_buffer: buffer belongs to a different device");
}
if old.is_view {
anyhow::bail!("resize_buffer: cannot resize a buffer view");
}
if old.transient_placed {
anyhow::bail!("resize_buffer: cannot resize a transient placed buffer");
}
if new_size == old.size {
return Ok(());
}
if old.is_reserved && new_size <= old.allocation_size {
anyhow::bail!(
"reserved buffer: growth within virtual capacity must use set_buffer_logical_size, not resize_buffer"
);
}
let cpu_readable = old.flags.contains(BufferFlags::CPU_READABLE);
if cpu_readable && !old.is_storage {
anyhow::bail!("resize_buffer: invalid CPU_READABLE uniform buffer");
}
let stride = old.element_stride.unwrap_or(4);
if old.is_storage && stride > 0 && new_size > 0 && !(new_size as u32).is_multiple_of(stride) {
anyhow::bail!("resize_buffer: new size {new_size} not divisible by stride {stride}");
}
let logical_device_ro = state
.devices
.get(&device_handle)
.context("resize_buffer: invalid device")?;
if old.coherent_readback_mapped.is_some() {
if let Some(ref rb) = old.coherent_readback {
let no_write = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { rb.Unmap(0, Some(&no_write)) };
}
}
let new_alloc_width = if old.is_storage {
new_size
} else {
uniform_buffer_allocation_width(new_size, new_size)
};
let (new_resource, new_readback, new_readback_mapped) =
alloc_committed_buffer_pair(logical_device_ro, new_alloc_width, old.is_storage, cpu_readable)?;
let old_resource = old.resource.clone();
let copy_len = if preserve_contents { old.size.min(new_size) } else { 0 };
let need_copy = old.is_storage && copy_len > 0;
let need_tail_clear = old.is_storage && preserve_contents && new_size > old.size;
if old.is_storage && (need_copy || need_tail_clear) {
let device = state.devices.get(&device_handle).context("resize_buffer: device")?;
let copy_allocator: ID3D12CommandAllocator =
unsafe { device.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }
.context("resize_buffer: allocator")?;
let cmd: ID3D12GraphicsCommandList = unsafe {
device
.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, ©_allocator, None)
}
.context("resize_buffer: command list")?;
let cmd7: ID3D12GraphicsCommandList7 = cmd.cast().context("ID3D12GraphicsCommandList7")?;
if need_copy {
let mut b_old = [barriers::buffer_barrier_full(
&old_resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_ACCESS_COMMON,
D3D12_BARRIER_ACCESS_COPY_SOURCE,
)];
let mut b_new = [barriers::buffer_barrier_full(
&new_resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_ACCESS_COMMON,
D3D12_BARRIER_ACCESS_COPY_DEST,
)];
unsafe {
barriers::barrier_buffers(&cmd7, &b_old);
barriers::drop_buffer_barriers(&mut b_old);
barriers::barrier_buffers(&cmd7, &b_new);
barriers::drop_buffer_barriers(&mut b_new);
}
unsafe { cmd.CopyBufferRegion(&new_resource, 0, &old_resource, 0, copy_len) };
}
if need_tail_clear {
let tail_len = new_size - old.size;
if !need_copy {
let mut b_to_copy = [barriers::buffer_barrier_full(
&new_resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_ACCESS_COMMON,
D3D12_BARRIER_ACCESS_COPY_DEST,
)];
unsafe {
barriers::barrier_buffers(&cmd7, &b_to_copy);
barriers::drop_buffer_barriers(&mut b_to_copy);
}
}
let zero = &device.zero_buffer;
let mut tail_written = 0u64;
while tail_written < tail_len {
let this_chunk = (tail_len - tail_written).min(ZERO_BUFFER_SIZE);
unsafe {
cmd.CopyBufferRegion(&new_resource, old.size + tail_written, zero, 0, this_chunk);
}
tail_written += this_chunk;
}
let mut b_to_common = [barriers::buffer_barrier_full(
&new_resource,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_ACCESS_COPY_DEST,
D3D12_BARRIER_ACCESS_COMMON,
)];
unsafe {
barriers::barrier_buffers(&cmd7, &b_to_common);
barriers::drop_buffer_barriers(&mut b_to_common);
}
} else if need_copy {
let mut b_to_common = [barriers::buffer_barrier_full(
&new_resource,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_ACCESS_COPY_DEST,
D3D12_BARRIER_ACCESS_COMMON,
)];
unsafe {
barriers::barrier_buffers(&cmd7, &b_to_common);
barriers::drop_buffer_barriers(&mut b_to_common);
}
}
unsafe { cmd.Close() }.context("resize_buffer: Close")?;
let lists: [Option<ID3D12CommandList>; 1] = [Some(cmd.cast()?)];
let fence_value = super::utils::execute_command_lists_and_signal_device(device, &lists)?;
super::utils::wait_for_fence(&device.fence, fence_value)?;
} else if !old.is_storage && preserve_contents && copy_len > 0 {
let mut src: *mut std::ffi::c_void = std::ptr::null_mut();
let read_all = D3D12_RANGE {
Begin: 0,
End: old.size as usize,
};
unsafe { old_resource.Map(0, Some(&read_all), Some(&mut src)) }.context("resize_buffer: map old uniform")?;
let mut dst: *mut std::ffi::c_void = std::ptr::null_mut();
let dst_range = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { new_resource.Map(0, Some(&dst_range), Some(&mut dst)) }.context("resize_buffer: map new uniform")?;
unsafe {
std::ptr::copy_nonoverlapping(src as *const u8, dst as *mut u8, copy_len as usize);
if new_size > old.size {
std::ptr::write_bytes(
(dst as *mut u8).add(old.size as usize),
0,
(new_size - old.size) as usize,
);
}
}
let written = D3D12_RANGE {
Begin: 0,
End: new_size as usize,
};
unsafe { new_resource.Unmap(0, Some(&written)) };
let noop = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { old_resource.Unmap(0, Some(&noop)) };
}
let logical_device = state
.devices
.get(&device_handle)
.context("resize_buffer: device for descriptors")?;
rewrite_root_buffer_descriptors(logical_device, &new_resource, new_size, &old)?;
let cpu_writable = old.flags.contains(BufferFlags::CPU_WRITABLE);
let (upload_buffer, cpu_writable_upload_mapped) = if cpu_writable {
let (upload, mapped) = create_cpu_writable_upload(logical_device, new_alloc_width)?;
if preserve_contents && copy_len > 0 {
if let Some(old_mapped) = old.cpu_writable_upload_mapped {
unsafe {
std::ptr::copy_nonoverlapping(old_mapped as *const u8, mapped as *mut u8, copy_len as usize);
}
}
}
(Some(upload), Some(mapped))
} else {
(None, None)
};
state.buffers.write().unwrap().entries.insert(
buffer_handle,
BufferState {
device_handle,
resource: new_resource,
size: new_size,
allocation_size: new_alloc_width,
bindless_offset: old.bindless_offset,
bindless_srv_offset: old.bindless_srv_offset,
is_storage: old.is_storage,
upload_buffer,
element_stride: old.element_stride,
is_view: false,
coherent_readback: new_readback,
coherent_readback_mapped: new_readback_mapped,
cpu_writable_upload_mapped,
flags: old.flags,
transient_placed: false,
parent_for_view: None,
view_byte_offset: None,
is_reserved: false,
tile_byte_size: 0,
reserved_tiles: Vec::new(),
is_withdraw_staging: false,
texture_copy_footprint: None,
},
);
patch_buffer_views_after_parent_resize(state, buffer_handle)?;
let ctx_h = super::context::destroy_attribution_context(state, device_handle);
let base = super::context::reclamation_requirements(state, device_handle, ctx_h);
let requirements = {
let dev = state
.devices
.get(&device_handle)
.context("resize_buffer: queue deletion")?;
let registry = dev.descriptors.lock().unwrap();
registry.bindless_retirement_requirements_for_buffer(buffer_handle, base)
};
let dev = state
.devices
.get(&device_handle)
.context("resize_buffer: queue deletion")?;
if old.is_reserved {
dev.deletion_queue.lock().unwrap().queue(
requirements,
super::types::PendingDeletion::ReplacedReservedBufferGpu {
resource: old_resource,
tiles: old.reserved_tiles,
upload_buffer: old.upload_buffer,
coherent_readback: old.coherent_readback,
},
);
} else {
dev.deletion_queue.lock().unwrap().queue(
requirements,
super::types::PendingDeletion::ReplacedBufferGpu {
resource: old_resource,
upload_buffer: old.upload_buffer,
coherent_readback: old.coherent_readback,
},
);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(super) fn create(
state: &mut Dx12State,
device_handle: DeviceHandle,
logical_size: u64,
allocation_size: u64,
access: BufferKind,
element_stride: Option<u32>,
flags: BufferFlags,
) -> Result<BufferHandle> {
debug_assert!(logical_size <= allocation_size);
let allocation_size = if access == BufferKind::Broadcast {
uniform_buffer_allocation_width(logical_size, allocation_size)
} else {
allocation_size
};
let cpu_readable = flags.contains(BufferFlags::CPU_READABLE);
let cpu_writable = flags.contains(BufferFlags::CPU_WRITABLE);
let (resource, upload_buffer, is_storage, coherent_readback, coherent_readback_mapped, cpu_writable_upload_mapped) = {
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
let is_storage = access == BufferKind::Scattered;
if cpu_readable && !is_storage {
anyhow::bail!("BufferFlags::CPU_READABLE is only valid for BufferKind::Scattered (storage) buffers");
}
if cpu_writable && !is_storage {
anyhow::bail!("BufferFlags::CPU_WRITABLE is only valid for BufferKind::Scattered (storage) buffers");
}
if cpu_writable && (cpu_readable || flags.contains(BufferFlags::GPU_ONLY)) {
anyhow::bail!("BufferFlags::CPU_WRITABLE cannot be combined with CPU_READABLE or GPU_ONLY");
}
let (heap_type, resource_flags) = if is_storage {
(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
} else {
(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_FLAG_NONE)
};
let heap_properties = D3D12_HEAP_PROPERTIES {
Type: heap_type,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let resource_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: allocation_size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: resource_flags,
};
let initial_state = if heap_type == D3D12_HEAP_TYPE_UPLOAD {
D3D12_RESOURCE_STATE_GENERIC_READ
} else {
D3D12_RESOURCE_STATE_COMMON
};
let mut resource: Option<ID3D12Resource> = None;
let hr = unsafe {
logical_device.device.CreateCommittedResource(
&heap_properties,
D3D12_HEAP_FLAG_NONE,
&resource_desc,
initial_state,
None,
&mut resource,
)
};
if hr.is_err() {
crate::signal::push_sync_signal(crate::signal::Signal::Oversubscribed {
reason: crate::signal::OversubscribedReason::BufferHeap,
size_hint: allocation_size,
});
}
hr.context("Failed to create buffer resource")?;
let resource = resource.context("CreateCommittedResource returned null")?;
let (upload_buffer, cpu_writable_upload_mapped) = if cpu_writable && is_storage {
let upload_heap = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_UPLOAD,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let upload_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: allocation_size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut upload: Option<ID3D12Resource> = None;
unsafe {
logical_device.device.CreateCommittedResource(
&upload_heap,
D3D12_HEAP_FLAG_NONE,
&upload_desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
None,
&mut upload,
)
}
.context("Failed to create CPU_WRITABLE upload buffer")?;
let upload = upload.context("CreateCommittedResource upload returned null")?;
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { upload.Map(0, Some(&no_read), Some(&mut mapped)) }
.context("Failed to map CPU_WRITABLE upload buffer")?;
let p = mapped as *mut u8;
if p.is_null() {
anyhow::bail!("Map returned null for CPU_WRITABLE upload");
}
(Some(upload), Some(p as usize))
} else {
(None, None)
};
let (coherent_readback, coherent_readback_mapped) = if cpu_readable && is_storage {
let readback_heap = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_READBACK,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let readback_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: allocation_size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut rb: Option<ID3D12Resource> = None;
unsafe {
logical_device.device.CreateCommittedResource(
&readback_heap,
D3D12_HEAP_FLAG_NONE,
&readback_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
None,
&mut rb,
)
}
.context("Failed to create CPU_READABLE readback buffer")?;
let rb = rb.context("CreateCommittedResource readback returned null")?;
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { rb.Map(0, Some(&no_read), Some(&mut mapped)) }
.context("Failed to map CPU_READABLE readback buffer")?;
let p = mapped as *mut u8;
if p.is_null() {
anyhow::bail!("Map returned null for CPU_READABLE readback");
}
(Some(rb), Some(p as usize))
} else {
(None, None)
};
(
resource,
upload_buffer,
is_storage,
coherent_readback,
coherent_readback_mapped,
cpu_writable_upload_mapped,
)
};
let handle = state.buffers.write().unwrap().alloc_handle();
let is_uniform = access == BufferKind::Broadcast;
let (bindless_offset, bindless_srv_offset) = if is_storage || is_uniform {
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
if is_storage {
let stride = element_stride.unwrap_or(4);
debug_assert!(
stride > 0 && (logical_size as u32).is_multiple_of(stride),
"buffer logical size {logical_size} not evenly divisible by element stride {stride} — \
likely a stride mismatch (set BufferProxy::element_stride or \
update element_stride_for_buffer)"
);
let num_elements = (logical_size as u32) / stride;
let uav_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_buffer_uav(handle);
let uav_desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN, ViewDimension: D3D12_UAV_DIMENSION_BUFFER,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_UAV {
FirstElement: 0,
NumElements: num_elements,
StructureByteStride: stride,
CounterOffsetInBytes: 0,
Flags: D3D12_BUFFER_UAV_FLAG_NONE,
},
},
};
let uav_cpu_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (uav_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateUnorderedAccessView(&resource, None, Some(&uav_desc), uav_cpu_handle);
}
let srv_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_buffer_srv(handle);
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN, ViewDimension: D3D12_SRV_DIMENSION_BUFFER,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_SRV {
FirstElement: 0,
NumElements: num_elements,
StructureByteStride: stride,
Flags: D3D12_BUFFER_SRV_FLAG_NONE,
},
},
};
let srv_cpu_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (srv_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateShaderResourceView(&resource, Some(&srv_desc), srv_cpu_handle);
}
tracing::debug!(
"Created UAV at {} and SRV at {} for storage buffer {}",
uav_offset,
srv_offset,
handle
);
(Some(uav_offset), Some(srv_offset))
} else {
let cbv_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_buffer_cbv(handle);
let aligned_size = (logical_size + 255) & !255;
if aligned_size > allocation_size {
anyhow::bail!("uniform buffer CBV size {aligned_size} exceeds allocation {allocation_size}");
}
let cbv_desc = D3D12_CONSTANT_BUFFER_VIEW_DESC {
BufferLocation: unsafe { resource.GetGPUVirtualAddress() },
SizeInBytes: aligned_size as u32,
};
let cbv_handle = unsafe {
let mut cpu_handle = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (cbv_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
logical_device
.device
.CreateConstantBufferView(Some(&cbv_desc), cbv_handle);
}
tracing::debug!("Created CBV for buffer {} at heap offset {}", handle, cbv_offset);
(Some(cbv_offset), None) }
} else {
(None, None)
};
state.buffers.write().unwrap().entries.insert(
handle,
BufferState {
device_handle,
resource,
size: logical_size,
allocation_size,
bindless_offset,
bindless_srv_offset,
is_storage,
upload_buffer,
element_stride,
is_view: false,
coherent_readback,
coherent_readback_mapped,
cpu_writable_upload_mapped,
flags,
transient_placed: false,
parent_for_view: None,
view_byte_offset: None,
is_reserved: false,
tile_byte_size: 0,
reserved_tiles: Vec::new(),
is_withdraw_staging: false,
texture_copy_footprint: None,
},
);
Ok(handle)
}
pub(super) fn create_reserved_with_capacity(
state: &mut Dx12State,
device_handle: DeviceHandle,
logical_size: u64,
capacity: u64,
element_stride: Option<u32>,
flags: BufferFlags,
) -> Result<BufferHandle> {
let allocation_size = tiles::align_reserved_cap(capacity.max(logical_size));
let num_tiles = tiles::num_tiles_for_bytes(allocation_size) as usize;
let initial_tiles = tiles::tiles_needed_for_logical_size(logical_size) as usize;
let resource_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: allocation_size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
};
let (resource, reserved_tiles) = {
let ld = state.devices.get(&device_handle).context("Invalid device handle")?;
let mut pool_guard = ld.tile_heap_pool.lock().unwrap();
let pool = pool_guard.as_mut().context("internal: tile heap pool missing")?;
let queue = ld.command_queue.clone();
let mut resource: Option<ID3D12Resource> = None;
unsafe {
ld.device
.CreateReservedResource(&resource_desc, D3D12_RESOURCE_STATE_COMMON, None, &mut resource)
}
.context("CreateReservedResource")?;
let resource = resource.context("CreateReservedResource returned null")?;
let mut slots: Vec<Option<(ID3D12Heap, u64)>> = vec![None; num_tiles];
let mut mappings = Vec::with_capacity(initial_tiles);
for (i, slot) in slots.iter_mut().enumerate().take(initial_tiles) {
let (heap, off) = pool.alloc_tile(&ld.device)?;
mappings.push((i as u32, heap.clone(), off));
*slot = Some((heap, off));
}
tiles::map_tiles_batched(&queue, &resource, &mappings)?;
(resource, slots)
};
let handle = state.buffers.write().unwrap().alloc_handle();
let stride = element_stride.unwrap_or(4);
debug_assert!(
stride > 0 && (logical_size as u32).is_multiple_of(stride),
"buffer logical size {logical_size} not evenly divisible by element stride {stride}"
);
let num_elements = (logical_size as u32) / stride;
let (bindless_offset, bindless_srv_offset) = {
let ld = state.devices.get(&device_handle).context("Invalid device handle")?;
let uav_offset = ld
.descriptors
.lock()
.unwrap()
.resource_registry
.register_buffer_uav(handle);
let uav_desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_UAV_DIMENSION_BUFFER,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_UAV {
FirstElement: 0,
NumElements: num_elements,
StructureByteStride: stride,
CounterOffsetInBytes: 0,
Flags: D3D12_BUFFER_UAV_FLAG_NONE,
},
},
};
let uav_cpu_handle = unsafe {
let mut cpu_handle = ld.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (uav_offset * ld.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
ld.device
.CreateUnorderedAccessView(&resource, None, Some(&uav_desc), uav_cpu_handle);
}
let srv_offset = ld
.descriptors
.lock()
.unwrap()
.resource_registry
.register_buffer_srv(handle);
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_SRV_DIMENSION_BUFFER,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_SRV {
FirstElement: 0,
NumElements: num_elements,
StructureByteStride: stride,
Flags: D3D12_BUFFER_SRV_FLAG_NONE,
},
},
};
let srv_cpu_handle = unsafe {
let mut cpu_handle = ld.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
cpu_handle.ptr += (srv_offset * ld.cbv_srv_uav_descriptor_size) as usize;
cpu_handle
};
unsafe {
ld.device
.CreateShaderResourceView(&resource, Some(&srv_desc), srv_cpu_handle);
}
(Some(uav_offset), Some(srv_offset))
};
state.buffers.write().unwrap().entries.insert(
handle,
BufferState {
device_handle,
resource,
size: logical_size,
allocation_size,
bindless_offset,
bindless_srv_offset,
is_storage: true,
upload_buffer: None,
element_stride,
is_view: false,
coherent_readback: None,
coherent_readback_mapped: None,
cpu_writable_upload_mapped: None,
flags,
transient_placed: false,
parent_for_view: None,
view_byte_offset: None,
is_reserved: true,
tile_byte_size: tiles::BUFFER_TILE_BYTES,
reserved_tiles,
is_withdraw_staging: false,
texture_copy_footprint: None,
},
);
Ok(handle)
}
pub(super) fn create_with_capacity(
state: &mut Dx12State,
device_handle: DeviceHandle,
initial_size: u64,
requested_capacity: u64,
access: BufferKind,
element_stride: Option<u32>,
flags: BufferFlags,
) -> Result<(BufferHandle, u64)> {
let cap = requested_capacity.max(initial_size);
let use_reserved = !super::env_disable_reserved_buffers()
&& state.devices.get(&device_handle).is_some_and(|d| {
d.supports_reserved_buffers
&& d.tile_heap_pool.lock().unwrap().is_some()
&& cap > initial_size
&& access == BufferKind::Scattered
&& !flags.contains(BufferFlags::CPU_READABLE)
});
if use_reserved {
let h = create_reserved_with_capacity(state, device_handle, initial_size, cap, element_stride, flags)?;
return Ok((h, capacity(state, h)));
}
let h = create(state, device_handle, initial_size, cap, access, element_stride, flags)?;
Ok((h, capacity(state, h)))
}
pub(super) fn capacity(state: &Dx12State, buffer_handle: BufferHandle) -> u64 {
state
.buffers
.read()
.unwrap()
.entries
.get(&buffer_handle)
.map(|b| b.allocation_size)
.unwrap_or(0)
}
pub(super) fn set_logical_size(
state: &mut Dx12State,
device_handle: DeviceHandle,
buffer_handle: BufferHandle,
new_logical_size: u64,
) -> Result<()> {
let old = {
let buffers_read = state.buffers.read().unwrap();
buffers_read
.entries
.get(&buffer_handle)
.cloned()
.context("set_logical_size: invalid buffer")?
};
if old.device_handle != device_handle {
anyhow::bail!("set_logical_size: buffer belongs to a different device");
}
if old.is_view {
anyhow::bail!("set_logical_size: cannot resize a buffer view");
}
if old.transient_placed {
anyhow::bail!("set_logical_size: cannot resize a transient placed buffer");
}
if new_logical_size > old.allocation_size {
anyhow::bail!("logical size exceeds allocation");
}
if new_logical_size == 0 {
anyhow::bail!("buffer size must be non-zero");
}
let stride = old.element_stride.unwrap_or(4);
if old.is_storage && stride > 0 && !(new_logical_size as u32).is_multiple_of(stride) {
anyhow::bail!("set_logical_size: new size {new_logical_size} not divisible by stride {stride}");
}
if !old.is_storage {
let aligned = (new_logical_size + 255) & !255;
if aligned > old.allocation_size {
anyhow::bail!("CBV aligned size exceeds allocation");
}
}
if old.is_reserved {
let old_pages = tiles::tiles_needed_for_logical_size(old.size);
let new_pages = tiles::tiles_needed_for_logical_size(new_logical_size);
{
let ld = state.devices.get(&device_handle).context("set_logical_size: device")?;
let mut pool_guard = ld.tile_heap_pool.lock().unwrap();
let pool = pool_guard.as_mut().context("set_logical_size: tile heap pool")?;
let queue = ld.command_queue.clone();
let mut buffers_write = state.buffers.write().unwrap();
let buf = buffers_write
.entries
.get_mut(&buffer_handle)
.expect("set_logical_size: buffer");
if new_pages > old_pages {
let mut mappings = Vec::with_capacity((new_pages - old_pages) as usize);
for i in old_pages..new_pages {
let (heap, off) = pool.alloc_tile(&ld.device)?;
mappings.push((i, heap.clone(), off));
buf.reserved_tiles[i as usize] = Some((heap, off));
}
tiles::map_tiles_batched(&queue, &buf.resource, &mappings)?;
} else if new_pages < old_pages {
let n = old_pages - new_pages;
tiles::unmap_tile_run(&queue, &buf.resource, new_pages, n)?;
for i in new_pages..old_pages {
if let Some((heap, off)) = buf.reserved_tiles.get_mut(i as usize).and_then(|s| s.take()) {
pool.free_tile(&heap, off);
}
}
}
}
let logical_device = state.devices.get(&device_handle).context("set_logical_size: device")?;
{
let buffers_read = state.buffers.read().unwrap();
let buf = buffers_read.entries.get(&buffer_handle).unwrap();
rewrite_root_buffer_descriptors(logical_device, &buf.resource, new_logical_size, buf)?;
}
state
.buffers
.write()
.unwrap()
.entries
.get_mut(&buffer_handle)
.unwrap()
.size = new_logical_size;
return Ok(());
}
let logical_device = state.devices.get(&device_handle).context("set_logical_size: device")?;
rewrite_root_buffer_descriptors(logical_device, &old.resource, new_logical_size, &old)?;
state
.buffers
.write()
.unwrap()
.entries
.get_mut(&buffer_handle)
.unwrap()
.size = new_logical_size;
Ok(())
}
pub(super) fn destroy(state: &mut Dx12State, buffer_handle: BufferHandle) {
let Some(buffer) = ({
let mut buffers = state.buffers.write().unwrap();
buffers.entries.remove(&buffer_handle)
}) else {
return;
};
let Some(device) = state.devices.get(&buffer.device_handle) else {
return;
};
let slots = device.descriptors.lock().unwrap().buffer_slot_keys(buffer_handle);
super::compute::evict_retained_graphs_using_slots(state, buffer.device_handle, &slots);
let ctx_h = super::context::destroy_attribution_context(state, buffer.device_handle);
let base = super::context::reclamation_requirements(state, buffer.device_handle, ctx_h);
let requirements = {
let registry = device.descriptors.lock().unwrap();
registry.bindless_retirement_requirements_for_buffer(buffer_handle, base)
};
if buffer.is_view {
let deletion = super::types::PendingDeletion::BufferView { buffer_handle };
queue_pending_deletion(device, requirements, deletion);
return;
}
if buffer.transient_placed {
device.descriptors.lock().unwrap().reclaim_buffer_slots(buffer_handle);
return;
}
if buffer.coherent_readback_mapped.is_some() {
let no_write = D3D12_RANGE { Begin: 0, End: 0 };
if buffer.is_withdraw_staging {
unsafe { buffer.resource.Unmap(0, Some(&no_write)) };
} else if let Some(ref rb) = buffer.coherent_readback {
unsafe { rb.Unmap(0, Some(&no_write)) };
}
}
let deletion = super::types::PendingDeletion::Buffer {
buffer_handle,
resource: buffer.resource,
upload_buffer: buffer.upload_buffer,
coherent_readback: buffer.coherent_readback,
reserved_tiles: if buffer.is_reserved {
Some(buffer.reserved_tiles)
} else {
None
},
};
queue_pending_deletion(device, requirements, deletion);
}
fn queue_pending_deletion(
device: &super::types::LogicalDevice,
requirements: Vec<(super::ContextHandle, u64)>,
deletion: super::types::PendingDeletion,
) {
device.deletion_queue.lock().unwrap().queue(requirements, deletion);
}
pub(super) fn hint_unused_above(state: &mut Dx12State, buffer_handle: BufferHandle, offset: u64) {
let (device_handle, first_tile) = {
let buffers_read = state.buffers.read().unwrap();
let Some(buf) = buffers_read.entries.get(&buffer_handle) else {
return;
};
if !buf.is_reserved {
return;
}
let tile = u64::from(buf.tile_byte_size);
if tile == 0 {
return;
}
let ft = ((offset.saturating_add(tile.saturating_sub(1))) / tile) as usize;
if ft >= buf.reserved_tiles.len() {
return;
}
(buf.device_handle, ft)
};
let Some(ld) = state.devices.get(&device_handle) else {
return;
};
let mut pool_guard = ld.tile_heap_pool.lock().unwrap();
let Some(pool) = pool_guard.as_mut() else {
return;
};
let queue = &ld.command_queue;
let mut buffers_write = state.buffers.write().unwrap();
let Some(buf_mut) = buffers_write.entries.get_mut(&buffer_handle) else {
return;
};
let mut i = first_tile;
while i < buf_mut.reserved_tiles.len() {
while i < buf_mut.reserved_tiles.len() && buf_mut.reserved_tiles[i].is_none() {
i += 1;
}
if i >= buf_mut.reserved_tiles.len() {
break;
}
let run_start = i;
while i < buf_mut.reserved_tiles.len() && buf_mut.reserved_tiles[i].is_some() {
i += 1;
}
let n = (i - run_start) as u32;
let _ = tiles::unmap_tile_run(queue, &buf_mut.resource, run_start as u32, n);
for j in run_start..i {
if let Some((heap, off)) = buf_mut.reserved_tiles[j].take() {
pool.free_tile(&heap, off);
}
}
}
}
pub(super) fn create_view(
state: &mut Dx12State,
parent_handle: BufferHandle,
offset: u64,
size: u64,
element_stride: Option<u32>,
) -> Result<BufferHandle> {
let (device_handle, resource, parent_flags, parent_allocation_size) = {
let buffers_read = state.buffers.read().unwrap();
let parent = buffers_read
.entries
.get(&parent_handle)
.context("Invalid parent buffer handle")?;
if offset + size > parent.size {
anyhow::bail!(
"View [{}, {}) exceeds parent buffer size {}",
offset,
offset + size,
parent.size
);
}
if !parent.is_storage {
anyhow::bail!("Buffer views are only supported for storage (Scattered) buffers");
}
(
parent.device_handle,
parent.resource.clone(),
parent.flags,
parent.allocation_size,
)
};
let stride = element_stride.unwrap_or(4);
if stride == 0 {
anyhow::bail!("Buffer view element stride must be non-zero");
}
if !(size as u32).is_multiple_of(stride) {
anyhow::bail!("View byte size {size} is not evenly divisible by element stride {stride}");
}
if !offset.is_multiple_of(stride as u64) {
anyhow::bail!("View offset {} is not aligned to element stride {}", offset, stride);
}
let first_element = (offset / stride as u64) as u32;
let num_elements = (size as u32) / stride;
let handle = state.buffers.write().unwrap().alloc_handle();
let (bindless_offset, bindless_srv_offset) = {
let logical_device = state.devices.get(&device_handle).context("Invalid device handle")?;
if num_elements == 0 {
(None, None)
} else {
let uav_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_buffer_uav(handle);
let uav_desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_UAV_DIMENSION_BUFFER,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_UAV {
FirstElement: first_element as u64,
NumElements: num_elements,
StructureByteStride: stride,
CounterOffsetInBytes: 0,
Flags: D3D12_BUFFER_UAV_FLAG_NONE,
},
},
};
let uav_cpu_handle = unsafe {
let mut h = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
h.ptr += (uav_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
h
};
unsafe {
logical_device
.device
.CreateUnorderedAccessView(&resource, None, Some(&uav_desc), uav_cpu_handle);
}
let srv_offset = logical_device
.descriptors
.lock()
.unwrap()
.resource_registry
.register_buffer_srv(handle);
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_UNKNOWN,
ViewDimension: D3D12_SRV_DIMENSION_BUFFER,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Buffer: D3D12_BUFFER_SRV {
FirstElement: first_element as u64,
NumElements: num_elements,
StructureByteStride: stride,
Flags: D3D12_BUFFER_SRV_FLAG_NONE,
},
},
};
let srv_cpu_handle = unsafe {
let mut h = logical_device.cbv_srv_uav_heap.GetCPUDescriptorHandleForHeapStart();
h.ptr += (srv_offset * logical_device.cbv_srv_uav_descriptor_size) as usize;
h
};
unsafe {
logical_device
.device
.CreateShaderResourceView(&resource, Some(&srv_desc), srv_cpu_handle);
}
tracing::debug!(
"Created buffer view {} (UAV={}, SRV={}) into parent {} (offset={}, size={})",
handle,
uav_offset,
srv_offset,
parent_handle,
offset,
size
);
(Some(uav_offset), Some(srv_offset))
}
};
state.buffers.write().unwrap().entries.insert(
handle,
BufferState {
device_handle,
resource,
size,
allocation_size: parent_allocation_size,
bindless_offset,
bindless_srv_offset,
is_storage: true,
upload_buffer: None,
element_stride,
is_view: true,
coherent_readback: None,
coherent_readback_mapped: None,
cpu_writable_upload_mapped: None,
flags: parent_flags,
transient_placed: false,
parent_for_view: Some(parent_handle),
view_byte_offset: Some(offset),
is_reserved: false,
tile_byte_size: 0,
reserved_tiles: Vec::new(),
is_withdraw_staging: false,
texture_copy_footprint: None,
},
);
Ok(handle)
}
const UPLOAD_CHUNK_SIZE: u64 = 16 * 1024 * 1024;
pub(super) const ZERO_BUFFER_SIZE: u64 = UPLOAD_CHUNK_SIZE;
pub(super) fn ensure_upload_buffer(state: &mut Dx12State, buffer_handle: BufferHandle, min_size: u64) -> Result<()> {
let device_handle = {
let buffers_read = state.buffers.read().unwrap();
let buffer = buffers_read
.entries
.get(&buffer_handle)
.context("ensure_upload_buffer: invalid handle")?;
if buffer.upload_buffer.is_some() {
return Ok(());
}
buffer.device_handle
};
let chunk_size = min_size.min(UPLOAD_CHUNK_SIZE);
let logical_device = state
.devices
.get(&device_handle)
.context("ensure_upload_buffer: invalid device")?;
let upload_heap = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_UPLOAD,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let upload_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: chunk_size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut upload: Option<ID3D12Resource> = None;
unsafe {
logical_device.device.CreateCommittedResource(
&upload_heap,
D3D12_HEAP_FLAG_NONE,
&upload_desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
None,
&mut upload,
)
}
.context("ensure_upload_buffer: create failed")?;
state
.buffers
.write()
.unwrap()
.entries
.get_mut(&buffer_handle)
.unwrap()
.upload_buffer = Some(upload.context("Upload buffer is null")?);
Ok(())
}
pub(super) fn cpu_writable_flat_slice(
buffers: &std::collections::HashMap<BufferHandle, super::types::BufferState>,
buffer_handle: BufferHandle,
offset: u64,
len: usize,
) -> Result<&[u8]> {
let buffer = buffers
.get(&buffer_handle)
.context("cpu_writable_flat_slice: invalid buffer handle")?;
if !buffer.flags.contains(BufferFlags::CPU_WRITABLE) {
anyhow::bail!("cpu_writable_flat_slice: buffer is not CPU_WRITABLE");
}
if offset + len as u64 > buffer.size {
anyhow::bail!("cpu_writable_flat_slice: slice exceeds buffer bounds");
}
let base = buffer
.cpu_writable_upload_mapped
.context("cpu_writable_flat_slice: missing upload mapping")?;
Ok(unsafe { std::slice::from_raw_parts((base as *const u8).add(offset as usize), len) })
}
pub(super) fn write(state: &mut Dx12State, buffer_handle: BufferHandle, offset: u64, data: &[u8]) -> Result<()> {
if data.is_empty() {
return Ok(());
}
let buffers_read = state.buffers.read().unwrap();
let buffer = buffers_read
.entries
.get(&buffer_handle)
.context("Invalid buffer handle")?;
if offset + data.len() as u64 > buffer.size {
anyhow::bail!("Write would exceed buffer bounds");
}
if buffer.is_storage {
if let Some(stride) = buffer.element_stride {
if stride > 0 && !(data.len() as u32).is_multiple_of(stride) {
tracing::warn!(
"write of {} bytes to buffer (handle={}) with element stride {} \
— data length is not a multiple of stride, possible type mismatch",
data.len(),
buffer_handle,
stride,
);
}
}
}
if buffer.flags.contains(BufferFlags::CPU_WRITABLE) {
if let Some(base) = buffer.cpu_writable_upload_mapped {
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), (base as *mut u8).add(offset as usize), data.len());
}
return Ok(());
}
}
if !buffer.is_storage {
let mut mapped_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
let read_range = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { buffer.resource.Map(0, Some(&read_range), Some(&mut mapped_ptr)) }.context("Failed to map buffer")?;
unsafe {
std::ptr::copy_nonoverlapping(data.as_ptr(), (mapped_ptr as *mut u8).add(offset as usize), data.len());
}
let written_range = D3D12_RANGE {
Begin: offset as usize,
End: (offset as usize) + data.len(),
};
unsafe { buffer.resource.Unmap(0, Some(&written_range)) };
return Ok(());
}
let data_len = data.len() as u64;
let chunk_size = data_len.min(UPLOAD_CHUNK_SIZE);
let device_handle = buffer.device_handle;
let main_resource = buffer.resource.clone();
let needs_upload_buffer = buffer.upload_buffer.is_none();
drop(buffers_read);
if needs_upload_buffer {
ensure_upload_buffer(state, buffer_handle, chunk_size)?;
}
let upload_buf = state
.buffers
.read()
.unwrap()
.entries
.get(&buffer_handle)
.unwrap()
.upload_buffer
.as_ref()
.unwrap()
.clone();
let upload_buf_size = chunk_size;
let mut written = 0u64;
while written < data_len {
let this_chunk = (data_len - written).min(upload_buf_size);
let src_slice = &data[(written as usize)..((written + this_chunk) as usize)];
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { upload_buf.Map(0, Some(&no_read), Some(&mut mapped)) }.context("Failed to map upload buffer")?;
unsafe {
std::ptr::copy_nonoverlapping(src_slice.as_ptr(), mapped as *mut u8, this_chunk as usize);
}
let write_range = D3D12_RANGE {
Begin: 0,
End: this_chunk as usize,
};
unsafe { upload_buf.Unmap(0, Some(&write_range)) };
let device = state.devices.get(&device_handle).context("Invalid device handle")?;
let alloc: ID3D12CommandAllocator =
unsafe { device.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }
.context("Failed to create command allocator")?;
let cmd: ID3D12GraphicsCommandList = unsafe {
device
.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.context("Failed to create command list")?;
let cmd7: ID3D12GraphicsCommandList7 = cmd.cast().context("ID3D12GraphicsCommandList7")?;
let dst_offset = offset + written;
let mut b_pre = [barriers::buffer_barrier_full(
&main_resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_ACCESS_COMMON,
D3D12_BARRIER_ACCESS_COPY_DEST,
)];
unsafe {
barriers::barrier_buffers(&cmd7, &b_pre);
barriers::drop_buffer_barriers(&mut b_pre);
cmd.CopyBufferRegion(&main_resource, dst_offset, &upload_buf, 0, this_chunk);
}
let mut b_post = [barriers::buffer_barrier_full(
&main_resource,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_ACCESS_COPY_DEST,
D3D12_BARRIER_ACCESS_COMMON,
)];
unsafe {
barriers::barrier_buffers(&cmd7, &b_post);
barriers::drop_buffer_barriers(&mut b_post);
}
unsafe { cmd.Close() }.context("Failed to close command list")?;
let lists: [Option<ID3D12CommandList>; 1] = [Some(cmd.cast()?)];
let fence_value = super::utils::execute_command_lists_and_signal_device(device, &lists)?;
super::utils::wait_for_fence(&device.fence, fence_value)?;
written += this_chunk;
}
Ok(())
}
pub(super) fn size(state: &Dx12State, buffer_handle: BufferHandle) -> u64 {
state
.buffers
.read()
.unwrap()
.entries
.get(&buffer_handle)
.map(|b| b.size)
.unwrap_or(0)
}
pub(super) fn bindless_index(state: &Dx12State, buffer_handle: BufferHandle) -> Option<u32> {
state
.buffers
.read()
.unwrap()
.entries
.get(&buffer_handle)
.and_then(|b| b.bindless_offset)
}
pub(super) fn bindless_slot_kind_for_index(
buffers: &std::collections::HashMap<super::BufferHandle, super::types::BufferState>,
device_handle: super::DeviceHandle,
index: u32,
) -> Option<crate::types::BindlessSlotKind> {
use crate::types::BindlessSlotKind;
for b in buffers.values() {
if b.device_handle != device_handle {
continue;
}
if b.bindless_srv_offset == Some(index) {
return Some(BindlessSlotKind::ReadOnlySrv);
}
if b.bindless_offset == Some(index) {
return Some(if b.is_storage {
BindlessSlotKind::StorageUav
} else {
BindlessSlotKind::UniformCbv
});
}
}
None
}
pub(super) fn bindless_srv_index(state: &Dx12State, buffer_handle: BufferHandle) -> Option<u32> {
state
.buffers
.read()
.unwrap()
.entries
.get(&buffer_handle)
.and_then(|b| b.bindless_srv_offset.or(b.bindless_offset))
}
pub(super) fn clear(
state: &mut Dx12State,
device_handle: DeviceHandle,
buffer_handle: BufferHandle,
offset: u64,
size: u64,
) -> Result<()> {
let buffers_read = state.buffers.read().unwrap();
let buffer = buffers_read
.entries
.get(&buffer_handle)
.context("Invalid buffer handle")?;
let clear_size = super::super::shared::resolve_clear_size(buffer.size, offset, size);
if offset + clear_size > buffer.size {
anyhow::bail!("Clear would exceed buffer bounds");
}
if clear_size == 0 {
return Ok(());
}
if buffer.is_storage {
let device = state.devices.get(&device_handle).context("Invalid device handle")?;
let copy_allocator: ID3D12CommandAllocator =
unsafe { device.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }
.context("Failed to create command allocator")?;
let cmd_list: ID3D12GraphicsCommandList = unsafe {
device
.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, ©_allocator, None)
}
.context("Failed to create command list")?;
let cmd_list7: ID3D12GraphicsCommandList7 = cmd_list.cast().context("ID3D12GraphicsCommandList7")?;
let buf_resource = buffer.resource.clone();
let zero = device.zero_buffer.clone();
let mut b_pre = [barriers::buffer_barrier_full(
&buf_resource,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_ACCESS_COMMON,
D3D12_BARRIER_ACCESS_COPY_DEST,
)];
unsafe {
barriers::barrier_buffers(&cmd_list7, &b_pre);
barriers::drop_buffer_barriers(&mut b_pre);
}
let mut written = 0u64;
while written < clear_size {
let this_chunk = (clear_size - written).min(ZERO_BUFFER_SIZE);
unsafe {
cmd_list.CopyBufferRegion(&buf_resource, offset + written, &zero, 0, this_chunk);
}
written += this_chunk;
}
let mut b_post = [barriers::buffer_barrier_full(
&buf_resource,
D3D12_BARRIER_SYNC_COPY,
D3D12_BARRIER_SYNC_ALL,
D3D12_BARRIER_ACCESS_COPY_DEST,
D3D12_BARRIER_ACCESS_COMMON,
)];
unsafe {
barriers::barrier_buffers(&cmd_list7, &b_post);
barriers::drop_buffer_barriers(&mut b_post);
}
unsafe { cmd_list.Close() }.context("Failed to close command list")?;
let lists: [Option<ID3D12CommandList>; 1] = [Some(cmd_list.cast().context("Failed to cast command list")?)];
let fence_value = super::utils::execute_command_lists_and_signal_device(device, &lists)?;
super::utils::wait_for_fence(&device.fence, fence_value)?;
let removed_reason = unsafe { device.device.GetDeviceRemovedReason() };
if removed_reason.is_err() {
anyhow::bail!("Device removed during buffer clear: {:?}", removed_reason);
}
} else {
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { buffer.resource.Map(0, Some(&no_read), Some(&mut mapped)) }.context("Failed to map buffer")?;
unsafe {
std::ptr::write_bytes((mapped as *mut u8).add(offset as usize), 0, clear_size as usize);
}
let written = D3D12_RANGE {
Begin: offset as usize,
End: (offset + clear_size) as usize,
};
unsafe { buffer.resource.Unmap(0, Some(&written)) };
}
Ok(())
}
pub(super) fn alloc_readback_buffer(
state: &mut Dx12State,
device_handle: DeviceHandle,
size: u64,
) -> Result<BufferHandle> {
use windows::Win32::Graphics::{Direct3D12::*, Dxgi::Common::*};
let device = state.devices.get(&device_handle).context("Invalid device handle")?;
let readback_heap = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_READBACK,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let readback_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: size,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut resource: Option<ID3D12Resource> = None;
unsafe {
device.device.CreateCommittedResource(
&readback_heap,
D3D12_HEAP_FLAG_NONE,
&readback_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
None,
&mut resource,
)
}
.context("Failed to create withdraw staging buffer")?;
let resource = resource.context("CreateCommittedResource readback returned null")?;
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { resource.Map(0, Some(&no_read), Some(&mut mapped)) }.context("Failed to map withdraw staging buffer")?;
let mapped_addr = mapped as usize;
let handle = state.buffers.write().unwrap().alloc_handle();
state.buffers.write().unwrap().entries.insert(
handle,
BufferState {
device_handle,
resource,
size,
allocation_size: size,
bindless_offset: None,
bindless_srv_offset: None,
is_storage: false,
upload_buffer: None,
element_stride: None,
is_view: false,
coherent_readback: None,
coherent_readback_mapped: Some(mapped_addr),
cpu_writable_upload_mapped: None,
flags: BufferFlags::empty(),
transient_placed: false,
parent_for_view: None,
view_byte_offset: None,
is_reserved: false,
tile_byte_size: 0,
reserved_tiles: Vec::new(),
is_withdraw_staging: true,
texture_copy_footprint: None,
},
);
Ok(handle)
}
pub(super) fn alloc_texture_readback_staging(
state: &mut Dx12State,
device_handle: DeviceHandle,
layout: crate::backend::TextureCopyFootprint,
) -> Result<BufferHandle> {
use windows::Win32::Graphics::{Direct3D12::*, Dxgi::Common::*};
let device = state.devices.get(&device_handle).context("Invalid device handle")?;
let readback_heap = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_READBACK,
CPUPageProperty: D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
MemoryPoolPreference: D3D12_MEMORY_POOL_UNKNOWN,
CreationNodeMask: 0,
VisibleNodeMask: 0,
};
let readback_desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_BUFFER,
Alignment: 0,
Width: layout.staging_bytes,
Height: 1,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Layout: D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
Flags: D3D12_RESOURCE_FLAG_NONE,
};
let mut resource: Option<ID3D12Resource> = None;
unsafe {
device.device.CreateCommittedResource(
&readback_heap,
D3D12_HEAP_FLAG_NONE,
&readback_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
None,
&mut resource,
)
}
.context("Failed to create texture withdraw staging buffer")?;
let resource = resource.context("CreateCommittedResource texture readback returned null")?;
let mut mapped: *mut std::ffi::c_void = std::ptr::null_mut();
let no_read = D3D12_RANGE { Begin: 0, End: 0 };
unsafe { resource.Map(0, Some(&no_read), Some(&mut mapped)) }
.context("Failed to map texture withdraw staging buffer")?;
let mapped_addr = mapped as usize;
let handle = state.buffers.write().unwrap().alloc_handle();
state.buffers.write().unwrap().entries.insert(
handle,
BufferState {
device_handle,
resource,
size: layout.staging_bytes,
allocation_size: layout.staging_bytes,
bindless_offset: None,
bindless_srv_offset: None,
is_storage: false,
upload_buffer: None,
element_stride: None,
is_view: false,
coherent_readback: None,
coherent_readback_mapped: Some(mapped_addr),
cpu_writable_upload_mapped: None,
flags: BufferFlags::empty(),
transient_placed: false,
parent_for_view: None,
view_byte_offset: None,
is_reserved: false,
tile_byte_size: 0,
reserved_tiles: Vec::new(),
is_withdraw_staging: true,
texture_copy_footprint: Some(layout),
},
);
Ok(handle)
}
pub(super) fn read_texture_readback_staging(
buffers: &std::collections::HashMap<BufferHandle, BufferState>,
buffer_handle: BufferHandle,
layout: crate::backend::TextureCopyFootprint,
output: &mut [u8],
) -> Result<()> {
let buffer = buffers.get(&buffer_handle).context("Invalid buffer handle")?;
if !buffer.is_withdraw_staging {
anyhow::bail!("read_texture_readback_staging requires a withdraw staging buffer");
}
if output.len() as u64 != layout.logical_bytes {
anyhow::bail!("read_texture_readback_staging size mismatch");
}
let base = buffer
.coherent_readback_mapped
.context("texture withdraw staging buffer not mapped")?;
let row_bytes = layout.tight_row_bytes() as usize;
let pitch = layout.row_pitch as usize;
let p = base as *const u8;
for row in 0..layout.height as usize {
let src_offset = layout.footprint_offset as usize + row * pitch;
let dst_offset = row * row_bytes;
unsafe {
std::ptr::copy_nonoverlapping(p.add(src_offset), output.as_mut_ptr().add(dst_offset), row_bytes);
}
}
Ok(())
}
pub(super) fn read_readback_buffer(
buffers: &std::collections::HashMap<BufferHandle, BufferState>,
buffer_handle: BufferHandle,
output: &mut [u8],
) -> Result<()> {
let Some(buffer) = buffers.get(&buffer_handle) else {
anyhow::bail!("Invalid buffer handle");
};
if !buffer.is_withdraw_staging {
anyhow::bail!("read_readback_buffer requires a withdraw staging buffer");
}
let base = buffer
.coherent_readback_mapped
.context("withdraw staging buffer not mapped")?;
if output.len() as u64 > buffer.size {
anyhow::bail!("read_readback_buffer would exceed buffer bounds");
}
let p = base as *const u8;
unsafe {
std::ptr::copy_nonoverlapping(p, output.as_mut_ptr(), output.len());
}
Ok(())
}