use concinnity_core::render::depth::DEPTH_CLEAR;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::uniforms::ModelHistoryParams;
use concinnity_core::render::view_history::{ViewFrame, ViewHistory};
use std::cell::RefCell;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use super::gbuffer_sky::GbufferSky;
use crate::directx::allocator::{DeviceAllocator, PooledBuffer};
use crate::directx::builtin_shaders;
use crate::directx::builtin_shaders::CompileProgram;
use crate::directx::com;
use crate::directx::context::{DxContext, FRAMES, align256, dump_on_err};
use crate::directx::descriptor_slot::{DescriptorTables, SrvSlot};
use crate::directx::error::map_hresult;
use crate::directx::pso::{Blend, Depth, GraphicsPso, compute_pso};
use crate::directx::root_constants::RootConstants;
use crate::directx::root_sig::{Range, RootSig, Visibility};
use crate::directx::texture::{create_main_depth_texture, write_format_rtv, write_format_srv};
pub(crate) const GBUFFER_NORMAL_DEPTH_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
pub(crate) const GBUFFER_ROUGHNESS_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R8_UNORM;
pub(crate) const GBUFFER_VELOCITY_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R16G16_FLOAT;
pub(in crate::directx) const GBUFFER_ROUGHNESS_CLEAR: [f32; 4] = [1.0, 0.0, 0.0, 0.0];
const GBUFFER_VIEW_UBO_SIZE: u64 = std::mem::size_of::<GBufferView>() as u64;
pub(in crate::directx) use concinnity_core::render::uniforms::GBufferView;
pub(in crate::directx) fn gbuffer_targets(pso: GraphicsPso<'_>) -> GraphicsPso<'_> {
pso.target(GBUFFER_NORMAL_DEPTH_FORMAT, Blend::Opaque)
.target(GBUFFER_ROUGHNESS_FORMAT, Blend::Opaque)
.target(GBUFFER_VELOCITY_FORMAT, Blend::Opaque)
}
fn prepass_input_layout() -> Vec<D3D12_INPUT_ELEMENT_DESC> {
let mut layout = crate::directx::pipeline::main_input_layout();
layout.push(D3D12_INPUT_ELEMENT_DESC {
SemanticName: windows::core::s!("PREVPOSITION"),
SemanticIndex: 0,
Format: DXGI_FORMAT_R32G32B32_FLOAT,
InputSlot: 1,
AlignedByteOffset: 0,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
});
layout
}
pub(in crate::directx) fn create_prepass_root_signature(
device: &ID3D12Device,
) -> RenderResult<ID3D12RootSignature> {
use Visibility::{All, Pixel, Vertex};
RootSig::new()
.constant_dwords(0, 1, All)
.cbv(1, Vertex)
.cbv(6, All)
.srv(3, All)
.srv(21, Vertex)
.srv(22, Vertex)
.srv(20, Vertex)
.table(&[Range::bindless_srv(1)], Pixel)
.sampler_table(1, 2, Pixel)
.input_layout()
.build(device, "gbuffer prepass root sig")
}
const PREPASS_VIEW_PARAM: u32 = 1;
const PREPASS_GB_VIEW_PARAM: u32 = 2;
const PREPASS_OBJECTS_PARAM: u32 = 3;
const PREPASS_PREV_MODELS_PARAM: u32 = 4;
const PREPASS_DRAW_ARGS_PARAM: u32 = 5;
const PREPASS_MATERIAL_PARAMS_PARAM: u32 = 6;
const PREPASS_POOL_PARAM: u32 = 7;
const PREPASS_SAMPLERS_PARAM: u32 = 8;
pub(in crate::directx) fn create_prepass_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
) -> RenderResult<ID3D12PipelineState> {
let layout = prepass_input_layout();
gbuffer_targets(GraphicsPso::new(root_sig, vs, ps).input_layout(&layout))
.depth(DXGI_FORMAT_D32_FLOAT, Depth::write())
.build(device, "gbuffer prepass")
}
const MODEL_HISTORY_THREADGROUP: u32 = 64;
fn uav_barrier(resource: &ID3D12Resource) -> D3D12_RESOURCE_BARRIER {
D3D12_RESOURCE_BARRIER {
Type: D3D12_RESOURCE_BARRIER_TYPE_UAV,
Flags: D3D12_RESOURCE_BARRIER_FLAG_NONE,
Anonymous: D3D12_RESOURCE_BARRIER_0 {
UAV: std::mem::ManuallyDrop::new(D3D12_RESOURCE_UAV_BARRIER {
pResource: com::borrowed(resource),
}),
},
}
}
fn create_model_history_root_signature(device: &ID3D12Device) -> RenderResult<ID3D12RootSignature> {
RootSig::new()
.constants::<ModelHistoryParams>(0, Visibility::All)
.srv(0, Visibility::All)
.uav(0, Visibility::All)
.input_layout()
.build(device, "model history root sig")
}
pub(in crate::directx) fn build_model_history(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
hot_reload: bool,
) -> RenderResult<(ID3D12RootSignature, ID3D12PipelineState)> {
let cs = builtin_shaders::MODEL_HISTORY.compile(hot_reload)?;
let root_sig = dump_on_err(info_queue, create_model_history_root_signature(device))?;
let pso = dump_on_err(
info_queue,
compute_pso(device, &root_sig, &cs, "model history"),
)?;
Ok((root_sig, pso))
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct GbufferSlots {
pub normal_depth_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub normal_depth_srv: (D3D12_CPU_DESCRIPTOR_HANDLE, SrvSlot),
pub roughness_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub roughness_srv: (D3D12_CPU_DESCRIPTOR_HANDLE, SrvSlot),
pub velocity_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub velocity_srv: (D3D12_CPU_DESCRIPTOR_HANDLE, SrvSlot),
pub depth_dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
}
pub(in crate::directx) struct GbufferResources {
pub(in crate::directx) normal_depth: ID3D12Resource,
pub(in crate::directx) normal_depth_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) normal_depth_srv_gpu: SrvSlot,
pub(in crate::directx) roughness: ID3D12Resource,
pub(in crate::directx) roughness_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) roughness_srv_gpu: SrvSlot,
pub(in crate::directx) velocity: ID3D12Resource,
pub(in crate::directx) velocity_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) velocity_srv_gpu: SrvSlot,
pub(in crate::directx) depth: ID3D12Resource,
pub(in crate::directx) depth_dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) view_ubo_resources: Vec<PooledBuffer>,
pub(in crate::directx) view_ubo_ptrs: Vec<*mut u8>,
pub(in crate::directx) view_history: RefCell<ViewHistory>,
pub(in crate::directx) sky: GbufferSky,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct GbufferDeviceCtx<'a> {
pub alloc: &'a DeviceAllocator,
pub info_queue: Option<&'a ID3D12InfoQueue>,
pub hot_reload: bool,
}
#[derive(Clone)]
pub(in crate::directx) struct GbufferPooled {
pub normal_depth: ID3D12Resource,
pub roughness: ID3D12Resource,
pub velocity: ID3D12Resource,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct GbufferExtent {
pub width: u32,
pub height: u32,
}
struct GbufferViewSlots {
normal_depth: (D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_CPU_DESCRIPTOR_HANDLE),
roughness: (D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_CPU_DESCRIPTOR_HANDLE),
velocity: (D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_CPU_DESCRIPTOR_HANDLE),
}
fn write_pooled_views(
device: &ID3D12Device,
slots: GbufferViewSlots,
pooled: &GbufferPooled,
) -> (ID3D12Resource, ID3D12Resource, ID3D12Resource) {
for (res, (rtv, srv), format) in [
(
&pooled.normal_depth,
slots.normal_depth,
GBUFFER_NORMAL_DEPTH_FORMAT,
),
(&pooled.roughness, slots.roughness, GBUFFER_ROUGHNESS_FORMAT),
(&pooled.velocity, slots.velocity, GBUFFER_VELOCITY_FORMAT),
] {
write_format_rtv(device, res, rtv, format);
write_format_srv(device, res, srv, format);
}
(
pooled.normal_depth.clone(),
pooled.roughness.clone(),
pooled.velocity.clone(),
)
}
impl GbufferResources {
pub(in crate::directx) fn new(
ctx: GbufferDeviceCtx,
extent: GbufferExtent,
slots: GbufferSlots,
pooled: &GbufferPooled,
) -> RenderResult<Self> {
let GbufferDeviceCtx {
alloc,
info_queue,
hot_reload,
} = ctx;
let device = alloc.device();
let GbufferExtent { width, height } = extent;
let (normal_depth, roughness, velocity) = write_pooled_views(
device,
GbufferViewSlots {
normal_depth: (slots.normal_depth_rtv, slots.normal_depth_srv.0),
roughness: (slots.roughness_rtv, slots.roughness_srv.0),
velocity: (slots.velocity_rtv, slots.velocity_srv.0),
},
pooled,
);
let depth = create_main_depth_texture(device, width, height, slots.depth_dsv, 1, true)?;
let view_size = align256(GBUFFER_VIEW_UBO_SIZE);
let mut view_ubo_resources: Vec<PooledBuffer> = Vec::with_capacity(FRAMES);
let mut view_ubo_ptrs: Vec<*mut u8> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let buf = alloc.alloc_buffer(
view_size,
D3D12_HEAP_TYPE_UPLOAD,
D3D12_RESOURCE_STATE_GENERIC_READ,
)?;
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
unsafe { buf.Map(0, None, Some(&mut ptr)) }
.map_err(|e| map_hresult(e.code(), "map gbuffer view ubo"))?;
view_ubo_ptrs.push(ptr as *mut u8);
view_ubo_resources.push(buf);
}
let sky = GbufferSky::build(device, info_queue, hot_reload)?;
Ok(Self {
normal_depth,
normal_depth_rtv: slots.normal_depth_rtv,
normal_depth_srv_gpu: slots.normal_depth_srv.1,
roughness,
roughness_rtv: slots.roughness_rtv,
roughness_srv_gpu: slots.roughness_srv.1,
velocity,
velocity_rtv: slots.velocity_rtv,
velocity_srv_gpu: slots.velocity_srv.1,
depth,
depth_dsv: slots.depth_dsv,
view_ubo_resources,
view_ubo_ptrs,
view_history: RefCell::new(ViewHistory::default()),
sky,
})
}
pub(in crate::directx) fn resize_to(
&mut self,
device: &ID3D12Device,
width: u32,
height: u32,
srv_cpu_base: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu_base: SrvSlot,
pooled: &GbufferPooled,
) -> RenderResult<()> {
self.repoint_pooled(device, srv_cpu_base, srv_gpu_base, pooled);
self.depth = create_main_depth_texture(device, width, height, self.depth_dsv, 1, true)?;
Ok(())
}
pub(in crate::directx) fn repoint_pooled(
&mut self,
device: &ID3D12Device,
srv_cpu_base: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_gpu_base: SrvSlot,
pooled: &GbufferPooled,
) {
let srv_cpu = |gpu: SrvSlot| gpu.cpu_in(srv_cpu_base, srv_gpu_base);
let (normal_depth, roughness, velocity) = write_pooled_views(
device,
GbufferViewSlots {
normal_depth: (self.normal_depth_rtv, srv_cpu(self.normal_depth_srv_gpu)),
roughness: (self.roughness_rtv, srv_cpu(self.roughness_srv_gpu)),
velocity: (self.velocity_rtv, srv_cpu(self.velocity_srv_gpu)),
},
pooled,
);
self.normal_depth = normal_depth;
self.roughness = roughness;
self.velocity = velocity;
}
}
pub(in crate::directx) struct GbufferPrepassView {
pub jittered_vp: [[f32; 4]; 4],
pub cur_vp: [[f32; 4]; 4],
pub elapsed: f32,
pub cam_pos: [f32; 3],
}
pub(in crate::directx) struct GbufferPrepassFrame {
pub velocity_active: bool,
pub prime_history: bool,
}
impl DxContext {
pub(in crate::directx) fn reads_motion(&self) -> bool {
self.taa.is_some()
|| self.upscale.backend.is_some()
|| self.ssgi.as_ref().is_some_and(|s| s.settings.contributes())
}
pub(in crate::directx) fn enable_gbuffer_prepass(&mut self) -> RenderResult<()> {
if self.cull.main_bindless_pso.is_some() && self.cull.model_history_pso.is_none() {
let device = self.hw.alloc.device();
let (root_sig, pso) =
build_model_history(device, self.hw.info_queue.as_ref(), self.hot_reload.enabled)?;
let size =
align256((self.cull.bucket_stride * std::mem::size_of::<[[f32; 4]; 4]>()) as u64);
let mut ring = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
ring.push(crate::directx::texture::create_uav_buffer(
device,
size,
D3D12_RESOURCE_STATE_COMMON,
)?);
}
self.cull.prev_model_buffers = ring;
self.cull.model_history_root_sig = Some(root_sig);
self.cull.model_history_pso = Some(pso);
self.state.model_history.borrow_mut().request_prime();
}
self.sync_prepass_psos();
Ok(())
}
pub(in crate::directx) fn encode_gbuffer_prepass(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
view: GbufferPrepassView,
frame: GbufferPrepassFrame,
) {
let GbufferPrepassView {
jittered_vp,
cur_vp,
elapsed,
cam_pos,
} = view;
let GbufferPrepassFrame {
velocity_active,
prime_history,
} = frame;
let gb = match &self.gbuffer {
Some(g) => g,
None => return,
};
let cur = ViewFrame {
vp: cur_vp,
elapsed,
cam_pos,
};
let view_uni = GBufferView::new(
jittered_vp,
self.state.view.matrix,
cur,
gb.view_history.borrow().prev_or(cur),
velocity_active,
);
unsafe {
std::ptr::copy_nonoverlapping(
&view_uni as *const GBufferView as *const u8,
gb.view_ubo_ptrs[frame_idx],
std::mem::size_of::<GBufferView>(),
);
}
let view_gva = com::gpu_va(&gb.view_ubo_resources[frame_idx]);
let w = self.targets.extent.render_width;
let h = self.targets.extent.render_height;
let rtvs = [gb.normal_depth_rtv, gb.roughness_rtv, gb.velocity_rtv];
unsafe {
cmd.OMSetRenderTargets(3, Some(rtvs.as_ptr()), false, Some(&gb.depth_dsv));
cmd.ClearRenderTargetView(gb.normal_depth_rtv, &[0.0_f32; 4], None);
cmd.ClearRenderTargetView(gb.roughness_rtv, &GBUFFER_ROUGHNESS_CLEAR, None);
cmd.ClearRenderTargetView(gb.velocity_rtv, &[0.0_f32; 4], None);
cmd.ClearDepthStencilView(gb.depth_dsv, D3D12_CLEAR_FLAG_DEPTH, DEPTH_CLEAR, 0, None);
let vp = D3D12_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: w as f32,
Height: h as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
cmd.RSSetViewports(&[vp]);
let scissor = RECT {
left: 0,
top: 0,
right: w as i32,
bottom: h as i32,
};
cmd.RSSetScissorRects(&[scissor]);
cmd.IASetPrimitiveTopology(
windows::Win32::Graphics::Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
);
}
self.encode_gbuffer_prepass_gpu_driven(cmd, frame_idx, view_gva, velocity_active);
self.encode_raymarch_prepass(cmd, frame_idx, &view, &view_uni);
if self.draws_sky(self.state.view.mode) {
gb.sky.encode(cmd, view_gva);
self.inc_draw_calls(1);
}
self.encode_model_history(cmd, frame_idx, prime_history);
}
fn encode_model_history(&self, cmd: &ID3D12GraphicsCommandList, frame_idx: usize, prime: bool) {
let (Some(root_sig), Some(pso), true) = (
self.cull.model_history_root_sig.as_ref(),
self.cull.model_history_pso.as_ref(),
frame_idx < self.cull.prev_model_buffers.len(),
) else {
return;
};
let records = self.cull_count();
if records == 0 {
return;
}
let slots = match prime {
true => 0..self.cull.prev_model_buffers.len(),
false => frame_idx..frame_idx + 1,
};
let params = ModelHistoryParams {
record_count: records as u32,
_pad: [0; 3],
};
let object_gva = com::gpu_va(&self.cull.object_buffer_resources[frame_idx]);
unsafe {
cmd.SetPipelineState(pso);
cmd.SetComputeRootSignature(root_sig);
cmd.set_compute_root_constants(0, ¶ms);
cmd.SetComputeRootShaderResourceView(1, object_gva);
for slot in slots {
let history = &self.cull.prev_model_buffers[slot];
cmd.SetComputeRootUnorderedAccessView(2, com::gpu_va(history));
cmd.Dispatch((records as u32).div_ceil(MODEL_HISTORY_THREADGROUP), 1, 1);
cmd.ResourceBarrier(&[uav_barrier(history)]);
}
}
}
fn encode_gbuffer_prepass_gpu_driven(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
view_gva: u64,
velocity_active: bool,
) {
let pso = self.cull.main_prepass_pso.as_ref();
let (Some(root_sig), Some(cmd_sig), true) = (
self.cull.prepass_root_sig.as_ref(),
self.cull.prepass_cmd_sig.as_ref(),
frame_idx < self.cull.prev_model_buffers.len(),
) else {
return;
};
let indirect = &self.cull.indirect_cmd_buffers[frame_idx];
let stride = crate::directx::cull::INDIRECT_COMMAND_STRIDE as usize;
let prefix = self.skinned_record_base();
let object_gva = com::gpu_va(&self.cull.object_buffer_resources[frame_idx]);
let main_view_gva = com::gpu_va(&self.uniforms.view_ubo_resources[frame_idx]);
let frames = self.cull.prev_model_buffers.len();
let prev_model_gva =
com::gpu_va(&self.cull.prev_model_buffers[(frame_idx + frames - 1) % frames]);
let draw_args_gva = com::gpu_va(&self.cull.draw_args_buffer_resources[frame_idx]);
unsafe {
cmd.SetDescriptorHeaps(&[
Some(self.descriptors.srv_heap.clone()),
Some(self.descriptors.sampler_heap.clone()),
]);
cmd.SetGraphicsRootSignature(root_sig);
cmd.IASetVertexBuffers(
0,
Some(&[
self.scene.geometry.vertex_buffer_view,
self.scene.geometry.vertex_buffer_view,
]),
);
cmd.IASetIndexBuffer(Some(&self.scene.geometry.index_buffer_view));
cmd.SetGraphicsRootConstantBufferView(PREPASS_VIEW_PARAM, main_view_gva);
cmd.SetGraphicsRootConstantBufferView(PREPASS_GB_VIEW_PARAM, view_gva);
cmd.SetGraphicsRootShaderResourceView(PREPASS_OBJECTS_PARAM, object_gva);
cmd.SetGraphicsRootShaderResourceView(PREPASS_PREV_MODELS_PARAM, prev_model_gva);
cmd.SetGraphicsRootShaderResourceView(PREPASS_DRAW_ARGS_PARAM, draw_args_gva);
cmd.SetGraphicsRootShaderResourceView(
PREPASS_MATERIAL_PARAMS_PARAM,
self.material_params_gva(frame_idx),
);
cmd.set_graphics_srv_table(
PREPASS_POOL_PARAM,
self.cull.bindless_pool_gpu[self.current_frame],
);
cmd.set_graphics_sampler_table(
PREPASS_SAMPLERS_PARAM,
self.descriptors.linear_sampler_gpu,
);
}
if let Some(pso) = pso {
unsafe {
cmd.SetPipelineState(pso);
cmd.ExecuteIndirect(
cmd_sig,
prefix as u32,
indirect,
0,
None::<&ID3D12Resource>,
0,
);
}
self.inc_draw_calls(1);
}
self.inc_draw_calls(self.execute_prepass_bucket_regions(
cmd,
cmd_sig,
indirect,
prefix as u32,
));
if self.state.draw.n_skinned > 0
&& let (Some(pso), Some(cur_vbv)) = (pso, self.skinned.deformed_vbvs.get(frame_idx))
{
let use_prev_pose = velocity_active
&& self
.skinned
.deformed_primed
.load(std::sync::atomic::Ordering::Relaxed);
let prev_frame_idx = if use_prev_pose {
(frame_idx + FRAMES - 1) % FRAMES
} else {
frame_idx
};
let prev_vbv = self
.skinned
.deformed_vbvs
.get(prev_frame_idx)
.copied()
.unwrap_or(*cur_vbv);
unsafe {
cmd.SetPipelineState(pso);
cmd.IASetVertexBuffers(0, Some(&[*cur_vbv, prev_vbv]));
cmd.IASetIndexBuffer(Some(&self.skinned.index_buffer_view));
cmd.ExecuteIndirect(
cmd_sig,
self.state.draw.n_skinned as u32,
indirect,
(prefix * stride) as u64,
None::<&ID3D12Resource>,
0,
);
}
self.inc_draw_calls(1);
self.skinned
.deformed_primed
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
}