use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use crate::directx::allocator::{DeviceAllocator, PooledBuffer};
use crate::directx::texture::transition_barrier;
use crate::gfx::fullscreen::{FullscreenPass, encode_fullscreen};
use crate::gfx::render_types::SsgiParams;
use crate::gfx::ssgi::SsgiSettings;
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::slang_builtins;
use crate::directx::slang_builtins::SlangCompile;
use crate::directx::texture::{
HDR_FORMAT, create_buffer, create_rt_target, write_format_rtv, write_format_srv,
};
const SSGI_PARAMS_UBO_SIZE: u64 = 32;
struct SsgiShaders {
vs: Vec<u8>,
gather_ps: Vec<u8>,
composite_ps: Vec<u8>,
}
fn compile_ssgi_shaders(hot_reload: bool) -> Result<SsgiShaders, String> {
Ok(SsgiShaders {
vs: slang_builtins::FULLSCREEN_VERT.compile(hot_reload)?,
gather_ps: slang_builtins::SSGI_GATHER.compile(hot_reload)?,
composite_ps: slang_builtins::SSGI_COMPOSITE.compile(hot_reload)?,
})
}
fn create_ssgi_root_signature(device: &ID3D12Device) -> Result<ID3D12RootSignature, String> {
let t0_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 0, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let t1_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 1, 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: &t0_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: &t1_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
];
let samplers = [0u32, 1].map(|reg| 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 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, "ssgi root sig")
}
fn create_ssgi_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
additive: bool,
) -> Result<ID3D12PipelineState, String> {
let blend = if additive {
D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: true.into(),
SrcBlend: D3D12_BLEND_ONE,
DestBlend: D3D12_BLEND_ONE,
BlendOp: D3D12_BLEND_OP_ADD,
SrcBlendAlpha: D3D12_BLEND_ONE,
DestBlendAlpha: D3D12_BLEND_ONE,
BlendOpAlpha: D3D12_BLEND_OP_ADD,
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as u8,
..Default::default()
}
} else {
D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: false.into(),
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as u8,
..Default::default()
}
};
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] = HDR_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] = blend;
arr
},
..Default::default()
},
..Default::default()
};
unsafe { crate::directx::pso_library::create_graphics(device, &pso_desc) }
.map_err(|e| format!("create ssgi PSO: {e}"))
}
pub(in crate::directx) struct SsgiResources {
pub(in crate::directx) settings: SsgiSettings,
gi: ID3D12Resource,
gi_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
gi_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
params_ubo_resources: Vec<PooledBuffer>,
params_ubo_ptrs: Vec<*mut u8>,
root_sig: ID3D12RootSignature,
gather_pso: ID3D12PipelineState,
composite_pso: ID3D12PipelineState,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct SsgiDevice<'a> {
pub alloc: &'a DeviceAllocator,
pub info_queue: Option<&'a ID3D12InfoQueue>,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct SsgiDescriptors {
pub gi_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub gi_srv: (D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE),
}
impl SsgiResources {
pub(in crate::directx) fn new(
dev: SsgiDevice,
width: u32,
height: u32,
settings: SsgiSettings,
descriptors: SsgiDescriptors,
hot_reload: bool,
) -> Result<Self, String> {
let SsgiDevice { alloc, info_queue } = dev;
let device = alloc.device();
let SsgiDescriptors { gi_rtv, gi_srv } = descriptors;
let (gw, gh) = settings.gi_dimensions(width, height);
let gi = create_rt_target(device, gw, gh, HDR_FORMAT)?;
write_format_rtv(device, &gi, gi_rtv, HDR_FORMAT);
write_format_srv(device, &gi, gi_srv.0, HDR_FORMAT);
let params_size = align256(SSGI_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 ssgi params ubo: {e}"))?;
params_ubo_ptrs.push(ptr as *mut u8);
params_ubo_resources.push(buf);
}
let shaders = compile_ssgi_shaders(hot_reload)?;
let root_sig = dump_on_err(info_queue, create_ssgi_root_signature(device))?;
let gather_pso = dump_on_err(
info_queue,
create_ssgi_pso(device, &root_sig, &shaders.vs, &shaders.gather_ps, false),
)?;
let composite_pso = dump_on_err(
info_queue,
create_ssgi_pso(device, &root_sig, &shaders.vs, &shaders.composite_ps, true),
)?;
Ok(Self {
settings,
gi,
gi_rtv,
gi_srv_gpu: gi_srv.1,
params_ubo_resources,
params_ubo_ptrs,
root_sig,
gather_pso,
composite_pso,
})
}
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,
};
let (gw, gh) = self.settings.gi_dimensions(width, height);
self.gi = create_rt_target(device, gw, gh, HDR_FORMAT)?;
write_format_rtv(device, &self.gi, self.gi_rtv, HDR_FORMAT);
write_format_srv(device, &self.gi, srv_cpu(self.gi_srv_gpu), HDR_FORMAT);
Ok(())
}
}
pub(in crate::directx) struct RebuiltSsgiPipelines {
pub gather_pso: ID3D12PipelineState,
pub composite_pso: ID3D12PipelineState,
}
pub(in crate::directx) fn rebuild_ssgi_pipelines(
device: &ID3D12Device,
ssgi: &SsgiResources,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> Result<RebuiltSsgiPipelines, String> {
let shaders = compile_ssgi_shaders(hot_reload)?;
let gather_pso = dump_on_err(
info_queue,
create_ssgi_pso(
device,
&ssgi.root_sig,
&shaders.vs,
&shaders.gather_ps,
false,
),
)?;
let composite_pso = dump_on_err(
info_queue,
create_ssgi_pso(
device,
&ssgi.root_sig,
&shaders.vs,
&shaders.composite_ps,
true,
),
)?;
Ok(RebuiltSsgiPipelines {
gather_pso,
composite_pso,
})
}
pub(in crate::directx) fn swap_ssgi_pipelines(
ssgi: &mut SsgiResources,
rebuilt: RebuiltSsgiPipelines,
) {
ssgi.gather_pso = rebuilt.gather_pso;
ssgi.composite_pso = rebuilt.composite_pso;
}
impl DxContext {
pub(in crate::directx) fn encode_ssgi(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
fov_y_radians: f32,
aspect: f32,
) {
let Some(ssgi) = &self.ssgi else { return };
let gbuffer_srv = match &self.gbuffer {
Some(g) => g.normal_depth_srv_gpu,
None => return,
};
let params = ssgi.settings.params(fov_y_radians, aspect);
unsafe {
std::ptr::copy_nonoverlapping(
¶ms as *const SsgiParams as *const u8,
ssgi.params_ubo_ptrs[frame_idx],
std::mem::size_of::<SsgiParams>(),
);
}
let params_gva = com::gpu_va(&ssgi.params_ubo_resources[frame_idx]);
let gather_read = transition_barrier(
self.hdr_scene_target(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
);
unsafe { cmd.ResourceBarrier(&[gather_read]) };
encode_fullscreen(
&SsgiPass {
ctx: self,
ssgi,
output: &ssgi.gi,
output_rtv: ssgi.gi_rtv,
graph_driven: false,
pso: &ssgi.gather_pso,
source_srv: self.hdr.srv_gpu,
gbuffer_srv,
params_gva,
},
cmd,
);
let gather_done = transition_barrier(
self.hdr_scene_target(),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET,
);
unsafe { cmd.ResourceBarrier(&[gather_done]) };
encode_fullscreen(
&SsgiPass {
ctx: self,
ssgi,
output: self.hdr_scene_target(),
output_rtv: self.hdr_scene_rtv(),
graph_driven: true,
pso: &ssgi.composite_pso,
source_srv: ssgi.gi_srv_gpu,
gbuffer_srv,
params_gva,
},
cmd,
);
}
}
struct SsgiPass<'a> {
ctx: &'a DxContext,
ssgi: &'a SsgiResources,
output: &'a ID3D12Resource,
output_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
graph_driven: bool,
pso: &'a ID3D12PipelineState,
source_srv: D3D12_GPU_DESCRIPTOR_HANDLE,
gbuffer_srv: D3D12_GPU_DESCRIPTOR_HANDLE,
params_gva: u64,
}
impl FullscreenPass for SsgiPass<'_> {
type Rec = ID3D12GraphicsCommandList;
fn begin(&self, cmd: &Self::Rec) {
if self.graph_driven {
self.ctx
.bind_fullscreen_rt(cmd, self.output, self.output_rtv);
} else {
self.ctx
.begin_fullscreen_rt(cmd, self.output, self.output_rtv);
}
}
fn draw(&self, cmd: &Self::Rec) {
unsafe {
cmd.SetPipelineState(self.pso);
cmd.SetGraphicsRootSignature(&self.ssgi.root_sig);
cmd.SetGraphicsRootConstantBufferView(0, self.params_gva);
cmd.SetGraphicsRootDescriptorTable(1, self.source_srv);
cmd.SetGraphicsRootDescriptorTable(2, self.gbuffer_srv);
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) {
if !self.graph_driven {
self.ctx.end_fullscreen_rt(cmd, self.output);
}
}
}