use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use crate::directx::allocator::{DeviceAllocator, PooledBuffer};
use crate::gfx::fullscreen::{FullscreenPass, encode_fullscreen};
use crate::gfx::render_types::SsrParams;
use crate::directx::com;
use crate::directx::context::{DxContext, FRAMES, align256, dump_on_err};
use crate::directx::pipeline::serialize_desc_and_create;
use crate::directx::post::gbuffer::GbufferResources;
use crate::directx::slang_builtins;
use crate::directx::slang_builtins::SlangCompile;
use crate::directx::texture::{
create_buffer, create_rt_target, write_format_rtv, write_format_srv,
};
pub(crate) const SSR_OUTPUT_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
const SSR_PARAMS_UBO_SIZE: u64 = 96;
const PROBE_CUBE_REGISTER: u32 = 4;
struct SsrShaders {
resolve_vs: Vec<u8>,
resolve_ps: Vec<u8>,
}
fn compile_ssr_shaders(hot_reload: bool) -> Result<SsrShaders, String> {
Ok(SsrShaders {
resolve_vs: slang_builtins::FULLSCREEN_VERT.compile(hot_reload)?,
resolve_ps: slang_builtins::SSR_RESOLVE.compile(hot_reload)?,
})
}
fn create_ssr_resolve_root_signature(device: &ID3D12Device) -> Result<ID3D12RootSignature, String> {
let scene_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 0, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let gbuffer_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 1, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let rough_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 2, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let cube_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 3, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let probe_cube_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: concinnity_core::render::uniforms::MAX_PROBES as u32,
BaseShaderRegister: PROBE_CUBE_REGISTER,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let params = [
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_CBV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: 0,
RegisterSpace: 0,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &scene_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &gbuffer_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &rough_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &cube_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &probe_cube_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_CBV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: 1,
RegisterSpace: 0,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
];
let linear_clamp = |reg: u32| D3D12_STATIC_SAMPLER_DESC {
Filter: D3D12_FILTER_MIN_MAG_MIP_LINEAR,
AddressU: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressV: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
AddressW: D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
ComparisonFunc: D3D12_COMPARISON_FUNC_ALWAYS,
BorderColor: D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK,
MinLOD: 0.0,
MaxLOD: f32::MAX,
ShaderRegister: reg,
RegisterSpace: 0,
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
..Default::default()
};
let samplers: Vec<D3D12_STATIC_SAMPLER_DESC> =
(0..=PROBE_CUBE_REGISTER).map(linear_clamp).collect();
let desc = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: params.len() as u32,
pParameters: params.as_ptr(),
NumStaticSamplers: samplers.len() as u32,
pStaticSamplers: samplers.as_ptr(),
Flags: D3D12_ROOT_SIGNATURE_FLAG_NONE,
};
serialize_desc_and_create(device, &desc, "ssr resolve root sig")
}
fn create_ssr_resolve_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
) -> Result<ID3D12PipelineState, String> {
let pso_desc = D3D12_GRAPHICS_PIPELINE_STATE_DESC {
pRootSignature: com::borrowed(root_sig),
VS: D3D12_SHADER_BYTECODE {
pShaderBytecode: vs.as_ptr() as _,
BytecodeLength: vs.len(),
},
PS: D3D12_SHADER_BYTECODE {
pShaderBytecode: ps.as_ptr() as _,
BytecodeLength: ps.len(),
},
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 1,
RTVFormats: {
let mut a = [DXGI_FORMAT_UNKNOWN; 8];
a[0] = SSR_OUTPUT_FORMAT;
a
},
DSVFormat: DXGI_FORMAT_UNKNOWN,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
SampleMask: u32::MAX,
RasterizerState: D3D12_RASTERIZER_DESC {
FillMode: D3D12_FILL_MODE_SOLID,
CullMode: D3D12_CULL_MODE_NONE,
FrontCounterClockwise: true.into(),
DepthClipEnable: false.into(),
..Default::default()
},
DepthStencilState: D3D12_DEPTH_STENCIL_DESC {
DepthEnable: false.into(),
DepthWriteMask: D3D12_DEPTH_WRITE_MASK_ZERO,
StencilEnable: false.into(),
..Default::default()
},
BlendState: D3D12_BLEND_DESC {
RenderTarget: {
let mut arr = [D3D12_RENDER_TARGET_BLEND_DESC::default(); 8];
arr[0] = D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: false.into(),
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as u8,
..Default::default()
};
arr
},
..Default::default()
},
..Default::default()
};
unsafe { crate::directx::pso_library::create_graphics(device, &pso_desc) }
.map_err(|e| format!("create ssr resolve PSO: {e}"))
}
pub(in crate::directx) struct SsrResolve {
pub(in crate::directx) settings: crate::gfx::ssr::SsrSettings,
pub(in crate::directx) output: ID3D12Resource,
pub(in crate::directx) output_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) output_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) params_ubo_resources: Vec<PooledBuffer>,
pub(in crate::directx) params_ubo_ptrs: Vec<*mut u8>,
pub(in crate::directx) resolve_root_sig: ID3D12RootSignature,
pub(in crate::directx) resolve_pso: ID3D12PipelineState,
}
pub(in crate::directx) struct SsrResources {
pub(in crate::directx) resolve: Option<SsrResolve>,
}
pub(in crate::directx) struct SsrInitInputs {
pub resolve_settings: Option<crate::gfx::ssr::SsrSettings>,
pub output_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub output_srv: (D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE),
}
impl SsrResources {
pub(in crate::directx) fn new(
alloc: &DeviceAllocator,
width: u32,
height: u32,
inputs: SsrInitInputs,
info_queue: Option<&ID3D12InfoQueue>,
hot_reload: bool,
) -> Result<Self, String> {
let device = alloc.device();
let SsrInitInputs {
resolve_settings,
output_rtv,
output_srv,
} = inputs;
let resolve = if let Some(settings) = resolve_settings {
let output = create_rt_target(device, width, height, SSR_OUTPUT_FORMAT)?;
write_format_rtv(device, &output, output_rtv, SSR_OUTPUT_FORMAT);
write_format_srv(device, &output, output_srv.0, SSR_OUTPUT_FORMAT);
let params_size = align256(SSR_PARAMS_UBO_SIZE);
let mut params_ubo_resources: Vec<PooledBuffer> = Vec::with_capacity(FRAMES);
let mut params_ubo_ptrs: Vec<*mut u8> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let buf = create_buffer(
alloc,
params_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| format!("map ssr params ubo: {e}"))?;
params_ubo_ptrs.push(ptr as *mut u8);
params_ubo_resources.push(buf);
}
let shaders = compile_ssr_shaders(hot_reload)?;
let resolve_root_sig =
dump_on_err(info_queue, create_ssr_resolve_root_signature(device))?;
let resolve_pso = dump_on_err(
info_queue,
create_ssr_resolve_pso(
device,
&resolve_root_sig,
&shaders.resolve_vs,
&shaders.resolve_ps,
),
)?;
Some(SsrResolve {
settings,
output,
output_rtv,
output_srv_gpu: output_srv.1,
params_ubo_resources,
params_ubo_ptrs,
resolve_root_sig,
resolve_pso,
})
} else {
None
};
Ok(Self { resolve })
}
}
pub(in crate::directx) struct RebuiltSsrPipelines {
pub resolve_pso: Option<ID3D12PipelineState>,
}
impl SsrResources {
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: D3D12_GPU_DESCRIPTOR_HANDLE,
) -> Result<(), String> {
let srv_cpu = |gpu: D3D12_GPU_DESCRIPTOR_HANDLE| D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: srv_cpu_base.ptr + (gpu.ptr - srv_gpu_base.ptr) as usize,
};
if let Some(r) = self.resolve.as_mut() {
r.output = create_rt_target(device, width, height, SSR_OUTPUT_FORMAT)?;
write_format_rtv(device, &r.output, r.output_rtv, SSR_OUTPUT_FORMAT);
write_format_srv(
device,
&r.output,
srv_cpu(r.output_srv_gpu),
SSR_OUTPUT_FORMAT,
);
}
Ok(())
}
}
pub(in crate::directx) fn rebuild_ssr_pipelines(
device: &ID3D12Device,
ssr: &SsrResources,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> Result<RebuiltSsrPipelines, String> {
let resolve_pso = if let Some(r) = ssr.resolve.as_ref() {
let shaders = compile_ssr_shaders(hot_reload)?;
Some(dump_on_err(
info_queue,
create_ssr_resolve_pso(
device,
&r.resolve_root_sig,
&shaders.resolve_vs,
&shaders.resolve_ps,
),
)?)
} else {
None
};
Ok(RebuiltSsrPipelines { resolve_pso })
}
impl DxContext {
pub(in crate::directx) fn post_scene_target(&self) -> &ID3D12Resource {
match self
.reflection_composite
.as_ref()
.filter(|_| self.reflection_resolve_active())
{
Some(rc) => &rc.output,
None => self.hdr_scene_target(),
}
}
pub(in crate::directx) fn post_scene_rtv(&self) -> D3D12_CPU_DESCRIPTOR_HANDLE {
match self
.reflection_composite
.as_ref()
.filter(|_| self.reflection_resolve_active())
{
Some(rc) => rc.output_rtv,
None => self.hdr_scene_rtv(),
}
}
pub(in crate::directx) fn scene_srv_for_post(&self) -> D3D12_GPU_DESCRIPTOR_HANDLE {
if let Some(up) = &self.upscale.backend {
return up.output_srv_gpu();
}
if let Some(rc) = self.reflection_composite.as_ref()
&& self.reflection_resolve_active()
{
return rc.output_srv_gpu;
}
self.hdr.srv_gpu
}
pub(in crate::directx) fn prefilter_cube_srv_gpu(&self) -> D3D12_GPU_DESCRIPTOR_HANDLE {
let srv_gpu_base = unsafe {
self.descriptors
.srv_heap
.GetGPUDescriptorHandleForHeapStart()
};
D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: srv_gpu_base.ptr + (2 * self.descriptors.srv_descriptor_size) as u64,
}
}
pub(in crate::directx) fn encode_ssr_resolve(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
fov_y_radians: f32,
aspect: f32,
cam_pos: [f32; 3],
) {
let Some(ssr) = &self.ssr else { return };
let Some(resolve) = &ssr.resolve else { return };
let Some(gbuffer) = &self.gbuffer else { return };
let reflection_srv = resolve.output_srv_gpu;
encode_fullscreen(
&SsrResolvePass {
ctx: self,
resolve,
gbuffer,
frame_idx,
fov_y_radians,
aspect,
cam_pos,
},
cmd,
);
self.encode_reflection_composite(cmd, reflection_srv);
}
}
struct SsrResolvePass<'a> {
ctx: &'a DxContext,
resolve: &'a SsrResolve,
gbuffer: &'a GbufferResources,
frame_idx: usize,
fov_y_radians: f32,
aspect: f32,
cam_pos: [f32; 3],
}
impl FullscreenPass for SsrResolvePass<'_> {
type Rec = ID3D12GraphicsCommandList;
fn begin(&self, cmd: &Self::Rec) {
self.ctx
.begin_fullscreen_rt(cmd, &self.resolve.output, self.resolve.output_rtv);
}
fn draw(&self, cmd: &Self::Rec) {
let v = self.ctx.view.matrix;
let inv_view_rot = [
[v[0][0], v[1][0], v[2][0], 0.0],
[v[0][1], v[1][1], v[2][1], 0.0],
[v[0][2], v[1][2], v[2][2], 0.0],
[0.0, 0.0, 0.0, 1.0],
];
let params = self.resolve.settings.params(
self.fov_y_radians,
self.aspect,
inv_view_rot,
self.cam_pos,
self.ctx.env_map.prefilter_mip_count as f32,
);
unsafe {
std::ptr::copy_nonoverlapping(
¶ms as *const SsrParams as *const u8,
self.resolve.params_ubo_ptrs[self.frame_idx],
std::mem::size_of::<SsrParams>(),
);
}
let params_gva = com::gpu_va(&self.resolve.params_ubo_resources[self.frame_idx]);
unsafe {
cmd.SetPipelineState(&self.resolve.resolve_pso);
cmd.SetGraphicsRootSignature(&self.resolve.resolve_root_sig);
cmd.SetGraphicsRootConstantBufferView(0, params_gva);
cmd.SetGraphicsRootDescriptorTable(1, self.ctx.hdr.srv_gpu);
cmd.SetGraphicsRootDescriptorTable(2, self.gbuffer.normal_depth_srv_gpu);
cmd.SetGraphicsRootDescriptorTable(3, self.gbuffer.roughness_srv_gpu);
cmd.SetGraphicsRootDescriptorTable(4, self.ctx.prefilter_cube_srv_gpu());
cmd.SetGraphicsRootDescriptorTable(5, self.ctx.probe_cube_table_gpu());
cmd.SetGraphicsRootConstantBufferView(
6,
com::gpu_va(&self.ctx.probe.set_cbvs[self.frame_idx]),
);
cmd.IASetPrimitiveTopology(
windows::Win32::Graphics::Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
);
cmd.IASetVertexBuffers(0, None);
cmd.IASetIndexBuffer(None);
cmd.DrawInstanced(3, 1, 0, 0);
}
}
fn end(&self, cmd: &Self::Rec) {
self.ctx.end_fullscreen_rt(cmd, &self.resolve.output);
}
}
#[cfg(test)]
mod tests {
#[test]
fn ssr_resolve_shaders_compile() {
if !crate::slangc_gate::slangc_available() {
return;
}
super::compile_ssr_shaders(false).expect("ssr resolve shaders must compile");
}
}