<p>The code for mip1 and mip2 (and higher mips if you have them—SSR ray marching can benefit a lot from higher mips) is identical except for which mip is being read and written; you don't need separate PSOs for those. Instead of creating bindings for mip0, mip1, mip2, you could rather create bindings for &quot;source mip&quot; and &quot;dest mip&quot; and set the descriptors for each dispatch to point to the appropriate mips.</p>
<p>Also note that you'll need a UAV barrier between these dispatches since each one will be reading from the mip written by the previous one.</p>
<p>The overall sequence would be like:</p>
<pre><code>switch to PSO for mip0
dispatch mip0 (reads g_depthBuffer, writes mip0)
barrier on mip0
switch to shared PSO for all higher mips
dispatch mip1 (src = mip0, dest = mip1)
barrier on mip1
dispatch mip2 (src = mip1, dest = mip2)
barrier on mip2
...
</code></pre>
<p>If the remaining PSO switch is still too expensive, you could also try putting it all in one shader and branching on a constant buffer value based on whether it's mip0 or a higher mip. So the shader code would look something like:</p>
<pre><code>cbuffer { bool isFirstMip; }
void generateHiZ(uint3 index : SV_DispatchThreadID)
{
    [branch] if (isFirstMip)
        generateHiZMip0(index);
    else
        generateHiZHigherMips(index);
}
</code></pre>
<p>As this branch is 100% coherent within a dispatch, it should not be very expensive (especially if the bool is in a root constant), and might well be faster than incurring the PSO switch.</p>
<hr />
<p>A further optimization you can make is to generate more than 1 mip per dispatch. With a threadgroup size of 16x16 it is possible to generate up to 5 mips in one pass, using one source and 5 dest UAVs. You would loop over the 5 mips in the shader and do a memory barrier at the end of each iteration, ensuring all threads in the group have finished their UAV writes. The first iteration will read 32x32 and write 16x16 (over the whole threadgroup), then will write 8x8, 4x4, 2x2, and 1x1.</p>
<p>It also might be faster to use threadgroup shared memory after the first iteration, rather than re-reading it from the UAV, but this would need to be profiled to be sure (the UAV reads will be hitting cache, so may be fast enough on their own).</p>
<p>Then, you would only need to invoke one dispatch for each 5 mips to generate. Two dispatches gives you up to 10 mips which is probably enough for SSR.</p>

