Environment:
- DirectX 12
- Windows 10
- Visual Studio 2019

前言

在「INTRODUCTION TO 3D GAME PROGRAMMING WITH DIRECTX 12」一書中,示例代碼使用MSAA的方式已經過時,所以導致出現錯誤。這是因為在新版DirectX 12中,不能在運行時修改交換鏈(SwapChain)。為了使用MSAA,需要作出修改,本文使用的示例是Chapter 6中的Box。

INTRODUCTION TO 3D GAME PROGRAMMING WITH DIRECTX 12

修改

主要的步驟有

  1. 創建MSAA用的RenderTarget
  2. 創建MSAA用的DepthStencil
  3. 創建MSAA用的Pipeline

Notice: SwapChain不需要修改

創建RTV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ID3D12Resource* msaaRenderTarget;
D3D12_DESCRIPTOR_HEAP_DESC msaaRTVHeapDesc;
msaaRTVHeapDesc.NumDescriptors = 1;
msaaRTVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
msaaRTVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
msaaRTVHeapDesc.NodeMask = 0;
device->CreateDescriptorHeap (&msaaRTVHeapDesc, IID_PPV_ARGS (&msaaRTVHeap));

D3D12_RESOURCE_DESC msaaResourceDesc = CD3DX12_RESOURCE_DESC::Tex2D (
DXGI_FORMAT_R8G8B8A8_UNORM,
window_width,
window_height,
1, // ArraySize
1, // MipLevels
4 // msaa採樣次數
);
msaaResourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;

device->CreateCommittedResource (
&CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&msaaResourceDesc,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE, // 不是Render Target
&msaaOptimizedClearValue,
IID_PPV_ARGS (&msaaRenderTarget)
);

D3D12_RENDER_TARGET_VIEW_DESC msaaRTVDesc = {};
msaaRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
msaaRTVDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS;
device->CreateRenderTargetView (msaaRenderTarget, &msaaRTVDesc, msaaRTVHeap->GetCPUDescriptorHandleForHeapStart ());

注意在CreateCommittedResource ()中的State是D3D12_RESOURCE_STATE_RESOLVE_SOURCE,而不是D3D12_RESOURCE_STATE_RENDER_TARGET

創建DSV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
ID3D12Resource* msaaDepthStencil;
D3D12_DESCRIPTOR_HEAP_DESC msaaDSVHeapDesc;
msaaDSVHeapDesc.NumDescriptors = 1;
msaaDSVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
msaaDSVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
msaaDSVHeapDesc.NodeMask = 0;
device->CreateDescriptorHeap (&msaaDSVHeapDesc, IID_PPV_ARGS (&msaaDSVHeap));

D3D12_RESOURCE_DESC msaaDepthStencilDesc = CD3DX12_RESOURCE_DESC::Tex2D (
DXGI_FORMAT_D24_UNORM_S8_UINT,
window_width,
window_height,
1,
1,
4
);
msaaDepthStencilDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

D3D12_CLEAR_VALUE depthOptimizedClearValue = {};
depthOptimizedClearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthOptimizedClearValue.DepthStencil.Depth = 1.0f;
depthOptimizedClearValue.DepthStencil.Stencil = 0.0f;

device->CreateCommittedResource (
&CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&msaaDepthStencilDesc,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&depthOptimizedClearValue,
IID_PPV_ARGS (&msaaDepthStencil)
);

D3D12_DEPTH_STENCIL_VIEW_DESC msaaDSVDesc = {};
msaaDSVDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
msaaDSVDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS;
device->CreateDepthStencilView (msaaDepthStencil, &msaaDSVDesc, msaaDSVHeap->GetCPUDescriptorHandleForHeapStart ());

基本與RTV相同。

創建Pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
D3D12_GRAPHICS_PIPELINE_STATE_DESC msaaPSODesc;
ZeroMemory (&msaaPSODesc, sizeof (D3D12_GRAPHICS_PIPELINE_STATE_DESC));
msaaPSODesc.InputLayout = {inputLayout.data (), (UINT)inputLayout.size ()};
msaaPSODesc.pRootSignature = rootSignature;
msaaPSODesc.VS = {
vsByteCode->GetBufferPointer (),
vsByteCode->GetBufferSize ()
};
msaaPSODesc.PS = {
psByteCode->GetBufferPointer (),
psByteCode->GetBufferSize ()
};
msaaPSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC (D3D12_DEFAULT);
msaaPSODesc.BlendState = CD3DX12_BLEND_DESC (D3D12_DEFAULT);
msaaPSODesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC (D3D12_DEFAULT);
msaaPSODesc.SampleMask = D3D12_DEFAULT_SAMPLE_MASK;
msaaPSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
msaaPSODesc.NumRenderTargets = 1;
msaaPSODesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
msaaPSODesc.SampleDesc.Count = 4;
msaaPSODesc.SampleDesc.Quality = 0;
msaaPSODesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;

