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::probe_prefilter::PrefilterGpu;
use super::texture::{
HDR_FORMAT, create_buffer, create_hdr_color_target, create_hdr_resolve_target,
transition_barrier,
};
use crate::gfx::reflection_probe::{self, BakeAction, BakePhase, BakeSignals, PrefilterPlan};
const PLAN: PrefilterPlan = PrefilterPlan::RUNTIME;
const PROBE_FACE_SIZE: u32 = PLAN.face_size();
const PROBE_FACE_COUNT: usize = 6;
pub(in crate::directx) struct ProbeCube {
#[expect(
dead_code,
reason = "held to keep the prefilter cube resident; the array SRV is what the shaders bind"
)]
pub(in crate::directx) prefilter: ID3D12Resource,
}
pub(in crate::directx) struct RenderingBake {
index: usize,
placement: reflection_probe::ProbePlacement,
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,
placement: reflection_probe::ProbePlacement,
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]) {
use concinnity_core::render::uniforms::MAX_PROBES;
use concinnity_core::render::uniforms::ProbeSet;
let mut placements: Vec<reflection_probe::ProbePlacement> = if declared.is_empty() {
match self.scene_world_bounds() {
Some((mn, mx)) => {
let occupancy: Vec<([f32; 3], [f32; 3])> = self
.draw
.objects
.iter()
.map(|o| (o.bb_min, o.bb_max))
.filter(|(mn, mx)| mn.iter().chain(mx).all(|c| c.is_finite()))
.collect();
reflection_probe::auto_seed_probes(mn, mx, &occupancy)
}
None => Vec::new(),
}
} else {
declared.to_vec()
};
if placements.len() > MAX_PROBES {
tracing::warn!(
"reflection probes: {} placements, capping at MAX_PROBES={}",
placements.len(),
MAX_PROBES
);
placements.truncate(MAX_PROBES);
}
self.abandon_in_flight_bakes();
self.probe.placements = placements;
self.probe.maps.clear();
self.probe.set = ProbeSet::EMPTY;
self.probe.bake_queue = reflection_probe::ProbeBakeQueue::new(self.probe.placements.len());
}
fn bake_ring_slot(&self) -> usize {
FRAMES
}
pub(in crate::directx) fn probe_cube_table_gpu(&self) -> D3D12_GPU_DESCRIPTOR_HANDLE {
let base = unsafe {
self.descriptors
.srv_heap
.GetGPUDescriptorHandleForHeapStart()
};
D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: base.ptr
+ (self.descriptors.probe_cube_base_slot * self.descriptors.srv_descriptor_size)
as u64,
}
}
fn probe_cube_slot_cpu(&self, i: usize) -> D3D12_CPU_DESCRIPTOR_HANDLE {
let base = unsafe {
self.descriptors
.srv_heap
.GetCPUDescriptorHandleForHeapStart()
};
D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: base.ptr
+ (self.descriptors.probe_cube_base_slot + i)
* self.descriptors.srv_descriptor_size,
}
}
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,
elapsed: f32,
near: f32,
far: f32,
) -> Result<(), String> {
let _ = elapsed;
if !self.probe.bake_queue.pending()
&& self.probe.rendering.is_none()
&& self.probe.prefiltering.is_none()
{
return Ok(());
}
if self.env_map.prefilter_mip_count <= 1
|| !self.probe_capture_supported()
|| self.probe.prefilter.is_none()
{
self.abandon_in_flight_bakes();
self.probe.bake_queue.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.bake_queue.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: String) {
tracing::warn!(
"reflection probe bake failed, keeping {} baked: {e}",
self.probe.maps.len()
);
self.abandon_in_flight_bakes();
self.probe.bake_queue.abort();
}
fn probe_start_next(&mut self, near: f32, far: f32) -> Result<(), String> {
let Some(index) = self.probe.bake_queue.take_next() else {
return Ok(());
};
let placement = self.probe.placements[index];
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.alloc;
let device = &self.device;
let sample_count = self.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 crate::gfx::render_types::LightUniforms
as *const u8,
std::mem::size_of::<crate::gfx::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 crate::gfx::render_types::ShadowUniforms
as *const u8,
std::mem::size_of::<crate::gfx::render_types::ShadowUniforms>(),
)
};
let (shadow_cbv, shadow_gva) = make_snapshot_cbv(alloc, shadow_bytes)?;
let prefilter_mip_count = self.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 = create_buffer(
alloc,
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| format!("probe: map view cbv: {e}"))?;
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 prefilter = PrefilterGpu::new(self, &PLAN)?;
self.probe.rendering = Some(RenderingBake {
index,
placement,
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) -> Result<(), String> {
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("probe: render face with no capture in flight")?;
(
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 = crate::gfx::frustum::Frustum::from_view_projection(vp);
let alloc: ID3D12CommandAllocator = unsafe {
self.device
.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)
}
.map_err(|e| format!("probe: face allocator: {e}"))?;
let cmd: ID3D12GraphicsCommandList = unsafe {
self.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.map_err(|e| format!("probe: face cmd list: {e}"))?;
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| format!("probe: face close: {e}"))?;
let list: ID3D12CommandList =
windows::core::Interface::cast(&cmd).map_err(|e| format!("probe: face cast: {e}"))?;
unsafe { self.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.command_queue.Signal(&self.frame_sync.fence, fence_val) }
.map_err(|e| format!("probe: face signal: {e}"))?;
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,
) -> Result<(), String> {
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) -> Result<(), String> {
let bake = self
.probe
.rendering
.take()
.ok_or("probe: convolve with no bake in flight")?;
let RenderingBake {
index,
placement,
prefilter,
..
} = bake;
let mut bake = PrefilteringBake {
index,
placement,
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) -> Result<(), String> {
let mut bake = self
.probe
.prefiltering
.take()
.ok_or("probe: convolve mip with no bake in flight")?;
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 {
unsafe {
cmd.ResourceBarrier(&[transition_barrier(
bake.gpu.probe(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
)]);
}
}
Ok(())
});
bake.cursor += 1;
self.probe.prefiltering = Some(bake);
result
}
fn record_prefilter_step(
&self,
bake: &mut PrefilteringBake,
encode: impl FnOnce(&Self, &ID3D12GraphicsCommandList, &PrefilteringBake) -> Result<(), String>,
) -> Result<(), String> {
let alloc: ID3D12CommandAllocator = unsafe {
self.device
.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT)
}
.map_err(|e| format!("probe: convolve allocator: {e}"))?;
let cmd: ID3D12GraphicsCommandList = unsafe {
self.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.map_err(|e| format!("probe: convolve cmd list: {e}"))?;
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| format!("probe: convolve close: {e}"))?;
let list: ID3D12CommandList = windows::core::Interface::cast(&cmd)
.map_err(|e| format!("probe: convolve cast: {e}"))?;
unsafe { self.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.command_queue.Signal(&self.frame_sync.fence, fence_val) }
.map_err(|e| format!("probe: convolve signal: {e}"))?;
bake.last_fence_value = fence_val;
Ok(())
}
fn probe_install(&mut self) -> Result<(), String> {
let bake = self
.probe
.prefiltering
.take()
.ok_or("probe: install with no bake in flight")?;
let mips = bake.gpu.mips();
let PrefilteringBake {
index,
placement: p,
gpu,
..
} = bake;
let prefilter = gpu.into_probe_cube();
super::texture::write_cube_srv_mips_format(
&self.device,
&prefilter,
mips,
super::probe_prefilter::PROBE_CUBE_FORMAT,
self.probe_cube_slot_cpu(index),
);
debug_assert_eq!(index, self.probe.maps.len());
self.probe.maps.push(ProbeCube { prefilter });
self.probe.set.probes[index] = concinnity_core::render::uniforms::ProbeUniforms {
box_min: [p.box_min[0], p.box_min[1], p.box_min[2], 1.0],
box_max: [p.box_max[0], p.box_max[1], p.box_max[2], 0.0],
probe_pos: [p.position[0], p.position[1], p.position[2], 0.0],
};
self.probe.set.count = self.probe.maps.len() as u32;
tracing::info!(
"reflection probes: baked {}/{}",
index + 1,
self.probe.placements.len()
);
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.geometry.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&self.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.SetGraphicsRootDescriptorTable(4, self.shadow.srv_gpu);
cmd.SetGraphicsRootDescriptorTable(5, self.cull.bindless_pool_gpu[self.current_frame]);
cmd.SetGraphicsRootDescriptorTable(6, self.descriptors.shadow_sampler_gpu);
cmd.SetGraphicsRootDescriptorTable(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.SetGraphicsRootDescriptorTable(9, self.ssao_ao_srv_gpu());
cmd.SetGraphicsRootDescriptorTable(10, self.probe_cube_table_gpu());
cmd.SetGraphicsRootConstantBufferView(11, com::gpu_va(&self.probe.set_empty_cbv));
cmd.ExecuteIndirect(
cull_sig,
self.skinned_record_base() as u32,
indirect,
indirect_offset as u64,
None::<&ID3D12Resource>,
0,
);
}
self.inc_draw_calls(1);
}
pub(super) fn scene_world_bounds(&self) -> Option<([f32; 3], [f32; 3])> {
reflection_probe::fold_world_bounds(self.draw.objects.iter().map(|o| (o.bb_min, o.bb_max)))
}
}
fn make_snapshot_cbv(alloc: &DeviceAllocator, bytes: &[u8]) -> Result<(PooledBuffer, u64), String> {
let size = (((bytes.len() as u64) + 255) & !255).max(256);
let cbv = create_buffer(
alloc,
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| format!("probe: map snapshot cbv: {e}"))?;
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) -> Result<ID3D12DescriptorHeap, String> {
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| format!("probe: rtv heap: {e}"))
}
fn create_dsv_heap(device: &ID3D12Device) -> Result<ID3D12DescriptorHeap, String> {
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| format!("probe: dsv heap: {e}"))
}
fn create_bake_depth(
device: &ID3D12Device,
size: u32,
sample_count: u32,
dsv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) -> Result<ID3D12Resource, String> {
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| format!("probe: create face depth: {e}"))?;
let texture = tex_opt.ok_or_else(|| "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)
}