CC BY-SA 4.0
2021-09-05T20:48:30.987Z
I did try exactly what you've presented in the first part of your post, before I used hack presented in my post. However, I couldn't make it working because I am using root descriptor table for UAVs. I don't know how to use "SetComputeRootUnorderedAccessView" function because I don't know how to get its arguments correctly (i.e. gpu address argument).
DirectX_Programmer
CC BY-SA 4.0
2021-09-05T23:06:20.467Z
@DirectX_Programmer Unfortunately you can't put image UAVs in the root table, only buffer UAVs. (See [here](https://docs.microsoft.com/en-us/windows/win32/direct3d12/using-descriptors-directly-in-the-root-signature)—note `RWTexture2D` isn't one of the supported types.) You'll need to use a descriptor table for this.
Nathan Reed
CC BY-SA 4.0
2021-09-06T08:00:36.727Z
Remaining problem is that I need switch UAVs in root table (since I'll be using srcUAV and dstUAV). How do I do that? Right now, I'm binding all UAVs that I've created for given root table ( https://github.com/komilll/YARE/blob/main/YARE/Renderer.cpp#L641 )
DirectX_Programmer
CC BY-SA 4.0
2021-09-06T14:08:47.607Z
Ok, I got this working the way that you've presented. However, I need kind of weird setup ( https://github.com/komilll/YARE/commit/b95fb8bf498bd4f9bd878a4f65c0e052b2768c34#diff-e8b0651c2fabf6fc835e66e05d892cf8c66e485a311f36f006c81c71c60ec4edR628 ) - I don't understand how indexing of descriptor handles work. Do you have any article about that?
DirectX_Programmer
CC BY-SA 4.0
2021-09-07T20:57:51.747Z
It's a confusing topic for sure. I don't have any good resources off the top of my head, but look for some GDC talks from when DX12 was introduced, there were a bunch of deep dives on various parts of the API. But, usually how it works is you allocate some descriptors from the heap for each pass you draw, and populate them with the resource views for that pass. As the frame goes on the heap gets filled up with all the descriptors for all the passes. Then you reclaim the used descriptors when the GPU finishes that frame.
Nathan Reed
CC BY-SA 4.0
2021-09-05T17:28:38.513Z
2021-09-05T17:28:38.513Z
Nathan Reed
<p>I'm currently implementing SSR in my engine and I've created Hi-Z buffer using multiple PSOs in way presented below:</p>
<ol>
<li>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</li>
<li>Create PSO for each <strong>compute shader</strong> function depending on which mip is currently being generated</li>
<li>In runtime, switch PSO and dispatch <strong>CS</strong> for each shader</li>
<li>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</li>
</ol>
<p><strong>I want to get rid of PSO switch</strong> (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 <strong>use single UAV to read data from and save data to.</strong></p>
<p>If it is not possible, I would appreciate tips how to implement cleaner solution, because what I'm currently using seems a little ugly.</p>
<hr />
<p>Relevant code:</p>
<p>A - create PSO for each UAV mip</p>
<pre><code>// 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), &amp;pipelineStateStream };
    ThrowIfFailed(m_device-&gt;CreatePipelineState(&amp;pipelineStateStreamDesc, IID_PPV_ARGS(&amp;m_pipelineStateHiZMipZero))); 
}

// Create PSO for mip 1
Compile_Shader(L&quot;Shaders/CS_HiZ.hlsl&quot;, NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE, &quot;generateHiZMip1&quot;, &quot;cs_5_1&quot;, 0, 0, &amp;computeShader);
//...

// Create PSO for mip 2
Compile_Shader(L&quot;Shaders/CS_HiZ.hlsl&quot;, NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE, &quot;generateHiZMip2&quot;, &quot;cs_5_1&quot;, 0, 0, &amp;computeShader);
//...
</code></pre>
<p>B - in runtime, switch PSOs and dispatch compute shaders</p>
<pre><code>// Generate Hi-Z mip 0 (transform depth buffer from Compute-Space (CS) to View-Space (VS))
{
    m_commandList-&gt;ResourceBarrier(1, &amp;CD3DX12_RESOURCE_BARRIER::Transition(m_hiZBuffer.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
    m_commandList-&gt;Dispatch(m_windowSize.x / 16, m_windowSize.y / 16, 1);
    m_commandList-&gt;ResourceBarrier(1, &amp;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-&gt;SetPipelineState(m_pipelineStateHiZMipOne.Get());
m_commandList-&gt;Dispatch(m_windowSize.x / 32, m_windowSize.y / 32, 1); // Mip 1

m_commandList-&gt;SetPipelineState(m_pipelineStateHiZMipTwo.Get());
m_commandList-&gt;Dispatch(m_windowSize.x / 64, m_windowSize.y / 64, 1); // Mip 2
</code></pre>
<p>C - full compute shader, Hi-Z generation HLSL code</p>
<pre><code>Texture2D g_depthBuffer : register(t0);

RWTexture2D&lt;float&gt; hiZMip0 : register(u0);
RWTexture2D&lt;float&gt; hiZMip1 : register(u1);
RWTexture2D&lt;float&gt; 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);
}
</code></pre>

<directx12><z-buffer>
Generate Hi-Z buffer using single PSO and single UAV
DirectX_Programmer
# Generate Hi-Z buffer using single PSO and single UAV

(Asked by: username_0 on 2021-09-05)

I'm currently implementing SSR in my engine and I've created Hi-Z buffer using multiple PSOs in way presented below:

1. 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
2. Create PSO for each **compute shader** function depending on which mip is currently being generated
3. In runtime, switch PSO and dispatch **CS** for each shader
4. 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);
}

```

**Tags:** directx12, z-buffer


---

## 1 answers

### Answer by username_1 on 2021-09-05. Score: 1 | Accepted answer

The code for mip1 and mip2 (and higher mips if you have them—SSR ray marching can benefit a lot from higher mips) is identical except for which mip is being read and written; you don't need separate PSOs for those. Instead of creating bindings for mip0, mip1, mip2, you could rather create bindings for "source mip" and "dest mip" and set the descriptors for each dispatch to point to the appropriate mips.

Also note that you'll need a UAV barrier between these dispatches since each one will be reading from the mip written by the previous one.

The overall sequence would be like:

```
switch to PSO for mip0
dispatch mip0 (reads g_depthBuffer, writes mip0)
barrier on mip0
switch to shared PSO for all higher mips
dispatch mip1 (src = mip0, dest = mip1)
barrier on mip1
dispatch mip2 (src = mip1, dest = mip2)
barrier on mip2
...

```

If the remaining PSO switch is still too expensive, you could also try putting it all in one shader and branching on a constant buffer value based on whether it's mip0 or a higher mip. So the shader code would look something like:

```
cbuffer { bool isFirstMip; }
void generateHiZ(uint3 index : SV_DispatchThreadID)
{
    [branch] if (isFirstMip)
        generateHiZMip0(index);
    else
        generateHiZHigherMips(index);
}

```

As this branch is 100% coherent within a dispatch, it should not be very expensive (especially if the bool is in a root constant), and might well be faster than incurring the PSO switch.

---

A further optimization you can make is to generate more than 1 mip per dispatch. With a threadgroup size of 16x16 it is possible to generate up to 5 mips in one pass, using one source and 5 dest UAVs. You would loop over the 5 mips in the shader and do a memory barrier at the end of each iteration, ensuring all threads in the group have finished their UAV writes. The first iteration will read 32x32 and write 16x16 (over the whole threadgroup), then will write 8x8, 4x4, 2x2, and 1x1.

It also might be faster to use threadgroup shared memory after the first iteration, rather than re-reading it from the UAV, but this would need to be profiled to be sure (the UAV reads will be hitting cache, so may be fast enough on their own).

Then, you would only need to invoke one dispatch for each 5 mips to generate. Two dispatches gives you up to 10 mips which is probably enough for SSR.

> **Comments:**
> **username_0**: I did try exactly what you've presented in the first part of your post, before I used hack presented in my post. However, I couldn't make it working because I am using root descriptor table for UAVs. I don't know how to use "SetComputeRootUnorderedAccessView" function because I don't know how to get its arguments correctly (i.e. gpu address argument).  
> **username_1**: @username_0 Unfortunately you can't put image UAVs in the root table, only buffer UAVs. (See [here](https://docs.microsoft.com/en-us/windows/win32/direct3d12/using-descriptors-directly-in-the-root-signature)—note `RWTexture2D` isn't one of the supported types.) You'll need to use a descriptor table for this.  
> **username_0**: Remaining problem is that I need switch UAVs in root table (since I'll be using srcUAV and dstUAV). How do I do that? Right now, I'm binding all UAVs that I've created for given root table ( https://github.com/komilll/YARE/blob/main/YARE/Renderer.cpp#L641 )  
> **username_0**: Ok, I got this working the way that you've presented. However, I need kind of weird setup ( https://github.com/komilll/YARE/commit/b95fb8bf498bd4f9bd878a4f65c0e052b2768c34#diff-e8b0651c2fabf6fc835e66e05d892cf8c66e485a311f36f006c81c71c60ec4edR628 ) - I don't understand how indexing of descriptor handles work. Do you have any article about that?  
> **username_1**: It's a confusing topic for sure. I don't have any good resources off the top of my head, but look for some GDC talks from when DX12 was introduced, there were a bunch of deep dives on various parts of the API. But, usually how it works is you allocate some descriptors from the heap for each pass you draw, and populate them with the resource views for that pass. As the frame goes on the heap gets filled up with all the descriptors for all the passes. Then you reclaim the used descriptors when the GPU finishes that frame.  

---

