use concinnity_core::gfx::transform::mat4_inverse;
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;
use super::draw::ViewUniforms;
use super::graph_exec::GraphFrameParams;
use super::texture::{
HDR_FORMAT, create_buffer, create_hdr_color_target, create_hdr_resolve_target,
create_uav_buffer, transition_barrier, write_hdr_srv,
};
use concinnity_core::gfx::transform::mat4_mul;
pub(in crate::directx) const MAX_PLANAR_PLANES: usize =
crate::gfx::planar_reflection::MAX_PLANAR_PLANES;
const PLANAR_CLIP_BIAS: f32 = 0.02;
pub(in crate::directx) fn pane_plane(normal: [f32; 3], centre: [f32; 3]) -> [f32; 4] {
[
normal[0],
normal[1],
normal[2],
-(normal[0] * centre[0] + normal[1] * centre[1] + normal[2] * centre[2]),
]
}
pub(in crate::directx) struct PlanarReflectionSet {
planes: Vec<[f32; 4]>,
sample_count: u32,
clear_color: [f32; 4],
color: ID3D12Resource,
_depth: ID3D12Resource,
_rtv_heap: ID3D12DescriptorHeap,
_dsv_heap: ID3D12DescriptorHeap,
color_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
depth_dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
resolves: Vec<ID3D12Resource>,
resolve_srv_cpu: Vec<D3D12_CPU_DESCRIPTOR_HANDLE>,
resolve_srv_gpu: Vec<D3D12_GPU_DESCRIPTOR_HANDLE>,
_view_cbvs: Vec<PooledBuffer>,
view_ptrs: Vec<*mut u8>,
view_gvas: Vec<u64>,
planar_indirect: Vec<ID3D12Resource>,
planar_status: Vec<ID3D12Resource>,
n_cull: usize,
}
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 [D3D12_GPU_DESCRIPTOR_HANDLE],
pub clear_color: [f32; 4],
}
impl PlanarReflectionSet {
pub(in crate::directx) fn new(
alloc: &DeviceAllocator,
config: PlanarConfig,
planes: &[[f32; 4]],
targets: PlanarTargets,
) -> Result<Self, String> {
let device = alloc.device();
let PlanarConfig {
sample_count,
width,
height,
n_cull,
} = config;
let PlanarTargets {
resolve_srv_cpu,
resolve_srv_gpu,
clear_color,
} = targets;
let rtv_heap = create_rtv_heap(device)?;
let dsv_heap = create_dsv_heap(device)?;
let color_rtv = unsafe { rtv_heap.GetCPUDescriptorHandleForHeapStart() };
let depth_dsv = unsafe { dsv_heap.GetCPUDescriptorHandleForHeapStart() };
let color = create_hdr_color_target(
device,
width.max(1),
height.max(1),
sample_count,
color_rtv,
clear_color,
)?;
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_resolve_target(device, width.max(1), height.max(1))?;
write_hdr_srv(device, &resolve, resolve_srv_cpu[i]);
resolves.push(resolve);
}
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 = 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!("planar: map view cbv: {e}"))?;
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,
)?);
}
Ok(Self {
planes: planes.to_vec(),
sample_count,
clear_color,
color,
_depth: depth,
_rtv_heap: rtv_heap,
_dsv_heap: dsv_heap,
color_rtv,
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,
})
}
pub(in crate::directx) fn resize_to(
&mut self,
device: &ID3D12Device,
width: u32,
height: u32,
) -> Result<(), String> {
let (w, h) = (width.max(1), height.max(1));
self.color = create_hdr_color_target(
device,
w,
h,
self.sample_count,
self.color_rtv,
self.clear_color,
)?;
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_resolve_target(device, w, h)?;
write_hdr_srv(device, &resolve, self.resolve_srv_cpu[i]);
self.resolves[i] = resolve;
}
Ok(())
}
pub(in crate::directx) fn resolve_srv_gpu(&self, slot: usize) -> D3D12_GPU_DESCRIPTOR_HANDLE {
self.resolve_srv_gpu[slot]
}
pub(in crate::directx) fn plane_count(&self) -> usize {
self.planes.len()
}
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 resolve_into(&self, cmd: &ID3D12GraphicsCommandList, slot: usize) {
let resolve = &self.resolves[slot];
if self.sample_count > 1 {
unsafe {
cmd.ResourceBarrier(&[
transition_barrier(
&self.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, &self.color, 0, HDR_FORMAT);
cmd.ResourceBarrier(&[
transition_barrier(
resolve,
D3D12_RESOURCE_STATE_RESOLVE_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
),
transition_barrier(
&self.color,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
),
]);
}
} else {
unsafe {
cmd.ResourceBarrier(&[
transition_barrier(
&self.color,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_COPY_SOURCE,
),
transition_barrier(
resolve,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COPY_DEST,
),
]);
cmd.CopyResource(resolve, &self.color);
cmd.ResourceBarrier(&[
transition_barrier(
resolve,
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
),
transition_barrier(
&self.color,
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
),
]);
}
}
}
}
impl DxContext {
pub(in crate::directx) fn encode_planar_reflections(
&self,
cmd: &ID3D12GraphicsCommandList,
params: &GraphFrameParams<'_>,
) -> Result<(), String> {
let Some(set) = self.planar_reflection.as_ref() else {
return Ok(());
};
let proj = mat4_mul(params.vp_mat, mat4_inverse(self.view.matrix));
let prefilter_mip_count = self.env_map.prefilter_mip_count as f32;
let (w, h) = (self.extent.render_width, self.extent.render_height);
let mut cull_planes: Vec<(crate::gfx::frustum::Frustum, [f32; 3])> =
Vec::with_capacity(set.plane_count());
for slot in 0..set.plane_count() {
let oriented = crate::gfx::planar_reflection::orient_plane_toward(
set.planes[slot],
params.cam_pos,
);
let m = crate::gfx::planar_reflection::planar_matrices(
self.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,
_end_pad: 0.0,
sky_rot: self.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>(),
);
}
cull_planes.push((
crate::gfx::frustum::Frustum::from_view_projection(m.view_proj),
m.eye,
));
}
self.encode_planar_culls(
cmd,
params.frame_idx,
&cull_planes,
set.indirect(params.frame_idx),
set.status_gva(params.frame_idx),
set.n_cull,
);
let frame_object_gva = com::gpu_va(&self.cull.object_buffer_resources[params.frame_idx]);
let indirect = set.indirect(params.frame_idx);
for slot in 0..set.plane_count() {
let ring = slot * FRAMES + params.frame_idx;
self.encode_main_into_face(
cmd,
crate::directx::probe::FaceTargets {
rtv: set.color_rtv,
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,
},
crate::directx::probe::IndirectDraw {
indirect,
indirect_offset: set.region_offset(slot),
object_gva: frame_object_gva,
},
crate::directx::probe::FaceExtent {
width: w,
height: h,
},
);
set.resolve_into(cmd, slot);
}
Ok(())
}
}
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!("planar: 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!("planar: dsv heap: {e}"))
}
fn create_planar_depth(
device: &ID3D12Device,
width: u32,
height: 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: 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| format!("planar: create depth: {e}"))?;
let texture = tex_opt.ok_or_else(|| "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 pane_plane_passes_through_centre_with_unit_normal() {
let p = pane_plane([0.0, 0.0, 1.0], [1.0, 2.0, 3.0]);
assert_eq!([p[0], p[1], p[2]], [0.0, 0.0, 1.0]);
let signed = p[0] * 1.0 + p[1] * 2.0 + p[2] * 3.0 + p[3];
assert!(signed.abs() < 1e-5, "centre lies on the plane");
}
#[test]
fn pane_plane_offset_is_negative_normal_dot_centre() {
let n = [0.6, 0.0, 0.8];
let c = [2.0, 5.0, -1.0];
let p = pane_plane(n, c);
let expect_d = -(n[0] * c[0] + n[1] * c[1] + n[2] * c[2]);
assert!((p[3] - expect_d).abs() < 1e-5);
}
#[test]
fn planar_capacity_is_four() {
assert_eq!(MAX_PLANAR_PLANES, 4);
assert_eq!(
MAX_PLANAR_PLANES,
crate::gfx::planar_reflection::MAX_PLANAR_PLANES
);
}
}