use concinnity_core::gfx::render_types::{FogFroxelParams, FogParams};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::render_graph::{FOG_FROXEL_X, FOG_FROXEL_Y, FOG_FROXEL_Z};
use concinnity_core::render::volumetric_fog;
use concinnity_core::transform::mat4_inverse;
use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::com;
use crate::directx::builtin_shaders;
use crate::directx::builtin_shaders::CompileProgram;
use crate::directx::context::{DxContext, FRAMES, align256, dump_on_err};
use crate::directx::descriptor_slot::DescriptorTables;
use crate::directx::descriptor_slot::SrvSlot;
use crate::directx::error::map_hresult;
use crate::directx::pso::{Blend, GraphicsPso, Raster, compute_pso};
use crate::directx::root_sig::{RootSig, SamplerState, Visibility};
use crate::directx::texture::HDR_FORMAT;
pub(in crate::directx) struct FogState {
pub resources: Option<FogResources>,
pub settings: Option<volumetric_fog::FogSettings>,
pub sun_dir: [f32; 3],
pub sun_color: [f32; 3],
}
pub(in crate::directx) fn compile_fog_shaders(
msaa_samples: u32,
hot_reload: bool,
) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let vs = builtin_shaders::FULLSCREEN_VERT.compile(hot_reload)?;
let ps = builtin_shaders::FOG_FRAG
.at(msaa_samples > 1)
.compile(hot_reload)?;
Ok((vs, ps))
}
pub(in crate::directx) fn compile_fog_froxel_shader(hot_reload: bool) -> RenderResult<Vec<u8>> {
builtin_shaders::FOG_FROXEL.compile(hot_reload)
}
pub(in crate::directx) fn rebuild_fog_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
msaa_samples: u32,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> RenderResult<ID3D12PipelineState> {
let (vs, ps) = compile_fog_shaders(msaa_samples, hot_reload)?;
dump_on_err(info_queue, create_fog_pso(device, root_sig, &vs, &ps))
}
pub(in crate::directx) fn rebuild_fog_froxel_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> RenderResult<ID3D12PipelineState> {
let cs = compile_fog_froxel_shader(hot_reload)?;
dump_on_err(info_queue, create_fog_froxel_pso(device, root_sig, &cs))
}
fn create_fog_root_signature(device: &ID3D12Device) -> RenderResult<ID3D12RootSignature> {
RootSig::new()
.cbv(0, Visibility::Pixel)
.cbv(1, Visibility::Pixel)
.srv_table(0, 1, Visibility::Pixel)
.srv_table(1, 1, Visibility::Pixel)
.static_sampler(
SamplerState::LinearClampTransparentBorder,
0,
Visibility::Pixel,
)
.build(device, "fog root sig")
}
fn create_fog_froxel_root_signature(device: &ID3D12Device) -> RenderResult<ID3D12RootSignature> {
RootSig::new()
.cbv(0, Visibility::All)
.cbv(1, Visibility::All)
.cbv(2, Visibility::All)
.srv_table(0, 1, Visibility::All)
.uav_table(0, 1, Visibility::All)
.static_sampler(SamplerState::ShadowCompare, 0, Visibility::All)
.build(device, "fog froxel root sig")
}
fn create_fog_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
) -> RenderResult<ID3D12PipelineState> {
GraphicsPso::fullscreen(root_sig, vs, ps, HDR_FORMAT, Blend::PremultipliedOver)
.raster(Raster {
depth_clip: false,
..Raster::default()
})
.build(device, "fog")
}
fn create_fog_froxel_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
cs: &[u8],
) -> RenderResult<ID3D12PipelineState> {
compute_pso(device, root_sig, cs, "fog froxel")
}
fn create_fog_froxel_volume(
device: &ID3D12Device,
uav_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) -> RenderResult<ID3D12Resource> {
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE3D,
Width: FOG_FROXEL_X as u64,
Height: FOG_FROXEL_Y,
DepthOrArraySize: FOG_FROXEL_Z as u16,
MipLevels: 1,
Format: DXGI_FORMAT_R16G16B16A16_FLOAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
..Default::default()
};
let mut tex_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
None,
&mut tex_opt,
)
}
.map_err(|e| map_hresult(e.code(), "create fog froxel volume"))?;
let resource = tex_opt
.ok_or_else(|| RenderError::Other("create fog froxel volume returned None".to_string()))?;
let uav_desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: DXGI_FORMAT_R16G16B16A16_FLOAT,
ViewDimension: D3D12_UAV_DIMENSION_TEXTURE3D,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Texture3D: D3D12_TEX3D_UAV {
MipSlice: 0,
FirstWSlice: 0,
WSize: FOG_FROXEL_Z,
},
},
};
unsafe {
device.CreateUnorderedAccessView(&resource, None, Some(&uav_desc), uav_cpu);
}
let srv_desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: DXGI_FORMAT_R16G16B16A16_FLOAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURE3D,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
Texture3D: D3D12_TEX3D_SRV {
MostDetailedMip: 0,
MipLevels: 1,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(&resource, Some(&srv_desc), srv_cpu) };
Ok(resource)
}
pub(in crate::directx) struct FogResources {
pub(in crate::directx) root_sig: ID3D12RootSignature,
pub(in crate::directx) pso: ID3D12PipelineState,
pub(in crate::directx) froxel_root_sig: ID3D12RootSignature,
pub(in crate::directx) froxel_pso: ID3D12PipelineState,
pub(in crate::directx) params_ubo_resources: Vec<PooledBuffer>,
pub(in crate::directx) params_ubo_ptrs: Vec<*mut u8>,
pub(in crate::directx) froxel_params_ubo_resources: Vec<PooledBuffer>,
pub(in crate::directx) froxel_params_ubo_ptrs: Vec<*mut u8>,
pub(in crate::directx) volume_resource: ID3D12Resource,
pub(in crate::directx) volume_uav_gpu: SrvSlot,
pub(in crate::directx) volume_srv_gpu: SrvSlot,
pub(in crate::directx) depth_srv_gpu: SrvSlot,
pub(in crate::directx) shadow_srv_gpu: SrvSlot,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct FogVolumeDescriptors {
pub uav_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub uav_gpu: SrvSlot,
pub srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
pub srv_gpu: SrvSlot,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct FogShaderResourceHandles {
pub depth_srv_gpu: SrvSlot,
pub shadow_srv_gpu: SrvSlot,
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct FogDeviceParams {
pub msaa_samples: u32,
pub hot_reload: bool,
}
impl FogResources {
pub(in crate::directx) fn new(
alloc: &DeviceAllocator,
volume: FogVolumeDescriptors,
shader_resources: FogShaderResourceHandles,
params: FogDeviceParams,
info_queue: Option<&ID3D12InfoQueue>,
) -> RenderResult<Self> {
let device = alloc.device();
let FogVolumeDescriptors {
uav_cpu: volume_uav_cpu,
uav_gpu: volume_uav_gpu,
srv_cpu: volume_srv_cpu,
srv_gpu: volume_srv_gpu,
} = volume;
let FogShaderResourceHandles {
depth_srv_gpu,
shadow_srv_gpu,
} = shader_resources;
let FogDeviceParams {
msaa_samples,
hot_reload,
} = params;
let (vs, ps) = compile_fog_shaders(msaa_samples, hot_reload)?;
let cs = compile_fog_froxel_shader(hot_reload)?;
let root_sig = dump_on_err(info_queue, create_fog_root_signature(device))?;
let pso = dump_on_err(info_queue, create_fog_pso(device, &root_sig, &vs, &ps))?;
let froxel_root_sig = dump_on_err(info_queue, create_fog_froxel_root_signature(device))?;
let froxel_pso = dump_on_err(
info_queue,
create_fog_froxel_pso(device, &froxel_root_sig, &cs),
)?;
let volume_resource = create_fog_froxel_volume(device, volume_uav_cpu, volume_srv_cpu)?;
let params_ubo_size = align256(std::mem::size_of::<FogParams>() as u64);
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 = alloc.alloc_buffer(
params_ubo_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 fog params ubo"))?;
params_ubo_ptrs.push(ptr as *mut u8);
params_ubo_resources.push(buf);
}
let froxel_ubo_size = align256(std::mem::size_of::<FogFroxelParams>() as u64);
let mut froxel_params_ubo_resources: Vec<PooledBuffer> = Vec::with_capacity(FRAMES);
let mut froxel_params_ubo_ptrs: Vec<*mut u8> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let buf = alloc.alloc_buffer(
froxel_ubo_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 fog froxel params ubo"))?;
froxel_params_ubo_ptrs.push(ptr as *mut u8);
froxel_params_ubo_resources.push(buf);
}
Ok(Self {
root_sig,
pso,
froxel_root_sig,
froxel_pso,
params_ubo_resources,
params_ubo_ptrs,
froxel_params_ubo_resources,
froxel_params_ubo_ptrs,
volume_resource,
volume_uav_gpu,
volume_srv_gpu,
depth_srv_gpu,
shadow_srv_gpu,
})
}
}
impl DxContext {
pub(crate) fn update_fog_settings(&mut self, settings: Option<volumetric_fog::FogSettings>) {
if settings.is_some() && self.fog.resources.is_none() {
tracing::warn!(
"VolumetricFog hot-reload: world started without fog, so the fog \
pipeline was never built: re-enabling fog mid-run is not \
supported (relaunch required). Ignoring update."
);
return;
}
self.fog.settings = settings;
}
fn fog_froxel_params(&self, near: f32) -> Option<FogFroxelParams> {
let fog = self.fog.settings?;
Some(FogFroxelParams {
view: self.state.view.matrix,
froxel_dims: [FOG_FROXEL_X, FOG_FROXEL_Y, FOG_FROXEL_Z],
_pad_align: 0,
z_near: near.max(1e-3),
z_far: fog.max_distance,
_pad: [0.0; 2],
})
}
pub(in crate::directx) fn encode_fog_froxel(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
near: f32,
vp: [[f32; 4]; 4],
cam_pos: [f32; 3],
shadow_ubo_gva: u64,
) {
let fog_settings = match &self.fog.settings {
Some(s) => *s,
None => return,
};
let fog = match &self.fog.resources {
Some(f) => f,
None => return,
};
let froxel_params = match self.fog_froxel_params(near) {
Some(p) => p,
None => return,
};
let inv_vp = mat4_inverse(vp);
let viewport = [
self.targets.extent.render_width as f32,
self.targets.extent.render_height as f32,
];
let params = fog_settings.params(
inv_vp,
cam_pos,
self.fog.sun_dir,
self.fog.sun_color,
viewport,
);
unsafe {
std::ptr::copy_nonoverlapping(
¶ms as *const FogParams as *const u8,
fog.params_ubo_ptrs[frame_idx],
std::mem::size_of::<FogParams>(),
);
std::ptr::copy_nonoverlapping(
&froxel_params as *const FogFroxelParams as *const u8,
fog.froxel_params_ubo_ptrs[frame_idx],
std::mem::size_of::<FogFroxelParams>(),
);
}
let params_gva = com::gpu_va(&fog.params_ubo_resources[frame_idx]);
let froxel_params_gva = com::gpu_va(&fog.froxel_params_ubo_resources[frame_idx]);
unsafe {
cmd.SetComputeRootSignature(&fog.froxel_root_sig);
cmd.SetPipelineState(&fog.froxel_pso);
cmd.SetDescriptorHeaps(&[Some(self.descriptors.srv_heap.clone())]);
cmd.SetComputeRootConstantBufferView(0, params_gva);
cmd.SetComputeRootConstantBufferView(1, froxel_params_gva);
cmd.SetComputeRootConstantBufferView(2, shadow_ubo_gva);
cmd.set_compute_srv_table(3, fog.shadow_srv_gpu);
cmd.set_compute_srv_table(4, fog.volume_uav_gpu);
let groups_x = FOG_FROXEL_X.div_ceil(8);
let groups_y = FOG_FROXEL_Y.div_ceil(8);
cmd.Dispatch(groups_x, groups_y, 1);
}
}
pub(in crate::directx) fn encode_fog(
&self,
cmd: &ID3D12GraphicsCommandList,
frame_idx: usize,
_vp: [[f32; 4]; 4],
_cam_pos: [f32; 3],
) {
let _ = match &self.fog.settings {
Some(s) => *s,
None => return,
};
let fog = match &self.fog.resources {
Some(f) => f,
None => return,
};
let params_gva = com::gpu_va(&fog.params_ubo_resources[frame_idx]);
let froxel_params_gva = com::gpu_va(&fog.froxel_params_ubo_resources[frame_idx]);
let scene_rtv = self.hdr_scene_rtv();
let w = self.targets.extent.render_width;
let h = self.targets.extent.render_height;
unsafe {
cmd.OMSetRenderTargets(1, Some(&scene_rtv), false, None);
let vp_state = 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_state]);
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,
);
cmd.SetPipelineState(&fog.pso);
cmd.SetGraphicsRootSignature(&fog.root_sig);
cmd.SetDescriptorHeaps(&[Some(self.descriptors.srv_heap.clone())]);
cmd.SetGraphicsRootConstantBufferView(0, params_gva);
cmd.SetGraphicsRootConstantBufferView(1, froxel_params_gva);
cmd.set_graphics_srv_table(2, fog.depth_srv_gpu);
cmd.set_graphics_srv_table(3, fog.volume_srv_gpu);
cmd.DrawInstanced(3, 1, 0, 0);
}
}
}