use windows::Win32::Foundation::RECT;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use crate::gfx::render_types::PostProcessParams;
use crate::directx::com;
use crate::directx::context::DxContext;
use crate::directx::pipeline::serialize_desc_and_create;
use crate::directx::slang_builtins;
use crate::directx::texture::{HDR_FORMAT, transition_barrier};
pub(in crate::directx) struct BloomShaders {
pub vs: Vec<u8>,
pub prefilter_ps: Vec<u8>,
pub downsample_ps: Vec<u8>,
pub upsample_ps: Vec<u8>,
}
pub(in crate::directx) fn compile_bloom_shaders(hot_reload: bool) -> Result<BloomShaders, String> {
Ok(BloomShaders {
vs: slang_builtins::FULLSCREEN_VERT.compile(hot_reload)?,
prefilter_ps: slang_builtins::BLOOM_PREFILTER.compile(hot_reload)?,
downsample_ps: slang_builtins::BLOOM_DOWNSAMPLE.compile(hot_reload)?,
upsample_ps: slang_builtins::BLOOM_UPSAMPLE.compile(hot_reload)?,
})
}
pub(in crate::directx) fn create_bloom_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 params = [
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
Anonymous: D3D12_ROOT_PARAMETER_0 {
DescriptorTable: D3D12_ROOT_DESCRIPTOR_TABLE {
NumDescriptorRanges: 1,
pDescriptorRanges: &srv_range,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
D3D12_ROOT_PARAMETER {
ParameterType: D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
Anonymous: D3D12_ROOT_PARAMETER_0 {
Constants: D3D12_ROOT_CONSTANTS {
ShaderRegister: 0,
RegisterSpace: 0,
Num32BitValues: 6,
},
},
ShaderVisibility: D3D12_SHADER_VISIBILITY_PIXEL,
},
];
let static_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_PIXEL,
..Default::default()
};
let desc = D3D12_ROOT_SIGNATURE_DESC {
NumParameters: params.len() as u32,
pParameters: params.as_ptr(),
NumStaticSamplers: 1,
pStaticSamplers: &static_sampler,
Flags: D3D12_ROOT_SIGNATURE_FLAG_NONE,
};
serialize_desc_and_create(device, &desc, "bloom root sig")
}
pub(in crate::directx) fn create_bloom_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
rtv_format: DXGI_FORMAT,
additive: bool,
) -> Result<ID3D12PipelineState, String> {
let blend_rt = D3D12_RENDER_TARGET_BLEND_DESC {
BlendEnable: additive.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()
};
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] = rtv_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: true.into(),
..Default::default()
},
DepthStencilState: D3D12_DEPTH_STENCIL_DESC {
DepthEnable: false.into(),
DepthWriteMask: D3D12_DEPTH_WRITE_MASK_ZERO,
DepthFunc: D3D12_COMPARISON_FUNC_ALWAYS,
StencilEnable: false.into(),
..Default::default()
},
BlendState: D3D12_BLEND_DESC {
RenderTarget: {
let mut arr = [D3D12_RENDER_TARGET_BLEND_DESC::default(); 8];
arr[0] = blend_rt;
arr
},
..Default::default()
},
..Default::default()
};
unsafe { crate::directx::pso_library::create_graphics(device, &pso_desc) }
.map_err(|e| format!("create bloom PSO: {e}"))
}
pub(in crate::directx) fn bloom_mip_count(width: u32, height: u32) -> u32 {
let min_dim = width.min(height).max(1);
let levels = (min_dim as f32).log2().floor() as i32 - 1;
levels.clamp(4, 6) as u32
}
type BloomMips = (Vec<ID3D12Resource>, Vec<(u32, u32)>);
pub(in crate::directx) fn create_bloom_mips(
device: &ID3D12Device,
width: u32,
height: u32,
top: ID3D12Resource,
) -> Result<BloomMips, String> {
let full_w = width.max(1);
let full_h = height.max(1);
let count = bloom_mip_count(full_w, full_h);
create_bloom_mips_at(device, full_w, full_h, count as usize, top)
}
pub(in crate::directx) fn create_bloom_mips_at(
device: &ID3D12Device,
width: u32,
height: u32,
count: usize,
top: ID3D12Resource,
) -> Result<BloomMips, String> {
let full_w = width.max(1);
let full_h = height.max(1);
let heap_props = D3D12_HEAP_PROPERTIES {
Type: D3D12_HEAP_TYPE_DEFAULT,
..Default::default()
};
let clear_value = D3D12_CLEAR_VALUE {
Format: HDR_FORMAT,
Anonymous: D3D12_CLEAR_VALUE_0 { Color: [0.0; 4] },
};
let mut mips = Vec::with_capacity(count);
let mut extents = Vec::with_capacity(count);
let (tw, th) = ((full_w.max(1) >> 1).max(1), (full_h.max(1) >> 1).max(1));
mips.push(top);
extents.push((tw, th));
for i in 1..count {
let mw = (full_w >> (i + 1)).max(1);
let mh = (full_h >> (i + 1)).max(1);
let desc = D3D12_RESOURCE_DESC {
Dimension: D3D12_RESOURCE_DIMENSION_TEXTURE2D,
Width: mw as u64,
Height: mh,
DepthOrArraySize: 1,
MipLevels: 1,
Format: HDR_FORMAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Flags: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
..Default::default()
};
let mut res_opt: Option<ID3D12Resource> = None;
unsafe {
device.CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
Some(&clear_value),
&mut res_opt,
)
}
.map_err(|e| format!("create bloom mip {i}: {e}"))?;
mips.push(res_opt.ok_or_else(|| format!("bloom mip {i} returned None"))?);
extents.push((mw, mh));
}
Ok((mips, extents))
}
pub(in crate::directx) fn write_color_rtv(
device: &ID3D12Device,
resource: &ID3D12Resource,
rtv_cpu: D3D12_CPU_DESCRIPTOR_HANDLE,
) {
let rtv_desc = D3D12_RENDER_TARGET_VIEW_DESC {
Format: HDR_FORMAT,
ViewDimension: D3D12_RTV_DIMENSION_TEXTURE2D,
..Default::default()
};
unsafe { device.CreateRenderTargetView(resource, Some(&rtv_desc), rtv_cpu) };
}
impl crate::gfx::fullscreen::BloomEncoder for DxContext {
type Rec = ID3D12GraphicsCommandList;
type Args = D3D12_GPU_DESCRIPTOR_HANDLE;
fn bloom_mip_count(&self) -> usize {
self.bloom.mips.len()
}
fn begin_bloom(&self, cmd: &Self::Rec, _scene_srv: &Self::Args) {
unsafe {
cmd.SetGraphicsRootSignature(&self.bloom.root_sig);
cmd.SetDescriptorHeaps(&[Some(self.descriptors.srv_heap.clone())]);
cmd.IASetPrimitiveTopology(
windows::Win32::Graphics::Direct3D::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
);
cmd.IASetVertexBuffers(0, None);
cmd.IASetIndexBuffer(None);
}
}
fn bloom_prefilter(&self, cmd: &Self::Rec, scene_srv: &Self::Args) {
let after = if self.bloom.mips.len() > 1 {
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
} else {
D3D12_RESOURCE_STATE_RENDER_TARGET
};
self.bloom_run_pass(
cmd,
BloomSubPass {
dst: 0,
src_srv: *scene_srv,
pso: &self.bloom.pso_prefilter,
before: D3D12_RESOURCE_STATE_RENDER_TARGET,
after,
},
);
}
fn bloom_downsample(&self, cmd: &Self::Rec, _scene_srv: &Self::Args, dst: usize) {
self.bloom_run_pass(
cmd,
BloomSubPass {
dst,
src_srv: self.bloom.mip_srv_gpus[dst - 1],
pso: &self.bloom.pso_downsample,
before: D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
after: D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
},
);
}
fn bloom_upsample(&self, cmd: &Self::Rec, _scene_srv: &Self::Args, dst: usize) {
let after = if dst == 0 {
D3D12_RESOURCE_STATE_RENDER_TARGET
} else {
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
};
self.bloom_run_pass(
cmd,
BloomSubPass {
dst,
src_srv: self.bloom.mip_srv_gpus[dst + 1],
pso: &self.bloom.pso_upsample,
before: D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
after,
},
);
}
}
struct BloomSubPass<'a> {
dst: usize,
src_srv: D3D12_GPU_DESCRIPTOR_HANDLE,
pso: &'a ID3D12PipelineState,
before: D3D12_RESOURCE_STATES,
after: D3D12_RESOURCE_STATES,
}
impl DxContext {
pub(in crate::directx) fn encode_bloom(
&self,
cmd: &ID3D12GraphicsCommandList,
scene_srv: D3D12_GPU_DESCRIPTOR_HANDLE,
) {
crate::gfx::fullscreen::encode_bloom_chain(self, cmd, scene_srv);
}
fn bloom_run_pass(&self, cmd: &ID3D12GraphicsCommandList, pass: BloomSubPass<'_>) {
let BloomSubPass {
dst,
src_srv,
pso,
before,
after,
} = pass;
let (mw, mh) = self.bloom.mip_extents[dst];
let post = self.post_process;
if before != D3D12_RESOURCE_STATE_RENDER_TARGET {
let to_rt = transition_barrier(
&self.bloom.mips[dst],
before,
D3D12_RESOURCE_STATE_RENDER_TARGET,
);
unsafe { cmd.ResourceBarrier(&[to_rt]) };
}
unsafe {
cmd.OMSetRenderTargets(1, Some(&self.bloom.mip_rtvs[dst]), false, None);
let vp = D3D12_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: mw as f32,
Height: mh as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
cmd.RSSetViewports(&[vp]);
let scissor = RECT {
left: 0,
top: 0,
right: mw as i32,
bottom: mh as i32,
};
cmd.RSSetScissorRects(&[scissor]);
cmd.SetPipelineState(pso);
cmd.SetGraphicsRootDescriptorTable(0, src_srv);
cmd.SetGraphicsRoot32BitConstants(
1,
6,
&post as *const PostProcessParams as *const std::ffi::c_void,
0,
);
cmd.DrawInstanced(3, 1, 0, 0);
}
if after != D3D12_RESOURCE_STATE_RENDER_TARGET {
let from_rt = transition_barrier(
&self.bloom.mips[dst],
D3D12_RESOURCE_STATE_RENDER_TARGET,
after,
);
unsafe { cmd.ResourceBarrier(&[from_rt]) };
}
}
}
#[cfg(test)]
mod tests {
use super::bloom_mip_count;
#[test]
fn bloom_mip_count_clamps_to_four_to_six() {
assert_eq!(bloom_mip_count(1920, 1080), 6);
assert_eq!(bloom_mip_count(1280, 720), 6);
assert_eq!(bloom_mip_count(64, 64), 5);
assert_eq!(bloom_mip_count(16, 16), 4);
assert_eq!(bloom_mip_count(1, 1), 4);
assert_eq!(bloom_mip_count(0, 0), 4);
}
}