use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use concinnity_core::render::reflection_probe::PrefilterPlan;
use concinnity_core::render::uniforms::ProbePrefilterParams;
use super::com;
use super::context::DxContext;
use super::pipeline::serialize_desc_and_create;
use super::slang_builtins::SlangCompile;
pub(in crate::directx) const PROBE_CUBE_FORMAT: DXGI_FORMAT = DXGI_FORMAT_R16G16B16A16_FLOAT;
pub(in crate::directx) const PROBE_MAX_MIPS: usize = 12;
const PREFILTER_TILE: u32 = 8;
const PREFILTER_PARAM_DWORDS: u32 = (size_of::<ProbePrefilterParams>() / 4) as u32;
pub(in crate::directx) struct ProbePrefilterPipelines {
mip_root: ID3D12RootSignature,
ggx_root: ID3D12RootSignature,
mip0: ID3D12PipelineState,
downsample: ID3D12PipelineState,
ggx: ID3D12PipelineState,
}
pub(in crate::directx) fn typed_uav_load_supported(device: &ID3D12Device) -> bool {
let mut options = D3D12_FEATURE_DATA_D3D12_OPTIONS::default();
let ok = unsafe {
device.CheckFeatureSupport(
D3D12_FEATURE_D3D12_OPTIONS,
&mut options as *mut _ as *mut std::ffi::c_void,
size_of::<D3D12_FEATURE_DATA_D3D12_OPTIONS>() as u32,
)
};
ok.is_ok() && options.TypedUAVLoadAdditionalFormats.as_bool()
}
impl ProbePrefilterPipelines {
pub(in crate::directx) fn new(device: &ID3D12Device, hot_reload: bool) -> Result<Self, String> {
use super::slang_builtins;
let mip_root = create_mip_root_signature(device)?;
let ggx_root = create_ggx_root_signature(device)?;
let mip0 = create_pso(
device,
&mip_root,
&slang_builtins::PROBE_MIP0.compile(hot_reload)?,
"probe_mip0",
)?;
let downsample = create_pso(
device,
&mip_root,
&slang_builtins::PROBE_DOWNSAMPLE.compile(hot_reload)?,
"probe_downsample",
)?;
let ggx = create_pso(
device,
&ggx_root,
&slang_builtins::PROBE_GGX.compile(hot_reload)?,
"probe_ggx",
)?;
Ok(Self {
mip_root,
ggx_root,
mip0,
downsample,
ggx,
})
}
}
pub(in crate::directx) struct PrefilterGpu {
capture: ID3D12Resource,
probe: ID3D12Resource,
mips: u32,
}
impl PrefilterGpu {
pub(in crate::directx) fn new(
ctx: &DxContext,
plan: &PrefilterPlan,
) -> Result<PrefilterGpu, String> {
let mips = plan.mips();
if mips as usize > PROBE_MAX_MIPS {
return Err(format!(
"probe: {mips} mips exceeds the {PROBE_MAX_MIPS} descriptors reserved for one bake"
));
}
let capture = create_cube(
&ctx.device,
plan.face_size(),
mips,
D3D12_RESOURCE_STATE_COPY_DEST,
"probe capture cube",
)?;
let probe = create_cube(
&ctx.device,
plan.face_size(),
mips,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
"probe cube",
)?;
let device = &ctx.device;
let d = &ctx.descriptors;
write_cube_srv(device, &capture, mips, ctx.probe_capture_srv_cpu());
for mip in 0..mips {
write_cube_mip_uav(
device,
&capture,
mip,
cpu_slot(ctx, d.probe_capture_uav_base_slot + mip as usize),
);
write_cube_mip_uav(
device,
&probe,
mip,
cpu_slot(ctx, d.probe_cube_uav_base_slot + mip as usize),
);
}
write_cube_mip_uav(device, &capture, 0, cpu_slot(ctx, d.probe_mip0_pair_slot));
write_cube_mip_uav(device, &probe, 0, cpu_slot(ctx, d.probe_mip0_pair_slot + 1));
Ok(PrefilterGpu {
capture,
probe,
mips,
})
}
pub(in crate::directx) fn capture(&self) -> &ID3D12Resource {
&self.capture
}
pub(in crate::directx) fn probe(&self) -> &ID3D12Resource {
&self.probe
}
pub(in crate::directx) fn mips(&self) -> u32 {
self.mips
}
pub(in crate::directx) fn into_probe_cube(self) -> ID3D12Resource {
self.probe
}
}
impl DxContext {
pub(in crate::directx) fn probe_capture_srv_cpu(&self) -> D3D12_CPU_DESCRIPTOR_HANDLE {
cpu_slot(self, self.descriptors.probe_capture_srv_slot)
}
pub(in crate::directx) fn encode_probe_pyramid(
&self,
cmd: &ID3D12GraphicsCommandList,
gpu: &PrefilterGpu,
plan: &PrefilterPlan,
) -> Result<(), String> {
let pipelines = self
.probe
.prefilter
.as_ref()
.ok_or("probe: prefilter pipelines missing")?;
unsafe {
cmd.ResourceBarrier(&[super::texture::transition_barrier(
gpu.capture(),
D3D12_RESOURCE_STATE_COPY_DEST,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
)]);
cmd.SetComputeRootSignature(&pipelines.mip_root);
}
let d = &self.descriptors;
self.dispatch_prefilter(
cmd,
&pipelines.mip0,
gpu_slot(self, d.probe_mip0_pair_slot),
None,
&plan.mip0_params(),
plan.face_size(),
);
for mip in 1..plan.mips() {
uav_barrier(cmd, gpu.capture());
self.dispatch_prefilter(
cmd,
&pipelines.downsample,
gpu_slot(self, d.probe_capture_uav_base_slot + (mip - 1) as usize),
None,
&plan.downsample_params(mip),
plan.mip_face_size(mip),
);
}
unsafe {
cmd.ResourceBarrier(&[super::texture::transition_barrier(
gpu.capture(),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
)]);
}
Ok(())
}
pub(in crate::directx) fn encode_probe_ggx_mip(
&self,
cmd: &ID3D12GraphicsCommandList,
plan: &PrefilterPlan,
dst_mip: u32,
) -> Result<(), String> {
let pipelines = self
.probe
.prefilter
.as_ref()
.ok_or("probe: prefilter pipelines missing")?;
unsafe { cmd.SetComputeRootSignature(&pipelines.ggx_root) };
let d = &self.descriptors;
self.dispatch_prefilter(
cmd,
&pipelines.ggx,
gpu_slot(self, d.probe_cube_uav_base_slot + dst_mip as usize),
Some(gpu_slot(self, d.probe_capture_srv_slot)),
&plan.ggx_params(dst_mip),
plan.mip_face_size(dst_mip),
);
Ok(())
}
fn dispatch_prefilter(
&self,
cmd: &ID3D12GraphicsCommandList,
pso: &ID3D12PipelineState,
uav_table: D3D12_GPU_DESCRIPTOR_HANDLE,
srv_table: Option<D3D12_GPU_DESCRIPTOR_HANDLE>,
params: &ProbePrefilterParams,
size: u32,
) {
let groups = size.div_ceil(PREFILTER_TILE).max(1);
unsafe {
cmd.SetPipelineState(pso);
cmd.SetComputeRoot32BitConstants(
0,
PREFILTER_PARAM_DWORDS,
params as *const ProbePrefilterParams as *const std::ffi::c_void,
0,
);
match srv_table {
Some(srv) => {
cmd.SetComputeRootDescriptorTable(1, srv);
cmd.SetComputeRootDescriptorTable(2, uav_table);
}
None => cmd.SetComputeRootDescriptorTable(1, uav_table),
}
cmd.Dispatch(groups, groups, 6);
}
}
}
fn create_mip_root_signature(device: &ID3D12Device) -> Result<ID3D12RootSignature, String> {
let uav_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
NumDescriptors: 2,
BaseShaderRegister: 0, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let params = [root_constants(), descriptor_table(&uav_range)];
let desc = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: params.len() as u32,
pParameters: params.as_ptr(),
Flags: D3D12_ROOT_SIGNATURE_FLAG_NONE,
..Default::default()
};
serialize_desc_and_create(device, &desc, "probe prefilter mip root sig")
}
fn create_ggx_root_signature(device: &ID3D12Device) -> Result<ID3D12RootSignature, String> {
let srv_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
NumDescriptors: 1,
BaseShaderRegister: 0, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let uav_range = D3D12_DESCRIPTOR_RANGE {
RangeType: D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
NumDescriptors: 1,
BaseShaderRegister: 0, RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
};
let params = [
root_constants(),
descriptor_table(&srv_range),
descriptor_table(&uav_range),
];
let sampler = 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: 0,
RegisterSpace: 0,
ShaderVisibility: D3D12_SHADER_VISIBILITY_ALL,
..Default::default()
};
let desc = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: params.len() as u32,
pParameters: params.as_ptr(),
NumStaticSamplers: 1,
pStaticSamplers: &sampler,
Flags: D3D12_ROOT_SIGNATURE_FLAG_NONE,
};
serialize_desc_and_create(device, &desc, "probe prefilter ggx root sig")
}
fn root_constants() -> D3D12_ROOT_PARAMETER {
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 0,
RegisterSpace: 0,
Num32BitValues: PREFILTER_PARAM_DWORDS,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_ALL,
}
}
fn descriptor_table(range: &D3D12_DESCRIPTOR_RANGE) -> D3D12_ROOT_PARAMETER {
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_ALL,
}
}
fn create_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
cs: &[u8],
label: &str,
) -> Result<ID3D12PipelineState, String> {
let desc = D3D12_COMPUTE_PIPELINE_STATE_DESC {
pRootSignature: com::borrowed(root_sig),
CS: D3D12_SHADER_BYTECODE {
pShaderBytecode: cs.as_ptr() as _,
BytecodeLength: cs.len(),
},
..Default::default()
};
unsafe { super::pso_library::create_compute(device, &desc) }
.map_err(|e| format!("create {label} PSO: {e}"))
}
fn create_cube(
device: &ID3D12Device,
face_size: u32,
mips: u32,
state: D3D12_RESOURCE_STATES,
label: &str,
) -> Result<ID3D12Resource, String> {
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: face_size as u64,
Height: face_size,
DepthOrArraySize: 6,
MipLevels: mips as u16,
Format: PROBE_CUBE_FORMAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
..Default::default()
};
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let mut cube: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
state,
None,
&mut cube,
)
}
.map_err(|e| format!("create {label}: {e}"))?;
cube.ok_or_else(|| format!("create {label} returned None"))
}
fn write_cube_srv(
device: &ID3D12Device,
resource: &ID3D12Resource,
mips: u32,
srv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let desc = D3D12_SHADER_RESOURCE_VIEW_DESC {
Format: PROBE_CUBE_FORMAT,
ViewDimension: D3D12_SRV_DIMENSION_TEXTURECUBE,
Shader4ComponentMapping: D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Anonymous: D3D12_SHADER_RESOURCE_VIEW_DESC_0 {
TextureCube: D3D12_TEXCUBE_SRV {
MostDetailedMip: 0,
MipLevels: mips,
ResourceMinLODClamp: 0.0,
},
},
};
unsafe { device.CreateShaderResourceView(resource, Some(&desc), srv_cpu) };
}
fn write_cube_mip_uav(
device: &ID3D12Device,
resource: &ID3D12Resource,
mip: u32,
uav_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let desc = D3D12_UNORDERED_ACCESS_VIEW_DESC {
Format: PROBE_CUBE_FORMAT,
ViewDimension: D3D12_UAV_DIMENSION_TEXTURE2DARRAY,
Anonymous: D3D12_UNORDERED_ACCESS_VIEW_DESC_0 {
Texture2DArray: D3D12_TEX2D_ARRAY_UAV {
MipSlice: mip,
FirstArraySlice: 0,
ArraySize: 6,
PlaneSlice: 0,
},
},
};
unsafe { device.CreateUnorderedAccessView(resource, None, Some(&desc), uav_cpu) };
}
fn uav_barrier(cmd: &ID3D12GraphicsCommandList, resource: &ID3D12Resource) {
let 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),
}),
},
};
unsafe { cmd.ResourceBarrier(&[barrier]) };
}
fn cpu_slot(ctx: &DxContext, slot: usize) -> D3D12_CPU_DESCRIPTOR_HANDLE {
let base = unsafe {
ctx.descriptors
.srv_heap
.GetCPUDescriptorHandleForHeapStart()
};
D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: base.ptr + slot * ctx.descriptors.srv_descriptor_size,
}
}
fn gpu_slot(ctx: &DxContext, slot: usize) -> D3D12_GPU_DESCRIPTOR_HANDLE {
let base = unsafe {
ctx.descriptors
.srv_heap
.GetGPUDescriptorHandleForHeapStart()
};
D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: base.ptr + (slot * ctx.descriptors.srv_descriptor_size) as u64,
}
}