ID3D12PipelineState* msaaPipelineState;
device->CreateGraphicsPipelineState (&msaaPSODesc, IID_PPV_ARGS (&msaaPipelineState));

Pipiline中的SampleDesc的設置需要與Buffer一致。所以Count = 4以及Quality = 0

Draw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
D3D12_RESOURCE_BARRIER barrierDesc = {};
barrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrierDesc.Transition.pResource = msaaRenderTarget;
barrierDesc.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_SOURCE;
barrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
cmdList->ResourceBarrier (1, &barrierDesc);

auto rtvH = msaaRTVHeap->GetCPUDescriptorHandleForHeapStart ();
auto dsvH = msaaDSVHeap->GetCPUDescriptorHandleForHeapStart ();
cmdList->ClearRenderTargetView (rtvH, Colors::LightSteelBlue, 0, nullptr);
cmdList->ClearDepthStencilView (dsvH, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
1.0f, 0, 0, nullptr);
cmdList->OMSetRenderTargets (1, &rtvH, true, &dsvH);

ID3D12DescriptorHeap* descriptorHeaps[] = {cbvHeap};
cmdList->SetGraphicsRootSignature (rootSignature);
cmdList->SetDescriptorHeaps (_countof (descriptorHeaps), descriptorHeaps);
cmdList->SetGraphicsRootDescriptorTable (0, cbvHeap->GetGPUDescriptorHandleForHeapStart ());

cmdList->IASetVertexBuffers (0, 1, &vbView);
cmdList->IASetIndexBuffer (&ibView);
cmdList->IASetPrimitiveTopology (D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

cmdList->DrawIndexedInstanced (indices.size (), 1, 0, 0, 0);

barrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_SOURCE;
cmdList->ResourceBarrier (1, &barrierDesc);

barrierDesc = CD3DX12_RESOURCE_BARRIER::Transition (backBuffers[currentBackBuffer],
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RESOLVE_DEST);
cmdList->ResourceBarrier (1, &barrierDesc);
cmdList->ResolveSubresource (backBuffers[currentBackBuffer],
0, msaaRenderTarget, 0, DXGI_FORMAT_R8G8B8A8_UNORM);

barrierDesc = CD3DX12_RESOURCE_BARRIER::Transition (
backBuffers[currentBackBuffer],
D3D12_RESOURCE_STATE_RESOLVE_DEST,
D3D12_RESOURCE_STATE_PRESENT
);
cmdList->ResourceBarrier (1, &barrierDesc);

cmdList->Close ();

ID3D12CommandList* cmdLists[] = {cmdList};
cmdQueue->ExecuteCommandLists (_countof (cmdLists), cmdLists);

swapChain->Present (0, 0);
currentBackBuffer = (currentBackBuffer + 1) % 2;

cmdQueue->Signal (fence, ++fenceValue);
if (fence->GetCompletedValue () != fenceValue) {
auto event = CreateEvent (nullptr, false, false, nullptr);
fence->SetEventOnCompletion (fenceValue, event);
WaitForSingleObject (event, INFINITE);
CloseHandle (event);
}

需要先渲染到MSAA的Buffer中,並解析到BackBuffer。ResolveSubresource()將MSAA資源複制到非MSAA資源中。

結果對比

點擊圖片放大查看

Normal MSAA

總結

因為MSAA算法已經包含在GPU中,所以只需要直接使用便可(實現原理也不難,可以自行上網搜索)。渲染的方式與關閉MSAA時基本一致。但是需要一個MSAA用的RTV、DSV以及Pipeline。

在渲染時先渲染到MSAA的RenderTarget上,然後通過ResolveSubresource ()將其複制到後台緩衝中。

that’s all!

Reference

  1. DirectX 12小技巧-启用MSAA
  2. Introduction of Multisampling(MSAA)