use concinnity_core::gfx::transform::IDENTITY;
use std::cell::RefCell;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use crate::directx::allocator::{DeviceAllocator, PooledBuffer};
use crate::directx::com;
use crate::directx::context::{DxContext, FRAMES, align256, dump_on_err};
use crate::directx::pipeline::{
main_input_layout, serialize_and_create_root_sig, skinned_input_layout,
};
use crate::directx::slang_builtins;
use crate::directx::texture::{
create_buffer, 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 = 256;
pub(in crate::directx) use concinnity_render::uniforms::GBufferModel;
pub(in crate::directx) use concinnity_render::uniforms::GBufferView;
struct GbufferShaders {
vs_static: Vec<u8>,
vs_instanced: Vec<u8>,
vs_skinned: Vec<u8>,
ps: Vec<u8>,
}
fn compile_gbuffer_shaders(
need_instanced: bool,
need_skinned: bool,
hot_reload: bool,
) -> Result<GbufferShaders, String> {
Ok(GbufferShaders {
vs_static: slang_builtins::GBUFFER_PREPASS_VERT.compile(hot_reload)?,
vs_instanced: if need_instanced {
slang_builtins::GBUFFER_PREPASS_VERT_INSTANCED.compile(hot_reload)?
} else {
Vec::new()
},
vs_skinned: if need_skinned {
slang_builtins::GBUFFER_PREPASS_VERT_SKINNED.compile(hot_reload)?
} else {
Vec::new()
},
ps: slang_builtins::GBUFFER_PREPASS_FRAG.compile(hot_reload)?,
})
}
fn create_gbuffer_root_signature(device: &ID3D12Device) -> Result<ID3D12RootSignature, String> {
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_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 1,
RegisterSpace: 0,
Num32BitValues: 32,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 0,
RegisterSpace: 0,
Num32BitValues: 4,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
];
serialize_and_create_root_sig(device, ¶ms, "gbuffer prepass root sig")
}
fn create_gbuffer_instanced_root_signature(
device: &ID3D12Device,
) -> Result<ID3D12RootSignature, String> {
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_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_SRV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: 0,
RegisterSpace: 0,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 0,
RegisterSpace: 0,
Num32BitValues: 4,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
];
serialize_and_create_root_sig(device, ¶ms, "gbuffer prepass instanced root sig")
}
fn create_gbuffer_skinned_root_signature(
device: &ID3D12Device,
) -> Result<ID3D12RootSignature, String> {
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_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 1,
RegisterSpace: 0,
Num32BitValues: 32,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_SRV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: 0,
RegisterSpace: 0,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_SRV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: 1,
RegisterSpace: 0,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 0,
RegisterSpace: 0,
Num32BitValues: 4,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
];
serialize_and_create_root_sig(device, ¶ms, "gbuffer prepass skinned root sig")
}
fn create_gbuffer_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
layout: &[D3D12_INPUT_ELEMENT_DESC],
) -> 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(),
},
InputLayout: D3D12_INPUT_LAYOUT_DESC {
pInputElementDescs: layout.as_ptr(),
NumElements: layout.len() as u32,
},
PrimitiveTopologyType: D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 3,
RTVFormats: {
let mut a = [DXGI_FORMAT_UNKNOWN; 8];
a[0] = GBUFFER_NORMAL_DEPTH_FORMAT;
a[1] = GBUFFER_ROUGHNESS_FORMAT;
a[2] = GBUFFER_VELOCITY_FORMAT;
a
},
DSVFormat: DXGI_FORMAT_D32_FLOAT,
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: true.into(),
..Default::default()
},
DepthStencilState: D3D12_DEPTH_STENCIL_DESC {
DepthEnable: true.into(),
DepthWriteMask: D3D12_DEPTH_WRITE_MASK_ALL,
DepthFunc: D3D12_COMPARISON_FUNC_LESS,
StencilEnable: false.into(),
..Default::default()
},
BlendState: D3D12_BLEND_DESC {
RenderTarget: {
let mut arr = [D3D12_RENDER_TARGET_BLEND_DESC::default(); 8];
let mt = D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: false.into(),
RenderTargetWriteMask: D3D12_COLOR_WRITE_ENABLE_ALL.0 as u8,
..Default::default()
};
arr[0] = mt;
arr[1] = mt;
arr[2] = mt;
arr
},
..Default::default()
},
..Default::default()
};
unsafe { crate::directx::pso_library::create_graphics(device, &pso_desc) }
.map_err(|e| format!("create gbuffer prepass PSO: {e}"))
}
fn gbuffer_bindless_input_layout() -> Vec<D3D12_INPUT_ELEMENT_DESC> {
vec![
D3D12_INPUT_ELEMENT_DESC {
SemanticName: windows::core::s!("POSITION"),
SemanticIndex: 0,
Format: DXGI_FORMAT_R32G32B32_FLOAT,
InputSlot: 0,
AlignedByteOffset: 0,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
D3D12_INPUT_ELEMENT_DESC {
SemanticName: windows::core::s!("NORMAL"),
SemanticIndex: 0,
Format: DXGI_FORMAT_R32G32B32_FLOAT,
InputSlot: 0,
AlignedByteOffset: 12,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
D3D12_INPUT_ELEMENT_DESC {
SemanticName: windows::core::s!("COLOR"),
SemanticIndex: 0,
Format: DXGI_FORMAT_R32G32B32_FLOAT,
InputSlot: 0,
AlignedByteOffset: 36,
InputSlotClass: D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
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,
},
]
}
fn create_gbuffer_bindless_root_signature(
device: &ID3D12Device,
) -> Result<ID3D12RootSignature, String> {
let params = [
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 0,
RegisterSpace: 0,
Num32BitValues: 1,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
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_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_SRV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: 0,
RegisterSpace: 0,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_SRV,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Descriptor: D3D12_ROOT_DESCRIPTOR {
ShaderRegister: 1,
RegisterSpace: 0,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_VERTEX,
},
];
serialize_and_create_root_sig(device, ¶ms, "gbuffer bindless root sig")
}
type GbufferBindlessPipeline = (
ID3D12RootSignature,
ID3D12PipelineState,
ID3D12CommandSignature,
);
pub(in crate::directx) fn build_gbuffer_bindless(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
hot_reload: bool,
) -> Result<GbufferBindlessPipeline, String> {
let vs = slang_builtins::GBUFFER_BINDLESS_VERT.compile(hot_reload)?;
let ps = slang_builtins::GBUFFER_BINDLESS_FRAG.compile(hot_reload)?;
let root_sig = dump_on_err(info_queue, create_gbuffer_bindless_root_signature(device))?;
let layout = gbuffer_bindless_input_layout();
let pso = dump_on_err(
info_queue,
create_gbuffer_pso(device, &root_sig, &vs, &ps, &layout),
)?;
let cmd_sig = dump_on_err(
info_queue,
crate::directx::cull::create_cull_command_signature(device, &root_sig),
)?;
Ok((root_sig, pso, cmd_sig))
}
#[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, D3D12_GPU_DESCRIPTOR_HANDLE),
pub roughness_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub roughness_srv: (D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE),
pub velocity_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub velocity_srv: (D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE),
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: D3D12_GPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) roughness: ID3D12Resource,
pub(in crate::directx) roughness_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) roughness_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) velocity: ID3D12Resource,
pub(in crate::directx) velocity_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub(in crate::directx) velocity_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
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) root_sig: ID3D12RootSignature,
pub(in crate::directx) pso: ID3D12PipelineState,
pub(in crate::directx) instanced_root_sig: Option<ID3D12RootSignature>,
pub(in crate::directx) instanced_pso: Option<ID3D12PipelineState>,
pub(in crate::directx) skinned_root_sig: Option<ID3D12RootSignature>,
pub(in crate::directx) skinned_pso: Option<ID3D12PipelineState>,
pub(in crate::directx) prev_view_proj: RefCell<[[f32; 4]; 4]>,
pub(in crate::directx) prev_models: RefCell<Vec<[[f32; 4]; 4]>>,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct GbufferDeviceCtx<'a> {
pub alloc: &'a DeviceAllocator,
pub info_queue: Option<&'a ID3D12InfoQueue>,
}
#[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,
pub need_instanced: bool,
pub need_skinned: bool,
pub hot_reload: bool,
}
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,
) -> Result<Self, String> {
let GbufferDeviceCtx { alloc, info_queue } = ctx;
let device = alloc.device();
let GbufferExtent {
width,
height,
need_instanced,
need_skinned,
hot_reload,
} = 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 = create_buffer(
alloc,
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| format!("map gbuffer view ubo: {e}"))?;
view_ubo_ptrs.push(ptr as *mut u8);
view_ubo_resources.push(buf);
}
let shaders = compile_gbuffer_shaders(need_instanced, need_skinned, hot_reload)?;
let root_sig = dump_on_err(info_queue, create_gbuffer_root_signature(device))?;
let static_layout = main_input_layout();
let pso = dump_on_err(
info_queue,
create_gbuffer_pso(
device,
&root_sig,
&shaders.vs_static,
&shaders.ps,
&static_layout,
),
)?;
let (instanced_root_sig, instanced_pso) = if need_instanced {
let rs = dump_on_err(info_queue, create_gbuffer_instanced_root_signature(device))?;
let pso = dump_on_err(
info_queue,
create_gbuffer_pso(
device,
&rs,
&shaders.vs_instanced,
&shaders.ps,
&static_layout,
),
)?;
(Some(rs), Some(pso))
} else {
(None, None)
};
let (skinned_root_sig, skinned_pso) = if need_skinned {
let rs = dump_on_err(info_queue, create_gbuffer_skinned_root_signature(device))?;
let layout = skinned_input_layout();
let pso = dump_on_err(
info_queue,
create_gbuffer_pso(device, &rs, &shaders.vs_skinned, &shaders.ps, &layout),
)?;
(Some(rs), Some(pso))
} else {
(None, None)
};
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,
root_sig,
pso,
instanced_root_sig,
instanced_pso,
skinned_root_sig,
skinned_pso,
prev_view_proj: RefCell::new(IDENTITY),
prev_models: RefCell::new(Vec::new()),
})
}
pub(in crate::directx) fn ensure_skinned_pso(
&mut self,
device: &ID3D12Device,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> Result<(), String> {
let vs = slang_builtins::GBUFFER_PREPASS_VERT_SKINNED.compile(hot_reload)?;
let ps = slang_builtins::GBUFFER_PREPASS_FRAG.compile(hot_reload)?;
let root_sig = match self.skinned_root_sig.as_ref() {
Some(rs) => rs.clone(),
None => dump_on_err(info_queue, create_gbuffer_skinned_root_signature(device))?,
};
let layout = skinned_input_layout();
let pso = dump_on_err(
info_queue,
create_gbuffer_pso(device, &root_sig, &vs, &ps, &layout),
)?;
self.skinned_root_sig = Some(root_sig);
self.skinned_pso = Some(pso);
Ok(())
}
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,
pooled: &GbufferPooled,
) -> Result<(), String> {
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: D3D12_GPU_DESCRIPTOR_HANDLE,
pooled: &GbufferPooled,
) {
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 (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 RebuiltGbufferPipelines {
pub pso: ID3D12PipelineState,
pub instanced_pso: Option<ID3D12PipelineState>,
pub skinned_pso: Option<ID3D12PipelineState>,
}
pub(in crate::directx) fn rebuild_gbuffer_pipelines(
device: &ID3D12Device,
gbuffer: &GbufferResources,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> Result<RebuiltGbufferPipelines, String> {
let shaders = compile_gbuffer_shaders(
gbuffer.instanced_pso.is_some(),
gbuffer.skinned_pso.is_some(),
hot_reload,
)?;
let static_layout = main_input_layout();
let pso = dump_on_err(
info_queue,
create_gbuffer_pso(
device,
&gbuffer.root_sig,
&shaders.vs_static,
&shaders.ps,
&static_layout,
),
)?;
let instanced_pso = match gbuffer.instanced_root_sig.as_ref() {
Some(rs) => Some(dump_on_err(
info_queue,
create_gbuffer_pso(
device,
rs,
&shaders.vs_instanced,
&shaders.ps,
&static_layout,
),
)?),
None => None,
};
let skinned_pso = match gbuffer.skinned_root_sig.as_ref() {
Some(rs) => {
let layout = skinned_input_layout();
Some(dump_on_err(
info_queue,
create_gbuffer_pso(device, rs, &shaders.vs_skinned, &shaders.ps, &layout),
)?)
}
None => None,
};
Ok(RebuiltGbufferPipelines {
pso,
instanced_pso,
skinned_pso,
})
}
pub(in crate::directx) struct GbufferPrepassView<'a> {
pub jittered_vp: [[f32; 4]; 4],
pub cur_vp: [[f32; 4]; 4],
pub frustum: &'a crate::gfx::frustum::Frustum,
pub cam_pos: [f32; 3],
}
struct GbufferLegacyView<'a> {
view_gva: u64,
frustum: &'a crate::gfx::frustum::Frustum,
cam_pos: [f32; 3],
}
impl DxContext {
pub(in crate::directx) fn encode_gbuffer_prepass(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
view: GbufferPrepassView<'_>,
visible: &[u32],
velocity_active: bool,
) {
let GbufferPrepassView {
jittered_vp,
cur_vp,
frustum,
cam_pos,
} = view;
let gb = match &self.gbuffer {
Some(g) => g,
None => return,
};
let prev_vp = if velocity_active {
*gb.prev_view_proj.borrow()
} else {
cur_vp
};
let view_uni = GBufferView {
jittered_vp,
cur_vp,
prev_vp,
view: self.view.matrix,
};
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.extent.render_width;
let h = self.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, 1.0, 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,
);
}
if self.cull.gbuffer_bindless_pso.is_some() && self.cull_count() > 0 {
self.encode_gbuffer_prepass_gpu_driven(
cmd,
frame_idx,
view_gva,
visible,
cam_pos,
velocity_active,
);
} else {
self.encode_gbuffer_prepass_legacy(
cmd,
frame_idx,
GbufferLegacyView {
view_gva,
frustum,
cam_pos,
},
visible,
velocity_active,
);
}
}
fn encode_gbuffer_prepass_legacy(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
view: GbufferLegacyView<'_>,
visible: &[u32],
velocity_active: bool,
) {
let GbufferLegacyView {
view_gva,
frustum,
cam_pos,
} = view;
let gb = match &self.gbuffer {
Some(g) => g,
None => return,
};
unsafe {
cmd.IASetVertexBuffers(0, Some(&[self.geometry.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&self.geometry.index_buffer_view));
cmd.SetPipelineState(&gb.pso);
cmd.SetGraphicsRootSignature(&gb.root_sig);
cmd.SetGraphicsRootConstantBufferView(0, view_gva);
}
{
let prev_models = gb.prev_models.borrow();
self.draw_static_objects(visible, cam_pos, |obj, i, index_offset, index_count| {
let prev_model = if velocity_active {
prev_models.get(i).copied().unwrap_or(obj.model)
} else {
obj.model
};
let push = GBufferModel {
cur_model: obj.model,
prev_model,
};
let mat = [obj.material.roughness, 0.0_f32, 0.0, 0.0];
unsafe {
cmd.SetGraphicsRoot32BitConstants(
1,
32,
&push as *const GBufferModel as *const std::ffi::c_void,
0,
);
cmd.SetGraphicsRoot32BitConstants(
2,
4,
mat.as_ptr() as *const std::ffi::c_void,
0,
);
cmd.DrawIndexedInstanced(
index_count as u32,
1,
index_offset as u32,
obj.base_vertex,
0,
);
}
});
}
if let (Some(inst_pso), Some(inst_root_sig)) =
(gb.instanced_pso.as_ref(), gb.instanced_root_sig.as_ref())
&& !self.instanced.clusters.is_empty()
{
unsafe {
cmd.SetPipelineState(inst_pso);
cmd.SetGraphicsRootSignature(inst_root_sig);
cmd.SetGraphicsRootConstantBufferView(0, view_gva);
}
self.draw_instanced_clusters(
frame_idx,
frustum,
cam_pos,
|_cluster_idx, cluster| {
let mat = [cluster.material.roughness, 0.0_f32, 0.0, 0.0];
unsafe {
cmd.SetGraphicsRoot32BitConstants(
2,
4,
mat.as_ptr() as *const std::ffi::c_void,
0,
);
}
},
|bucket, inst_gva_base| unsafe {
cmd.SetGraphicsRootShaderResourceView(
1,
inst_gva_base + bucket.instance_byte_offset,
);
cmd.DrawIndexedInstanced(
bucket.index_count as u32,
bucket.instance_count,
bucket.index_offset as u32,
0,
0,
);
},
);
}
if let (Some(sk_pso), Some(sk_root_sig)) =
(gb.skinned_pso.as_ref(), gb.skinned_root_sig.as_ref())
&& !self.skinned.draw_objects.is_empty()
{
let prev_frame_idx = (frame_idx + FRAMES - 1) % FRAMES;
unsafe {
cmd.SetPipelineState(sk_pso);
cmd.SetGraphicsRootSignature(sk_root_sig);
cmd.IASetVertexBuffers(0, Some(&[self.skinned.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&self.skinned.index_buffer_view));
cmd.SetGraphicsRootConstantBufferView(0, view_gva);
}
self.draw_skinned_objects(cam_pos, |obj, i, index_offset, index_count| {
let push = GBufferModel {
cur_model: obj.model,
prev_model: obj.model,
};
let mat = [obj.material.roughness, 0.0_f32, 0.0, 0.0];
let prev_slot = if velocity_active {
prev_frame_idx
} else {
frame_idx
};
unsafe {
cmd.SetGraphicsRoot32BitConstants(
1,
32,
&push as *const GBufferModel as *const std::ffi::c_void,
0,
);
cmd.SetGraphicsRootShaderResourceView(2, self.skinned_joint_gva(frame_idx, i));
cmd.SetGraphicsRootShaderResourceView(3, self.skinned_joint_gva(prev_slot, i));
cmd.SetGraphicsRoot32BitConstants(
4,
4,
mat.as_ptr() as *const std::ffi::c_void,
0,
);
cmd.DrawIndexedInstanced(index_count as u32, 1, index_offset as u32, 0, 0);
}
});
unsafe {
cmd.IASetVertexBuffers(0, Some(&[self.geometry.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&self.geometry.index_buffer_view));
}
}
}
fn encode_gbuffer_prepass_gpu_driven(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
view_gva: u64,
visible: &[u32],
cam_pos: [f32; 3],
velocity_active: bool,
) {
let (Some(pso), Some(root_sig), Some(cmd_sig), Some(prev_model_res)) = (
self.cull.gbuffer_bindless_pso.as_ref(),
self.cull.gbuffer_bindless_root_sig.as_ref(),
self.cull.gbuffer_bindless_cmd_sig.as_ref(),
self.cull.prev_model_buffers.get(frame_idx),
) 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]);
self.build_gbuffer_prev_models(frame_idx, velocity_active);
let prev_model_gva = com::gpu_va(prev_model_res);
unsafe {
cmd.SetPipelineState(pso);
cmd.SetGraphicsRootSignature(root_sig);
cmd.IASetVertexBuffers(
0,
Some(&[
self.geometry.vertex_buffer_view,
self.geometry.vertex_buffer_view,
]),
);
cmd.IASetIndexBuffer(Some(&self.geometry.index_buffer_view));
cmd.SetGraphicsRootConstantBufferView(1, view_gva);
cmd.SetGraphicsRootShaderResourceView(2, object_gva);
cmd.SetGraphicsRootShaderResourceView(3, prev_model_gva);
cmd.ExecuteIndirect(
cmd_sig,
prefix as u32,
indirect,
0,
None::<&ID3D12Resource>,
0,
);
}
self.inc_draw_calls(1);
self.inc_draw_calls(self.execute_bucket_regions_shared_pso(
cmd,
cmd_sig,
indirect,
prefix as u32,
));
if self.draw.n_skinned > 0
&& let Some(cur_vbv) = 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.IASetVertexBuffers(0, Some(&[*cur_vbv, prev_vbv]));
cmd.IASetIndexBuffer(Some(&self.skinned.index_buffer_view));
cmd.ExecuteIndirect(
cmd_sig,
self.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);
}
self.encode_gbuffer_legacy_extra(cmd, view_gva, visible, cam_pos, velocity_active);
}
fn encode_gbuffer_legacy_extra(
&self,
cmd: &ID3D12GraphicsCommandList,
view_gva: u64,
visible: &[u32],
cam_pos: [f32; 3],
velocity_active: bool,
) {
if self.clone.slot_by_draw_idx.is_empty() {
return;
}
let gb = match &self.gbuffer {
Some(g) => g,
None => return,
};
unsafe {
cmd.SetPipelineState(&gb.pso);
cmd.SetGraphicsRootSignature(&gb.root_sig);
cmd.IASetVertexBuffers(0, Some(&[self.geometry.vertex_buffer_view]));
cmd.IASetIndexBuffer(Some(&self.geometry.index_buffer_view));
cmd.SetGraphicsRootConstantBufferView(0, view_gva);
}
let prev_models = gb.prev_models.borrow();
self.draw_static_objects(visible, cam_pos, |obj, i, index_offset, index_count| {
if i < self.draw.n_objects {
return; }
if !self.clone.slot_by_draw_idx.contains_key(&i) {
return; }
let prev_model = if velocity_active {
prev_models.get(i).copied().unwrap_or(obj.model)
} else {
obj.model
};
let push = GBufferModel {
cur_model: obj.model,
prev_model,
};
let mat = [obj.material.roughness, 0.0_f32, 0.0, 0.0];
unsafe {
cmd.SetGraphicsRoot32BitConstants(
1,
32,
&push as *const GBufferModel as *const std::ffi::c_void,
0,
);
cmd.SetGraphicsRoot32BitConstants(2, 4, mat.as_ptr() as *const std::ffi::c_void, 0);
cmd.DrawIndexedInstanced(
index_count as u32,
1,
index_offset as u32,
obj.base_vertex,
0,
);
}
self.inc_draw_calls(1);
});
}
fn build_gbuffer_prev_models(&self, frame_idx: usize, velocity_active: bool) {
let Some(&ptr) = self.cull.prev_model_buffer_ptrs.get(frame_idx) else {
return;
};
let Some(gb) = self.gbuffer.as_ref() else {
return;
};
let stride = std::mem::size_of::<[[f32; 4]; 4]>();
let prev_models = gb.prev_models.borrow();
for (i, obj) in self
.draw
.objects
.iter()
.take(self.draw.n_objects)
.enumerate()
{
let prev = if velocity_active {
prev_models.get(i).copied().unwrap_or(obj.model)
} else {
obj.model
};
unsafe {
std::ptr::copy_nonoverlapping(
&prev as *const [[f32; 4]; 4] as *const u8,
ptr.add(i * stride),
stride,
);
}
}
let chunk_base = self.chunk_record_base();
self.for_each_chunk_record(|k, obj| {
let prev = obj.model;
unsafe {
std::ptr::copy_nonoverlapping(
&prev as *const [[f32; 4]; 4] as *const u8,
ptr.add((chunk_base + k) * stride),
stride,
);
}
});
let base = self.skinned_record_base();
for (k, obj) in self
.skinned
.draw_objects
.iter()
.take(self.draw.n_skinned)
.enumerate()
{
let prev = obj.model;
unsafe {
std::ptr::copy_nonoverlapping(
&prev as *const [[f32; 4]; 4] as *const u8,
ptr.add((base + k) * stride),
stride,
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gb_view_uniforms_fits_ubo_allocation() {
assert!(std::mem::size_of::<GBufferView>() as u64 <= align256(GBUFFER_VIEW_UBO_SIZE));
}
}