use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::gfx::render_types::ClusterParams;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::planar_reflection::{self, PixelRect, PlanarReflectors};
use concinnity_core::transform::mat4_inverse;
use concinnity_core::transform::mat4_mul;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::com;
use super::context::{DxContext, FRAMES, align256};
use super::cull::{INDIRECT_COMMAND_STRIDE, RegionCull};
use super::draw::ViewUniforms;
use super::error::map_hresult;
use super::graph_exec::GraphFrameParams;
use super::light_cull::{ClusterGrid, build_cluster_light_buffer};
use super::texture::{
HDR_FORMAT, create_hdr_color_target, create_hdr_sampled_target, create_uav_buffer,
transition_barrier, write_format_rtv, write_hdr_srv,
};
use crate::directx::depth::optimized_clear;
use crate::directx::descriptor_slot::SrvSlot;
pub(in crate::directx) const MAX_PLANAR_PLANES: usize = planar_reflection::MAX_PLANAR_PLANES;
const PLANAR_CLIP_BIAS: f32 = 0.02;
const PLANAR_CROP_MARGIN: u32 = 2;
const MIRROR_CLUSTER_OFFSET: usize = 256;
const MIRROR_CBV_SIZE: u64 = 512;
enum PlanarColor {
Multisampled(ID3D12Resource),
PerPlane,
}
pub(in crate::directx) struct PlanarReflectionSet {
layout: PlanarReflectors,
width: u32,
height: u32,
sample_count: u32,
clear_color: [f32; 4],
color: PlanarColor,
_depth: ID3D12Resource,
_rtv_heap: ID3D12DescriptorHeap,
_dsv_heap: ID3D12DescriptorHeap,
rtv_base: D3D12_CPU_DESCRIPTOR_HANDLE,
rtv_stride: usize,
depth_dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
resolves: Vec<ID3D12Resource>,
resolve_srv_cpu: Vec<D3D12_CPU_DESCRIPTOR_HANDLE>,
resolve_srv_gpu: Vec<SrvSlot>,
_view_cbvs: Vec<PooledBuffer>,
view_ptrs: Vec<*mut u8>,
view_gvas: Vec<u64>,
planar_indirect: Vec<ID3D12Resource>,
planar_status: Vec<ID3D12Resource>,
n_cull: usize,
cluster_lists: Vec<ID3D12Resource>,
}
unsafe impl Send for PlanarReflectionSet {}
unsafe impl Sync for PlanarReflectionSet {}
#[derive(Clone, Copy)]
pub(in crate::directx) struct PlanarConfig {
pub sample_count: u32,
pub width: u32,
pub height: u32,
pub n_cull: usize,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct PlanarTargets<'a> {
pub resolve_srv_cpu: &'a [D3D12_CPU_DESCRIPTOR_HANDLE],
pub resolve_srv_gpu: &'a [SrvSlot],
pub clear_color: [f32; 4],
}
impl PlanarReflectionSet {
pub(in crate::directx) fn new(
alloc: &DeviceAllocator,
config: PlanarConfig,
layout: PlanarReflectors,
targets: PlanarTargets,
) -> RenderResult<Self> {
let planes = layout.planes();
if planes.len() > MAX_PLANAR_PLANES {
return Err(RenderError::Other(format!(
"planar reflection: {} planes exceeds the {MAX_PLANAR_PLANES}-plane ceiling the \
reserved resolve descriptors are sized to",
planes.len()
)));
}
let device = alloc.device();
let PlanarConfig {
sample_count,
width: render_w,
height: render_h,
n_cull,
} = config;
let (width, height) = layout.target_size(render_w, render_h);
let PlanarTargets {
resolve_srv_cpu,
resolve_srv_gpu,
clear_color,
} = targets;
let rtv_heap = create_rtv_heap(device, planes.len())?;
let dsv_heap = create_dsv_heap(device)?;
let rtv_base = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() };
let depth_dsv = unsafe { dsv_heap.GetCPUDescriptorHandleForHeapStart() };
let rtv_stride =
unsafe { device.GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV) }
as usize;
let depth =
create_planar_depth(device, width.max(1), height.max(1), sample_count, depth_dsv)?;
let mut resolves = Vec::with_capacity(planes.len());
for (i, _) in planes.iter().enumerate() {
let resolve =
create_hdr_sampled_target(device, width.max(1), height.max(1), clear_color)?;
write_hdr_srv(device, &resolve, resolve_srv_cpu[i]);
resolves.push(resolve);
}
let color = create_planar_color(
device,
PlanarColorBuild {
width: width.max(1),
height: height.max(1),
sample_count,
clear_color,
resolves: &resolves,
rtv_base,
rtv_stride,
},
)?;
let mut view_cbvs = Vec::with_capacity(planes.len() * FRAMES);
let mut view_ptrs = Vec::with_capacity(planes.len() * FRAMES);
let mut view_gvas = Vec::with_capacity(planes.len() * FRAMES);
for _ in 0..planes.len() * FRAMES {
let cbv = alloc.alloc_buffer(
MIRROR_CBV_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(), "planar: map view cbv"))?;
view_gvas.push(com::gpu_va(&cbv));
view_ptrs.push(ptr as *mut u8);
view_cbvs.push(cbv);
}
let indirect_size =
align256((planes.len() * n_cull * INDIRECT_COMMAND_STRIDE as usize) as u64);
let status_size = align256((n_cull * std::mem::size_of::<u32>()) as u64).max(256);
let mut planar_indirect = Vec::with_capacity(FRAMES);
let mut planar_status = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
planar_indirect.push(create_uav_buffer(
device,
indirect_size.max(256),
D3D12_RESOURCE_STATE_COMMON,
)?);
planar_status.push(create_uav_buffer(
device,
status_size,
D3D12_RESOURCE_STATE_COMMON,
)?);
}
let cluster_lists = planes
.iter()
.map(|_| build_cluster_light_buffer(device))
.collect::<RenderResult<_>>()?;
Ok(Self {
layout,
width,
height,
sample_count,
clear_color,
color,
_depth: depth,
_rtv_heap: rtv_heap,
_dsv_heap: dsv_heap,
rtv_base,
rtv_stride,
depth_dsv,
resolves,
resolve_srv_cpu: resolve_srv_cpu.to_vec(),
resolve_srv_gpu: resolve_srv_gpu.to_vec(),
_view_cbvs: view_cbvs,
view_ptrs,
view_gvas,
planar_indirect,
planar_status,
n_cull,
cluster_lists,
})
}
pub(in crate::directx) fn resize_to(
&mut self,
device: &ID3D12Device,
render_w: u32,
render_h: u32,
) -> RenderResult<()> {
let (w, h) = self.layout.target_size(render_w, render_h);
self.width = w;
self.height = h;
self._depth = create_planar_depth(device, w, h, self.sample_count, self.depth_dsv)?;
for i in 0..self.resolves.len() {
let resolve = create_hdr_sampled_target(device, w, h, self.clear_color)?;
write_hdr_srv(device, &resolve, self.resolve_srv_cpu[i]);
self.resolves[i] = resolve;
}
self.color = create_planar_color(
device,
PlanarColorBuild {
width: w,
height: h,
sample_count: self.sample_count,
clear_color: self.clear_color,
resolves: &self.resolves,
rtv_base: self.rtv_base,
rtv_stride: self.rtv_stride,
},
)?;
Ok(())
}
pub(in crate::directx) fn resolve_srv_gpu(&self, slot: usize) -> SrvSlot {
self.resolve_srv_gpu[slot]
}
pub(in crate::directx) fn plane_count(&self) -> usize {
self.layout.planes().len()
}
pub(in crate::directx) fn frame_plan(
&self,
view_proj: [[f32; 4]; 4],
) -> planar_reflection::PlanarFramePlan {
self.layout.frame_plan(view_proj)
}
fn indirect(&self, frame: usize) -> &ID3D12Resource {
&self.planar_indirect[frame]
}
fn status_gva(&self, frame: usize) -> u64 {
com::gpu_va(&self.planar_status[frame])
}
fn region_offset(&self, slot: usize) -> u32 {
(slot * self.n_cull * INDIRECT_COMMAND_STRIDE as usize) as u32
}
fn face_rtv(&self, slot: usize) -> D3D12_CPU_DESCRIPTOR_HANDLE {
let index = rtv_slot_index(matches!(self.color, PlanarColor::Multisampled(_)), slot);
D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: self.rtv_base.ptr + index * self.rtv_stride,
}
}
fn cluster_grid(&self, slot: usize, frame: usize) -> ClusterGrid<'_> {
ClusterGrid {
params_gva: self.view_gvas[slot * FRAMES + frame] + MIRROR_CLUSTER_OFFSET as u64,
lists: &self.cluster_lists[slot],
}
}
fn begin_plane(&self, cmd: &ID3D12GraphicsCommandList, slot: usize) {
if !matches!(self.color, PlanarColor::PerPlane) {
return;
}
unsafe {
cmd.ResourceBarrier(&[transition_barrier(
&self.resolves[slot],
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
)]);
}
}
fn end_plane(&self, cmd: &ID3D12GraphicsCommandList, slot: usize) {
let resolve = &self.resolves[slot];
let PlanarColor::Multisampled(color) = &self.color else {
unsafe {
cmd.ResourceBarrier(&[transition_barrier(
resolve,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
)]);
}
return;
};
unsafe {
cmd.ResourceBarrier(&[
transition_barrier(
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, color, 0, HDR_FORMAT);
cmd.ResourceBarrier(&[
transition_barrier(
resolve,
D3D12_RESOURCE_STATE_RESOLVE_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
),
transition_barrier(
color,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
),
]);
}
}
}
impl DxContext {
pub(in crate::directx) fn encode_planar_reflections(
&self,
cmd: &ID3D12GraphicsCommandList,
params: &GraphFrameParams<'_>,
) -> RenderResult<()> {
let Some(set) = self.planar_reflection.as_ref() else {
return Ok(());
};
let crops =
params
.planar
.crops(set.plane_count(), set.width, set.height, PLANAR_CROP_MARGIN);
let crops = crops.as_slice();
if crops.is_empty() {
return Ok(());
}
let proj = mat4_mul(params.vp_mat, mat4_inverse(self.state.view.matrix));
let prefilter_mip_count = self.scene.env_map.prefilter_mip_count as f32;
let mut culls = [RegionCull::EMPTY; MAX_PLANAR_PLANES];
let mut kept = 0;
let mut clustered = [false; MAX_PLANAR_PLANES];
for &(slot, crop) in crops {
let oriented =
planar_reflection::orient_plane_toward(set.layout.planes()[slot], params.cam_pos);
let m = planar_reflection::planar_matrices(
self.state.view.matrix,
proj,
params.cam_pos,
oriented,
PLANAR_CLIP_BIAS,
);
let view = ViewUniforms {
vp: m.view_proj,
view: m.view,
elapsed: params.elapsed,
reflections_enabled: 0.0,
cam_pos: [m.eye[0], m.eye[1], m.eye[2]],
prefilter_mip_count,
shade_mode: 0.0,
ambient_occlusion: 0.0,
sky_rot: self.state.view.sky_rot,
};
let ring = slot * FRAMES + params.frame_idx;
unsafe {
std::ptr::copy_nonoverlapping(
&view as *const ViewUniforms as *const u8,
set.view_ptrs[ring],
std::mem::size_of::<ViewUniforms>(),
);
}
let cluster_params = params.cluster_params.with_camera(&m.cluster_camera(
params.cluster_params.z_near,
self.uniforms.cluster_reach.range(
m.eye,
params.cluster_params.z_near,
self.probe.book.records(),
self.state.view.view_distance,
),
set.width,
set.height,
));
clustered[slot] = cluster_params.use_clusters != 0;
unsafe {
std::ptr::copy_nonoverlapping(
&cluster_params as *const ClusterParams as *const u8,
set.view_ptrs[ring].add(MIRROR_CLUSTER_OFFSET),
std::mem::size_of::<ClusterParams>(),
);
}
if let Some(cull) = culls.get_mut(kept) {
*cull = RegionCull {
region: slot,
frustum: Frustum::from_camera(
crop.crop_view_projection(m.view_proj, set.width, set.height),
self.state.view.view_distance,
),
eye: m.eye,
};
kept += 1;
}
}
self.encode_planar_culls(
cmd,
params.frame_idx,
&culls[..kept],
set.indirect(params.frame_idx),
set.status_gva(params.frame_idx),
set.n_cull,
);
self.encode_mirror_light_culls(cmd, params.frame_idx, set, crops, &clustered);
let frame_object_gva = com::gpu_va(&self.cull.object_buffer_resources[params.frame_idx]);
let indirect = set.indirect(params.frame_idx);
for &(slot, crop) in crops {
let ring = slot * FRAMES + params.frame_idx;
set.begin_plane(cmd, slot);
self.encode_main_into_face(
cmd,
crate::directx::probe::FaceTargets {
rtv: set.face_rtv(slot),
dsv: set.depth_dsv,
},
crate::directx::probe::FaceUniforms {
view_gva: set.view_gvas[ring],
light_gva: params.light_gva,
shadow_ubo_gva: params.shadow_ubo_gva,
clusters: clustered[slot].then(|| set.cluster_grid(slot, params.frame_idx)),
},
crate::directx::probe::IndirectDraw {
indirect,
indirect_offset: set.region_offset(slot),
object_gva: frame_object_gva,
material_params_gva: self.material_params_gva(params.frame_idx),
},
crate::directx::probe::FaceExtent {
width: set.width,
height: set.height,
area: Some(crop),
},
);
set.end_plane(cmd, slot);
}
Ok(())
}
}
impl DxContext {
fn encode_mirror_light_culls(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
set: &PlanarReflectionSet,
crops: &[(usize, PixelRect)],
clustered: &[bool; MAX_PLANAR_PLANES],
) {
let mut slots = [0; MAX_PLANAR_PLANES];
let mut count = 0;
for &(slot, _) in crops {
if clustered[slot] && count < MAX_PLANAR_PLANES {
slots[count] = slot;
count += 1;
}
}
let slots = &slots[..count];
if slots.is_empty() {
return;
}
let flip = |from, to| -> [D3D12_RESOURCE_BARRIER; MAX_PLANAR_PLANES] {
std::array::from_fn(|i| match slots.get(i) {
Some(&slot) => transition_barrier(&set.cluster_lists[slot], from, to),
None => D3D12_RESOURCE_BARRIER::default(),
})
};
let to_write = flip(
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
);
unsafe { cmd.ResourceBarrier(&to_write[..count]) };
for &slot in slots {
self.encode_light_cull(cmd, frame_idx, set.cluster_grid(slot, frame_idx));
}
let to_read = flip(
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
unsafe { cmd.ResourceBarrier(&to_read[..count]) };
}
}
fn create_rtv_heap(
device: &ID3D12Device,
plane_count: usize,
) -> RenderResult<ID3D12DescriptorHeap> {
let desc = D3D12_DESCRIPTOR_HEAP_DESC {
Type: D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
NumDescriptors: plane_count.max(1) as u32,
Flags: D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
};
unsafe { device.CreateDescriptorHeap(&desc) }
.map_err(|e| map_hresult(e.code(), "planar: rtv heap"))
}
struct PlanarColorBuild<'a> {
width: u32,
height: u32,
sample_count: u32,
clear_color: [f32; 4],
resolves: &'a [ID3D12Resource],
rtv_base: D3D12_CPU_DESCRIPTOR_HANDLE,
rtv_stride: usize,
}
fn rtv_slot_index(multisampled: bool, slot: usize) -> usize {
if multisampled { 0 } else { slot }
}
fn create_planar_color(
device: &ID3D12Device,
build: PlanarColorBuild<'_>,
) -> RenderResult<PlanarColor> {
let PlanarColorBuild {
width,
height,
sample_count,
clear_color,
resolves,
rtv_base,
rtv_stride,
} = build;
if sample_count > 1 {
return Ok(PlanarColor::Multisampled(create_hdr_color_target(
device,
width,
height,
sample_count,
rtv_base,
clear_color,
)?));
}
for (i, resolve) in resolves.iter().enumerate() {
let rtv = D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: rtv_base.ptr + i * rtv_stride,
};
write_format_rtv(device, resolve, rtv, HDR_FORMAT);
}
Ok(PlanarColor::PerPlane)
}
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(), "planar: dsv heap"))
}
fn create_planar_depth(
device: &ID3D12Device,
width: u32,
height: 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 = optimized_clear();
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: width as u64,
Height: height,
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(), "planar: create depth"))?;
let texture = tex_opt
.ok_or_else(|| RenderError::Other("planar: create 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)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multisampled_planes_share_rtv_slot_zero() {
for slot in 0..MAX_PLANAR_PLANES {
assert_eq!(rtv_slot_index(true, slot), 0);
}
}
#[test]
fn single_sampled_planes_get_their_own_rtv_slot() {
for slot in 0..MAX_PLANAR_PLANES {
assert_eq!(rtv_slot_index(false, slot), slot);
}
}
#[test]
fn planar_capacity_is_four() {
assert_eq!(MAX_PLANAR_PLANES, 4);
assert_eq!(MAX_PLANAR_PLANES, planar_reflection::MAX_PLANAR_PLANES);
}
}