前言

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

INTRODUCTION TO 3D GAME PROGRAMMING WITH DIRECTX 12
INTRODUCTION TO 3D GAME PROGRAMMING WITH DIRECTX 12

修改

主要步驟:

  1. 創建MSAA用的RenderTarget
  2. 創建MSAA用的DepthStencil
  3. 創建MSAA用的Pipeline
  4. 將原本的MSAA代碼移除

1. 創建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
33
34
35
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;

D3D12_CLEAR_VALUE msaaRenderTargetClearValue = {};
msaaRenderTargetClearValue.Format = _backBufferFormat;
memcpy(msaaRenderTargetClearValue.Color, Colors::LightSteelBlue, sizeof(float) * 4);

ID3D12Resource* msaaRenderTarget;
device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&msaaResourceDesc,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE, // 不是Render Target
&msaaRenderTargetClearValue,
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

2. 創建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
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 msaaDepthStencilClearValue = {};
msaaDepthStencilClearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
msaaDepthStencilClearValue.DepthStencil.Depth = 1.0f;
msaaDepthStencilClearValue.DepthStencil.Stencil = 0.0f;

ID3D12Resource* msaaDepthStencil;
device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&msaaDepthStencilDesc,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&msaaDepthStencilClearValue,
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());

3. 創建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 = _4xMsaaQuality - 1;
msaaPSODesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;

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

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
60
61
62
63
64
65
66
67
68
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);

D3D12_RESOURCE_BARRIER barriers[2] = {
CD3DX12_RESOURCE_BARRIER::Transition(
_msaaRenderTargetBuffer.Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE
),
CD3DX12_RESOURCE_BARRIER::Transition(
backBuffers[currentBackBuffer],
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RESOLVE_DEST
)
};
cmdList->ResourceBarrier(2, &barriers);

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的RTV中,並解析到BackBuffer。ResolveSubresource()將MSAA資源複制到非MSAA資源中。

結果對比

Origin MSAA
Origin MSAA

總結

在現代的GPU中,基本上都支持4x MSAA,可以直接使用硬件的方式來實現。渲染的方式與關閉MSAA時基本一致。但是需要一個MSAA用的RTV、DSV以及Pipeline。

先渲染到MSAA用的RTV,調用ResolveSubresource(),將MSAA的資源合併到指定的RTV中。

that’s all!

Reference

  1. DirectX 12小技巧-启用MSAA
  2. Introduction of Multisampling
  3. Github Source Code