I'm currently implementing SSR in my engine and I've created Hi-Z buffer using multiple PSOs in way presented below:
- Create descriptor table with multiple UAV entries (this case - 3 entries for Mip 0, Mip 1, Mip 2). Each of this will be bound to appropriate UAV in shader
- Create PSO for each compute shader function depending on which mip is currently being generated
- In runtime, switch PSO and dispatch CS for each shader
- In result, each mip of Hi-Z buffer texture is filled with correct data. However, there is PSO switch before each dispatch which seems to be costly
I want to get rid of PSO switch (although, I'm not sure if that's possible because I have two functions - one is generating mip 0 by mapping depth from compute space to view space, other one is standard Hi-Z mip generation) and use single UAV to read data from and save data to.
If it is not possible, I would appreciate tips how to implement cleaner solution, because what I'm currently using seems a little ugly.
Relevant code:
A - create PSO for each UAV mip
// Create PSO for mip 0
pipelineStateStream.pRootSignature = m_rootSignatureHiZ.Get();
pipelineStateStream.CS = CD3DX12_SHADER_BYTECODE(computeShader.Get());
{
D3D12_PIPELINE_STATE_STREAM_DESC pipelineStateStreamDesc = { sizeof(PipelineStateStream), &pipelineStateStream };
ThrowIfFailed(m_device->CreatePipelineState(&pipelineStateStreamDesc, IID_PPV_ARGS(&m_pipelineStateHiZMipZero)));
}
// Create PSO for mip 1
Compile_Shader(L"Shaders/CS_HiZ.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE, "generateHiZMip1", "cs_5_1", 0, 0, &computeShader);
//...
// Create PSO for mip 2
Compile_Shader(L"Shaders/CS_HiZ.hlsl", NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE, "generateHiZMip2", "cs_5_1", 0, 0, &computeShader);
//...
B - in runtime, switch PSOs and dispatch compute shaders
// Generate Hi-Z mip 0 (transform depth buffer from Compute-Space (CS) to View-Space (VS))
{
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_hiZBuffer.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
m_commandList->Dispatch(m_windowSize.x / 16, m_windowSize.y / 16, 1);
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_hiZBuffer.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COMMON));
}
// Generate additional mips for Hi-Z buffer (in this implementation we use mip 1 and mip 2)
m_commandList->SetPipelineState(m_pipelineStateHiZMipOne.Get());
m_commandList->Dispatch(m_windowSize.x / 32, m_windowSize.y / 32, 1); // Mip 1
m_commandList->SetPipelineState(m_pipelineStateHiZMipTwo.Get());
m_commandList->Dispatch(m_windowSize.x / 64, m_windowSize.y / 64, 1); // Mip 2
C - full compute shader, Hi-Z generation HLSL code
Texture2D g_depthBuffer : register(t0);
RWTexture2D<float> hiZMip0 : register(u0);
RWTexture2D<float> hiZMip1 : register(u1);
RWTexture2D<float> hiZMip2 : register(u2);
[numthreads(16, 16, 1)]
void generateHiZMip0(uint3 index : SV_DispatchThreadID)
{
float depthCS = g_depthBuffer.Load(int3(index.xy, 0));
hiZMip0[index.xy] = depthCStoVS(depthCS);
}
[numthreads(16, 16, 1)]
void generateHiZMip1(uint3 index : SV_DispatchThreadID)
{
float a = hiZMip0.Load(int3(index.xy * 2 + int2(0, 0), 0));
float b = hiZMip0.Load(int3(index.xy * 2 + int2(1, 0), 0));
float c = hiZMip0.Load(int3(index.xy * 2 + int2(0, 1), 0));
float d = hiZMip0.Load(int3(index.xy * 2 + int2(1, 1), 0));
hiZMip1[index.xy] = min4(a.x, b.x, c.x, d.x);
}
[numthreads(16, 16, 1)]
void generateHiZMip2(uint3 index : SV_DispatchThreadID)
{
float a = hiZMip1.Load(int3(index.xy * 2 + int2(0, 0), 0));
float b = hiZMip1.Load(int3(index.xy * 2 + int2(1, 0), 0));
float c = hiZMip1.Load(int3(index.xy * 2 + int2(0, 1), 0));
float d = hiZMip1.Load(int3(index.xy * 2 + int2(1, 1), 0));
hiZMip2[index.xy] = min4(a.x, b.x, c.x, d.x);
}