use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::gfx::render_types;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::reflection_probe::{
self, BakeAction, BakePhase, BakeSignals, PrefilterPlan,
};
use windows::Win32::Graphics::Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::com;
use super::context::{DxContext, FRAMES};
use super::error::map_hresult;
use super::probe_prefilter::PrefilterGpu;
use super::texture::{
HDR_FORMAT, create_hdr_color_target, create_hdr_resolve_target, transition_barrier,
};
use crate::directx::descriptor_slot::DescriptorTables;
use crate::directx::descriptor_slot::SrvSlot;
pub(super) const PLAN: PrefilterPlan = PrefilterPlan::RUNTIME;
const PROBE_FACE_SIZE: u32 = PLAN.face_size();
const PROBE_FACE_COUNT: usize = 6;
pub(in crate::directx) struct RenderingBake {
index: usize,
cursor: usize,
eye: [f32; 3],
near: f32,
far: f32,
sample_count: u32,
color: ID3D12Resource,
_depth: ID3D12Resource,
resolve: Option<ID3D12Resource>,
_rtv_heap: ID3D12DescriptorHeap,
_dsv_heap: ID3D12DescriptorHeap,
rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
_view_cbvs: Vec<PooledBuffer>,
view_gvas: Vec<u64>,
light_gva: u64,
shadow_gva: u64,
_light_cbv: PooledBuffer,
_shadow_cbv: PooledBuffer,
prefilter: PrefilterGpu,
cmd_allocs: Vec<ID3D12CommandAllocator>,
cmd_lists: Vec<ID3D12GraphicsCommandList>,
last_fence_value: u64,
}
pub(in crate::directx) struct PrefilteringBake {
index: usize,
gpu: PrefilterGpu,
cursor: u32,
cmd_allocs: Vec<ID3D12CommandAllocator>,
cmd_lists: Vec<ID3D12GraphicsCommandList>,
last_fence_value: u64,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct FaceTargets {
pub rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct FaceUniforms {
pub view_gva: u64,
pub light_gva: u64,
pub shadow_ubo_gva: u64,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct IndirectDraw<'a> {
pub indirect: &'a ID3D12Resource,
pub indirect_offset: u32,
pub object_gva: u64,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct FaceExtent {
pub width: u32,
pub height: u32,
}
impl DxContext {
pub(super) fn set_reflection_probes(&mut self, declared: &[reflection_probe::ProbePlacement]) {
let placements = reflection_probe::resolve_placements(
declared,
self.draw.objects.iter().map(|o| (o.bb_min, o.bb_max)),
);
self.abandon_in_flight_bakes();
let placements = match self.reserve_probe_cubes(&PLAN, placements.len()) {
Ok(()) => placements,
Err(e) => {
tracing::warn!("reflection probes: {e}; keeping the sky");
Vec::new()
}
};
self.probe.book.reset(placements);
}
fn bake_ring_slot(&self) -> usize {
FRAMES
}
pub(in crate::directx) fn probe_cube_table_gpu(&self) -> SrvSlot {
SrvSlot::at(
&self.descriptors.srv_heap,
self.descriptors.srv_descriptor_size,
self.descriptors.layout.probe_cubes_srv_slot,
)
}
fn probe_capture_supported(&self) -> bool {
self.cull.main_bindless_pso.is_some()
&& self.cull.cull_pso.is_some()
&& self.cull.object_buffer_resources.len() > FRAMES
&& self.cull.draw_args_buffer_resources.len() > FRAMES
&& self.cull.indirect_cmd_buffers.len() > FRAMES
}
pub(super) fn bake_pending_probes(&mut self, near: f32, far: f32) -> RenderResult<()> {
if !self.probe.book.pending()
&& self.probe.rendering.is_none()
&& self.probe.prefiltering.is_none()
{
return Ok(());
}
if !self.probe_capture_supported() || self.probe.prefilter.is_none() {
self.abandon_in_flight_bakes();
self.probe.book.abort();
return Ok(());
}
let prefiltering_occupied = self.probe.prefiltering.is_some();
let more_mips = self
.probe
.prefiltering
.as_ref()
.is_some_and(|p| p.cursor < PLAN.mips());
let completed = unsafe { self.frame_sync.fence.GetCompletedValue() };
let mips_done = self
.probe
.prefiltering
.as_ref()
.is_some_and(|p| completed >= p.last_fence_value);
match reflection_probe::next_bake_action(
if prefiltering_occupied {
BakePhase::Prefiltering
} else {
BakePhase::Idle
},
BakeSignals {
more_mips,
mips_done,
..Default::default()
},
) {
BakeAction::PrefilterMip => {
if let Err(e) = self.probe_prefilter_next_mip() {
self.fail_bake(e);
return Ok(());
}
}
BakeAction::Install => {
if let Err(e) = self.probe_install() {
self.fail_bake(e);
return Ok(());
}
}
_ => {}
}
let prefiltering_free = self.probe.prefiltering.is_none();
let rendering_occupied = self.probe.rendering.is_some();
let more_faces = self
.probe
.rendering
.as_ref()
.is_some_and(|r| r.cursor < PROBE_FACE_COUNT);
let completed = unsafe { self.frame_sync.fence.GetCompletedValue() };
let done = self
.probe
.rendering
.as_ref()
.is_some_and(|r| r.cursor >= PROBE_FACE_COUNT && completed >= r.last_fence_value);
let eligible = self.cull_count() > 0 && prefiltering_free;
match reflection_probe::next_bake_action(
if rendering_occupied {
BakePhase::Rendering
} else {
BakePhase::Idle
},
BakeSignals {
faces_done: done && prefiltering_free,
queue_pending: self.probe.book.pending(),
eligible,
more_faces,
..Default::default()
},
) {
BakeAction::RenderFace => {
if let Err(e) = self.probe_render_next_face() {
self.fail_bake(e);
}
}
BakeAction::StartPrefilter => {
if let Err(e) = self.probe_begin_prefilter() {
self.fail_bake(e);
}
}
BakeAction::StartNext => {
if let Err(e) = self.probe_start_next(near, far) {
self.fail_bake(e);
}
}
BakeAction::PrefilterMip | BakeAction::Install | BakeAction::Idle => {}
}
Ok(())
}
fn abandon_in_flight_bakes(&mut self) {
if self.probe.rendering.is_some() || self.probe.prefiltering.is_some() {
self.wait_idle();
}
self.probe.rendering = None;
self.probe.prefiltering = None;
}
fn fail_bake(&mut self, e: RenderError) {
tracing::warn!(
"reflection probe bake failed, keeping {} baked: {e}",
self.probe.book.count()
);
self.abandon_in_flight_bakes();
self.probe.book.abort();
}
fn probe_start_next(&mut self, near: f32, far: f32) -> RenderResult<()> {
let Some((index, placement)) = self.probe.book.take_next() else {
return Ok(());
};
let eye = placement.position;
let slot = self.bake_ring_slot();
self.build_object_buffer(slot);
self.build_draw_args_buffer(
slot,
eye,
concinnity_core::render::model_history::HistoryMode::Untracked,
);
let alloc = &self.hw.alloc;
let device = &self.hw.device;
let sample_count = self.targets.hdr.msaa_samples.max(1);
let size = PROBE_FACE_SIZE;
let rtv_heap = create_rtv_heap(device)?;
let dsv_heap = create_dsv_heap(device)?;
let rtv = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() };
let dsv = unsafe { dsv_heap.GetCPUDescriptorHandleForHeapStart() };
let color =
create_hdr_color_target(device, size, size, sample_count, rtv, self.view.clear_color)?;
let depth = create_bake_depth(device, size, sample_count, dsv)?;
let resolve = if sample_count > 1 {
Some(create_hdr_resolve_target(device, size, size)?)
} else {
None
};
let light_bytes = unsafe {
std::slice::from_raw_parts(
&self.uniforms.light_uniforms as *const render_types::LightUniforms as *const u8,
std::mem::size_of::<render_types::LightUniforms>(),
)
};
let (light_cbv, light_gva) = make_snapshot_cbv(alloc, light_bytes)?;
let shadow_bytes = unsafe {
std::slice::from_raw_parts(
&self.shadow.uniforms as *const render_types::ShadowUniforms as *const u8,
std::mem::size_of::<render_types::ShadowUniforms>(),
)
};
let (shadow_cbv, shadow_gva) = make_snapshot_cbv(alloc, shadow_bytes)?;
let prefilter_mip_count = self.scene.env_map.prefilter_mip_count as f32;
let mut view_cbvs = Vec::with_capacity(PROBE_FACE_COUNT);
let mut view_gvas = Vec::with_capacity(PROBE_FACE_COUNT);
for face in 0..PROBE_FACE_COUNT {
let vp = reflection_probe::face_view_projection(eye, face, near, far);
let view_mat = reflection_probe::face_view_matrix(eye, face);
let view = super::draw::ViewUniforms {
vp,
view: view_mat,
elapsed: 0.0,
reflections_enabled: 0.0,
cam_pos: [eye[0], eye[1], eye[2]],
prefilter_mip_count,
shade_mode: 0.0,
_end_pad: 0.0,
sky_rot: self.view.sky_rot,
};
let cbv = alloc.alloc_buffer(
256,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { cbv.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "probe: map view cbv"))?;
unsafe {
std::ptr::copy_nonoverlapping(
&view as *const super::draw::ViewUniforms as *const u8,
ptr as *mut u8,
std::mem::size_of::<super::draw::ViewUniforms>(),
);
}
view_gvas.push(com::gpu_va(&cbv));
view_cbvs.push(cbv);
}
let cubes = self
.probe
.gpu
.cubes
.as_ref()
.ok_or_else(|| RenderError::Other("probe: no cube array for a placement".into()))?;
let prefilter = PrefilterGpu::new(self, &PLAN, cubes, index)?;
self.probe.rendering = Some(RenderingBake {
index,
cursor: 0,
eye,
near,
far,
sample_count,
color,
_depth: depth,
resolve,
_rtv_heap: rtv_heap,
_dsv_heap: dsv_heap,
rtv,
dsv,
_view_cbvs: view_cbvs,
view_gvas,
light_gva,
shadow_gva,
_light_cbv: light_cbv,
_shadow_cbv: shadow_cbv,
prefilter,
cmd_allocs: Vec::with_capacity(PROBE_FACE_COUNT),
cmd_lists: Vec::with_capacity(PROBE_FACE_COUNT),
last_fence_value: 0,
});
Ok(())
}
fn probe_render_next_face(&mut self) -> RenderResult<()> {
let slot = self.bake_ring_slot();
let (face, eye, near, far, sample_count, view_gva, light_gva, shadow_gva) = {
let bake = self.probe.rendering.as_ref().ok_or_else(|| {
RenderError::Other("probe: render face with no capture in flight".to_string())
})?;
(
bake.cursor,
bake.eye,
bake.near,
bake.far,
bake.sample_count,
bake.view_gvas[bake.cursor],
bake.light_gva,
bake.shadow_gva,
)
};
let vp = reflection_probe::face_view_projection(eye, face, near, far);
let frustum = Frustum::from_view_projection(vp);
let alloc: ID3D12CommandAllocator = unsafe {
self.hw
.device
.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)
}
.map_err(|e| map_hresult(e.code(), "probe: face allocator"))?;
let cmd: ID3D12GraphicsCommandList = unsafe {
self.hw
.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.map_err(|e| map_hresult(e.code(), "probe: face cmd list"))?;
if let Some(bake) = self.probe.rendering.as_mut() {
bake.cmd_allocs.push(alloc);
bake.cmd_lists.push(cmd.clone());
}
self.encode_probe_cull(&cmd, slot, &frustum, eye);
let (rtv, dsv) = {
let bake = self
.probe
.rendering
.as_ref()
.expect("probe bake targets are live while a bake is recording");
(bake.rtv, bake.dsv)
};
let indirect = &self.cull.indirect_cmd_buffers[slot];
let object_gva = com::gpu_va(&self.cull.object_buffer_resources[slot]);
self.encode_main_into_face(
&cmd,
FaceTargets { rtv, dsv },
FaceUniforms {
view_gva,
light_gva,
shadow_ubo_gva: shadow_gva,
},
IndirectDraw {
indirect,
indirect_offset: 0,
object_gva,
},
FaceExtent {
width: PROBE_FACE_SIZE,
height: PROBE_FACE_SIZE,
},
);
self.copy_face_to_capture(&cmd, face, sample_count)?;
unsafe { cmd.Close() }.map_err(|e| map_hresult(e.code(), "probe: face close"))?;
let list: ID3D12CommandList = windows::core::Interface::cast(&cmd)
.map_err(|e| map_hresult(e.code(), "probe: face cast"))?;
unsafe { self.hw.command_queue.ExecuteCommandLists(&[Some(list)]) };
let fence_val = self.frame_sync.next_fence_value.get();
self.frame_sync.next_fence_value.set(fence_val + 1);
unsafe {
self.hw
.command_queue
.Signal(&self.frame_sync.fence, fence_val)
}
.map_err(|e| map_hresult(e.code(), "probe: face signal"))?;
if let Some(bake) = self.probe.rendering.as_mut() {
bake.last_fence_value = fence_val;
bake.cursor += 1;
}
Ok(())
}
fn copy_face_to_capture(
&self,
cmd: &ID3D12GraphicsCommandList,
face: usize,
sample_count: u32,
) -> RenderResult<()> {
let bake = self
.probe
.rendering
.as_ref()
.expect("probe bake targets are live while a bake is recording");
let dst_subresource = face as u32 * bake.prefilter.mips();
let dst_loc = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(bake.prefilter.capture()),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: dst_subresource,
},
};
if sample_count > 1 {
let resolve = bake
.resolve
.as_ref()
.expect("a multisampled probe bake has a resolve image");
unsafe {
cmd.ResourceBarrier(&[
transition_barrier(
&bake.color,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
),
transition_barrier(
resolve,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RESOLVE_DEST,
),
]);
cmd.ResolveSubresource(resolve, 0, &bake.color, 0, HDR_FORMAT);
cmd.ResourceBarrier(&[
transition_barrier(
resolve,
D3D12_RESOURCE_STATE_RESOLVE_DEST,
D3D12_RESOURCE_STATE_COPY_SOURCE,
),
transition_barrier(
&bake.color,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
),
]);
let src_loc = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(resolve),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: 0,
},
};
cmd.CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, None);
cmd.ResourceBarrier(&[transition_barrier(
resolve,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
)]);
}
} else {
unsafe {
cmd.ResourceBarrier(&[transition_barrier(
&bake.color,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_COPY_SOURCE,
)]);
let src_loc = D3D12_TEXTURE_COPY_LOCATION {
pResource: com::borrowed(&bake.color),
Type: D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
Anonymous: D3D12_TEXTURE_COPY_LOCATION_0 {
SubresourceIndex: 0,
},
};
cmd.CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, None);
cmd.ResourceBarrier(&[transition_barrier(
&bake.color,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
)]);
}
}
Ok(())
}
fn probe_begin_prefilter(&mut self) -> RenderResult<()> {
let bake = self.probe.rendering.take().ok_or_else(|| {
RenderError::Other("probe: convolve with no bake in flight".to_string())
})?;
let RenderingBake {
index, prefilter, ..
} = bake;
let mut bake = PrefilteringBake {
index,
gpu: prefilter,
cursor: 1,
cmd_allocs: Vec::with_capacity(PLAN.mips() as usize),
cmd_lists: Vec::with_capacity(PLAN.mips() as usize),
last_fence_value: 0,
};
let result = self.record_prefilter_step(&mut bake, |ctx, cmd, bake| {
ctx.encode_probe_pyramid(cmd, &bake.gpu, &PLAN)
});
self.probe.prefiltering = Some(bake);
result
}
fn probe_prefilter_next_mip(&mut self) -> RenderResult<()> {
let mut bake = self.probe.prefiltering.take().ok_or_else(|| {
RenderError::Other("probe: convolve mip with no bake in flight".to_string())
})?;
let cursor = bake.cursor;
let last = cursor + 1 == PLAN.mips();
let result = self.record_prefilter_step(&mut bake, |ctx, cmd, bake| {
ctx.encode_probe_ggx_mip(cmd, &PLAN, cursor)?;
if last {
let barriers = ctx.probe.gpu.bound_cubes().cube_barriers(
bake.gpu.cube(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
super::probe_set::PROBE_CUBES_STATE,
);
unsafe { cmd.ResourceBarrier(&barriers) };
}
Ok(())
});
bake.cursor += 1;
self.probe.prefiltering = Some(bake);
result
}
fn record_prefilter_step(
&self,
bake: &mut PrefilteringBake,
encode: impl FnOnce(&Self, &ID3D12GraphicsCommandList, &PrefilteringBake) -> RenderResult<()>,
) -> RenderResult<()> {
let alloc: ID3D12CommandAllocator = unsafe {
self.hw
.device
.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)
}
.map_err(|e| map_hresult(e.code(), "probe: convolve allocator"))?;
let cmd: ID3D12GraphicsCommandList = unsafe {
self.hw
.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.map_err(|e| map_hresult(e.code(), "probe: convolve cmd list"))?;
bake.cmd_allocs.push(alloc);
bake.cmd_lists.push(cmd.clone());
unsafe {
cmd.SetDescriptorHeaps(&[Some(self.descriptors.srv_heap.clone())]);
}
encode(self, &cmd, bake)?;
unsafe { cmd.Close() }.map_err(|e| map_hresult(e.code(), "probe: convolve close"))?;
let list: ID3D12CommandList = windows::core::Interface::cast(&cmd)
.map_err(|e| map_hresult(e.code(), "probe: convolve cast"))?;
unsafe { self.hw.command_queue.ExecuteCommandLists(&[Some(list)]) };
let fence_val = self.frame_sync.next_fence_value.get();
self.frame_sync.next_fence_value.set(fence_val + 1);
unsafe {
self.hw
.command_queue
.Signal(&self.frame_sync.fence, fence_val)
}
.map_err(|e| map_hresult(e.code(), "probe: convolve signal"))?;
bake.last_fence_value = fence_val;
Ok(())
}
fn probe_install(&mut self) -> RenderResult<()> {
let bake = self.probe.prefiltering.take().ok_or_else(|| {
RenderError::Other("probe: install with no bake in flight".to_string())
})?;
let PrefilteringBake { index, .. } = bake;
let progress = self.probe.book.install(index)?;
tracing::info!("reflection probes: {progress}");
Ok(())
}
pub(in crate::directx) fn encode_main_into_face(
&self,
cmd: &ID3D12GraphicsCommandList,
targets: FaceTargets,
uniforms: FaceUniforms,
draw: IndirectDraw<'_>,
extent: FaceExtent,
) {
let FaceTargets { rtv, dsv } = targets;
let FaceUniforms {
view_gva,
light_gva,
shadow_ubo_gva,
} = uniforms;
let IndirectDraw {
indirect,
indirect_offset,
object_gva,
} = draw;
let FaceExtent { width, height } = extent;
let bindless_pso = self
.cull
.main_bindless_pso
.as_ref()
.expect("bindless PSO is live");
let bindless_root = self
.cull
.main_bindless_root_sig
.as_ref()
.expect("bindless root signature is live alongside its PSO");
let cull_sig = self
.cull
.cull_command_signature
.as_ref()
.expect("cull command signature is live alongside the bindless PSO");
let local_lights_gva = com::gpu_va(&self.uniforms.local_light_buffer);
unsafe {
cmd.OMSetRenderTargets(1, Some(&rtv), false, Some(&dsv));
cmd.ClearRenderTargetView(rtv, &self.view.clear_color, None);
cmd.ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH, 1.0, 0, None);
let vp = D3D12_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: width as f32,
Height: height as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
cmd.RSSetViewports(&[vp]);
let scissor = windows::Win32::Foundation::RECT {
left: 0,
top: 0,
right: width as i32,
bottom: height as i32,
};
cmd.RSSetScissorRects(&[scissor]);
cmd.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cmd.IASetVertexBuffers(0, Some(&[self.scene.geometry.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&self.scene.geometry.index_buffer_view));
cmd.SetDescriptorHeaps(&[
Some(self.descriptors.srv_heap.clone()),
Some(self.descriptors.sampler_heap.clone()),
]);
cmd.SetPipelineState(bindless_pso);
cmd.SetGraphicsRootSignature(bindless_root);
cmd.SetGraphicsRootConstantBufferView(1, view_gva);
cmd.SetGraphicsRootConstantBufferView(2, light_gva);
cmd.SetGraphicsRootConstantBufferView(3, shadow_ubo_gva);
cmd.set_graphics_srv_table(4, self.shadow.srv_gpu);
cmd.set_graphics_srv_table(5, self.cull.bindless_pool_gpu[self.current_frame]);
cmd.set_graphics_sampler_table(6, self.descriptors.shadow_sampler_gpu);
cmd.set_graphics_sampler_table(7, self.descriptors.linear_sampler_gpu);
cmd.SetGraphicsRootShaderResourceView(8, object_gva);
cmd.SetGraphicsRootShaderResourceView(12, local_lights_gva);
cmd.SetGraphicsRootConstantBufferView(
13,
self.cluster_params_gva(self.current_frame, false),
);
cmd.SetGraphicsRootShaderResourceView(14, self.cluster_list_gva());
self.bind_local_light_tables(cmd, super::draw::LocalLightParams::BINDLESS);
cmd.set_graphics_srv_table(9, self.ssao_ao_srv_gpu());
self.bind_main_probe_set(
cmd,
com::gpu_va(&self.uniforms.probe_set_empty_cbv),
self.probe.gpu.stand_in_records.gpu_va(),
);
cmd.ExecuteIndirect(
cull_sig,
self.skinned_record_base() as u32,
indirect,
indirect_offset as u64,
None::<&ID3D12Resource>,
0,
);
}
self.inc_draw_calls(1);
}
}
fn make_snapshot_cbv(alloc: &DeviceAllocator, bytes: &[u8]) -> RenderResult<(PooledBuffer, u64)> {
let size = (((bytes.len() as u64) + 255) & !255).max(256);
let cbv = alloc.alloc_buffer(
size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { cbv.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "probe: map snapshot cbv"))?;
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr as *mut u8, bytes.len());
}
let gva = com::gpu_va(&cbv);
Ok((cbv, gva))
}
fn create_rtv_heap(device: &ID3D12Device) -> RenderResult<ID3D12DescriptorHeap> {
let desc = D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
NumDescriptors: 1,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
};
unsafe { device.CreateDescriptorHeap(&desc) }
.map_err(|e| map_hresult(e.code(), "probe: rtv heap"))
}
fn create_dsv_heap(device: &ID3D12Device) -> RenderResult<ID3D12DescriptorHeap> {
let desc = D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_DSV,
NumDescriptors: 1,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
};
unsafe { device.CreateDescriptorHeap(&desc) }
.map_err(|e| map_hresult(e.code(), "probe: dsv heap"))
}
fn create_bake_depth(
device: &ID3D12Device,
size: u32,
sample_count: u32,
dsv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) -> RenderResult<ID3D12Resource> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let clear_value = D3D12_CLEAR_VALUE {
Format: DXGI_FORMAT_D32_FLOAT,
Anonymous: D3D12_CLEAR_VALUE_0 {
DepthStencil: D3D12_DEPTH_STENCIL_VALUE {
Depth: 1.0,
Stencil: 0,
},
},
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: size as u64,
Height: size,
DepthOrArraySize: 1,
MipLevels: 1,
Format: DXGI_FORMAT_D32_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: sample_count,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL,
..Default::default()
};
let mut tex_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
Some(&clear_value),
&mut tex_opt,
)
}
.map_err(|e| map_hresult(e.code(), "probe: create face depth"))?;
let texture = tex_opt
.ok_or_else(|| RenderError::Other("probe: create face depth returned None".to_string()))?;
let dsv_desc = D3D12_DEPTH_STENCIL_VIEW_DESC {
Format: DXGI_FORMAT_D32_FLOAT,
ViewDimension: if sample_count > 1 {
D3D12_DSV_DIMENSION_TEXTURE2DMS
} else {
D3D12_DSV_DIMENSION_TEXTURE2D
},
Flags: D3D12_DSV_FLAG_NONE,
..Default::default()
};
unsafe { device.CreateDepthStencilView(&texture, Some(&dsv_desc), dsv_cpu) };
Ok(texture)
}