use concinnity_core::gfx::mesh_payload::{SkinnedVertex, Vertex};
use concinnity_core::render::backend;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::geometry_repack;
use concinnity_core::render::rt_geom;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_R32_UINT;
use super::super::com;
use super::super::context::DxContext;
use super::super::texture::{one_shot_submit, transition_barrier};
use crate::directx::error::map_hresult;
impl DxContext {
pub(crate) fn rebuild_static_geometry(
&mut self,
changes: Vec<backend::DrawGeometryUpdate>,
) -> RenderResult<()> {
self.wait_idle();
let old_v_bytes = self.scene.geometry.vertex_buffer_view.SizeInBytes as u64;
let old_i_bytes = self.scene.geometry.index_buffer_view.SizeInBytes as u64;
let v_readback = self.hw.alloc.alloc_buffer(
old_v_bytes,
D3D12_HEAP_TYPE_READBACK,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
let i_readback = self.hw.alloc.alloc_buffer(
old_i_bytes,
D3D12_HEAP_TYPE_READBACK,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
one_shot_submit(&self.hw.device, &self.hw.command_queue, |cmd| unsafe {
let v_src = transition_barrier(
&self.scene.geometry.vertex_buffer,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
D3D12_RESOURCE_STATE_COPY_SOURCE,
);
let i_src = transition_barrier(
&self.scene.geometry.index_buffer,
D3D12_RESOURCE_STATE_INDEX_BUFFER,
D3D12_RESOURCE_STATE_COPY_SOURCE,
);
cmd.ResourceBarrier(&[v_src, i_src]);
cmd.CopyBufferRegion(
&*v_readback,
0,
&*self.scene.geometry.vertex_buffer,
0,
old_v_bytes,
);
cmd.CopyBufferRegion(
&*i_readback,
0,
&*self.scene.geometry.index_buffer,
0,
old_i_bytes,
);
let v_back = transition_barrier(
&self.scene.geometry.vertex_buffer,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
);
let i_back = transition_barrier(
&self.scene.geometry.index_buffer,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_INDEX_BUFFER,
);
cmd.ResourceBarrier(&[v_back, i_back]);
})?;
let old_v_count = (old_v_bytes as usize) / std::mem::size_of::<Vertex>();
let old_i_count = (old_i_bytes as usize) / std::mem::size_of::<u32>();
let old_vertices: Vec<Vertex> = read_typed_vec(&v_readback, old_v_count)?;
let old_indices: Vec<u32> = read_typed_vec(&i_readback, old_i_count)?;
let repacked = geometry_repack::repack_static_geometry(
&self.state.draw.objects,
&old_vertices,
&old_indices,
changes,
)?;
if repacked.ignored_changes > 0 {
tracing::warn!(
"rebuild_static_geometry: {} change(s) targeted draw indices not in \
draw_objects (ignored)",
repacked.ignored_changes
);
}
let new_vertices = repacked.vertices;
let new_indices = repacked.indices;
let new_v_bytes = std::mem::size_of_val(new_vertices.as_slice()) as u64;
let new_i_bytes = std::mem::size_of_val(new_indices.as_slice()) as u64;
let new_vbuf = self.hw.alloc.alloc_buffer(
new_v_bytes,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COMMON,
)?;
let new_ibuf = self.hw.alloc.alloc_buffer(
new_i_bytes,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COMMON,
)?;
let v_upload = self.hw.alloc.alloc_buffer(
new_v_bytes,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let i_upload = self.hw.alloc.alloc_buffer(
new_i_bytes,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
write_upload_buffer(&v_upload, bytemuck::cast_slice(&new_vertices))?;
write_upload_buffer(&i_upload, bytemuck::cast_slice(&new_indices))?;
one_shot_submit(&self.hw.device, &self.hw.command_queue, |cmd| unsafe {
cmd.CopyBufferRegion(&*new_vbuf, 0, &*v_upload, 0, new_v_bytes);
cmd.CopyBufferRegion(&*new_ibuf, 0, &*i_upload, 0, new_i_bytes);
let v_dst = transition_barrier(
&new_vbuf,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
);
let i_dst = transition_barrier(
&new_ibuf,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_INDEX_BUFFER,
);
cmd.ResourceBarrier(&[v_dst, i_dst]);
})?;
for (layout, obj) in repacked
.layouts
.into_iter()
.zip(&mut self.state.draw.objects)
{
layout.apply_to(obj);
}
self.scene.geometry.vertex_buffer_view = D3D12_VERTEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&new_vbuf),
SizeInBytes: new_v_bytes as u32,
StrideInBytes: std::mem::size_of::<Vertex>() as u32,
};
self.scene.geometry.index_buffer_view = D3D12_INDEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&new_ibuf),
SizeInBytes: new_i_bytes as u32,
Format: DXGI_FORMAT_R32_UINT,
};
self.scene.geometry.vertex_buffer = new_vbuf;
self.scene.geometry.index_buffer = new_ibuf;
if self.rt.accel.is_some() {
self.rebuild_rt_accel();
}
Ok(())
}
pub(crate) fn rebuild_skinned_geometry(
&mut self,
changes: Vec<backend::SkinnedDrawGeometryUpdate>,
) -> RenderResult<Vec<backend::SkinnedSlotLayout>> {
let v_buf = self.skinned.vertex_buffer.clone().ok_or_else(|| {
RenderError::Other(
"rebuild_skinned_geometry: no skinned vertex buffer (was upload_skinned called?)"
.into(),
)
})?;
let i_buf = self.skinned.index_buffer.clone().ok_or_else(|| {
RenderError::Other(
"rebuild_skinned_geometry: no skinned index buffer (was upload_skinned called?)"
.into(),
)
})?;
self.wait_idle();
let old_v_bytes = self.skinned.vertex_buffer_view.SizeInBytes as u64;
let old_i_bytes = self.skinned.index_buffer_view.SizeInBytes as u64;
let v_readback = self.hw.alloc.alloc_buffer(
old_v_bytes,
D3D12_HEAP_TYPE_READBACK,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
let i_readback = self.hw.alloc.alloc_buffer(
old_i_bytes,
D3D12_HEAP_TYPE_READBACK,
D3D12_RESOURCE_STATE_COPY_DEST,
)?;
one_shot_submit(&self.hw.device, &self.hw.command_queue, |cmd| unsafe {
let v_src = transition_barrier(
&v_buf,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
D3D12_RESOURCE_STATE_COPY_SOURCE,
);
let i_src = transition_barrier(
&i_buf,
D3D12_RESOURCE_STATE_INDEX_BUFFER,
D3D12_RESOURCE_STATE_COPY_SOURCE,
);
cmd.ResourceBarrier(&[v_src, i_src]);
cmd.CopyBufferRegion(&*v_readback, 0, &*v_buf, 0, old_v_bytes);
cmd.CopyBufferRegion(&*i_readback, 0, &*i_buf, 0, old_i_bytes);
let v_back = transition_barrier(
&v_buf,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
);
let i_back = transition_barrier(
&i_buf,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_INDEX_BUFFER,
);
cmd.ResourceBarrier(&[v_back, i_back]);
})?;
let old_v_count = (old_v_bytes as usize) / std::mem::size_of::<SkinnedVertex>();
let old_i_count = (old_i_bytes as usize) / std::mem::size_of::<u32>();
let old_vertices: Vec<SkinnedVertex> = read_typed_vec(&v_readback, old_v_count)?;
let old_indices: Vec<u32> = read_typed_vec(&i_readback, old_i_count)?;
let repacked = geometry_repack::repack_skinned_geometry(
&self.state.skinned.draw_objects,
&old_vertices,
&old_indices,
changes,
)?;
if repacked.ignored_changes > 0 {
tracing::warn!(
"rebuild_skinned_geometry: {} change(s) targeted skinned indices not \
in skinned_draw_objects (ignored)",
repacked.ignored_changes
);
}
let new_vertices = &repacked.vertices;
let new_indices = &repacked.indices;
let new_v_bytes = std::mem::size_of_val(new_vertices.as_slice()) as u64;
let new_i_bytes = std::mem::size_of_val(new_indices.as_slice()) as u64;
let ibuf_bytes = rt_geom::skinned_index_buffer_bytes(new_indices.len()) as u64;
let new_vbuf = self.hw.alloc.alloc_buffer(
new_v_bytes,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COMMON,
)?;
let new_ibuf = self.hw.alloc.alloc_buffer(
ibuf_bytes,
D3D12_HEAP_TYPE_DEFAULT,
D3D12_RESOURCE_STATE_COMMON,
)?;
let v_upload = self.hw.alloc.alloc_buffer(
new_v_bytes,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let i_upload = self.hw.alloc.alloc_buffer(
ibuf_bytes,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
write_upload_buffer(&v_upload, bytemuck::cast_slice(new_vertices))?;
write_upload_buffer(&i_upload, bytemuck::cast_slice(new_indices))?;
one_shot_submit(&self.hw.device, &self.hw.command_queue, |cmd| unsafe {
cmd.CopyBufferRegion(&*new_vbuf, 0, &*v_upload, 0, new_v_bytes);
cmd.CopyBufferRegion(&*new_ibuf, 0, &*i_upload, 0, new_i_bytes);
let v_dst = transition_barrier(
&new_vbuf,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
);
let i_dst = transition_barrier(
&new_ibuf,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_INDEX_BUFFER,
);
cmd.ResourceBarrier(&[v_dst, i_dst]);
})?;
repacked.apply_to(&mut self.state.skinned.draw_objects);
self.skinned.vertex_buffer_view = D3D12_VERTEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&new_vbuf),
SizeInBytes: new_v_bytes as u32,
StrideInBytes: std::mem::size_of::<SkinnedVertex>() as u32,
};
self.skinned.index_buffer_view = D3D12_INDEX_BUFFER_VIEW {
BufferLocation: com::gpu_va(&new_ibuf),
SizeInBytes: new_i_bytes as u32,
Format: DXGI_FORMAT_R32_UINT,
};
self.skinned.vertex_buffer = Some(new_vbuf);
self.skinned.index_buffer = Some(new_ibuf);
Ok(repacked.layouts())
}
}
fn buffer_bytes(res: &ID3D12Resource) -> u64 {
unsafe { res.GetDesc() }.Width
}
fn read_typed_vec<T: Copy>(src: &ID3D12Resource, count: usize) -> RenderResult<Vec<T>> {
let want = (count * std::mem::size_of::<T>()) as u64;
let have = buffer_bytes(src);
assert!(
want <= have,
"read_typed_vec: {want} bytes exceeds readback buffer {have}"
);
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { src.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "rebuild readback map"))?;
let mut out: Vec<T> = Vec::with_capacity(count);
unsafe {
std::ptr::copy_nonoverlapping(ptr as *const T, out.as_mut_ptr(), count);
out.set_len(count);
src.Unmap(0, None);
}
Ok(out)
}
fn write_upload_buffer(dest: &ID3D12Resource, bytes: &[u8]) -> RenderResult<()> {
let have = buffer_bytes(dest);
assert!(
bytes.len() as u64 <= have,
"write_upload_buffer: {} bytes exceeds upload buffer {have}",
bytes.len()
);
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { dest.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "rebuild upload map"))?;
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr as *mut u8, bytes.len());
dest.Unmap(0, None);
}
Ok(())
}