[toc]

#

最近重新回顾了下 DirectX12 那本书,这本书第六章封装了一个比较简单的渲染结构,想以此来重新复习一下在这些流程中,我们都可以做些什么,以及每个接口和名词的含义是什么。 源代码: https://github.com/mymetalseed/SolDirectx_Demo/tree/main 代码是使用 Cmake 搞得,可以直接 make (但是估计只支持 WindowsXD)

# 简单窗口类封装

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class LittleWindow
{
public:
bool Initialize(const wchar_t* title);
virtual void Run() = 0;
bool Destroy();

protected:
std::wstring title;
UINT32 width;
UINT32 height;
HWND hWnd;
HWND createWin32Window(const wchar_t* title);

};

class LittleGFXWindow : public LittleWindow
{
friend class LittleGFXInstance;

public:
LittleGFXWindow();
~LittleGFXWindow();
static LittleGFXWindow* GetWindow();
virtual bool Initialize(const wchar_t* title, LittleGFXDevice* device, bool enableVsync);
bool Destroy();
bool Get4xMsaaState()const;
void Set4xMsaaState(bool value);

protected:
bool vsyncEnabled = false;
uint32_t swapchainFlags;

virtual void OnResize();
virtual void Update(/*可以加入时间*/) = 0;
virtual void Draw() = 0;

virtual void OnMouseDown(WPARAM btnState, int x, int y) {}
virtual void OnMouseUp(WPARAM btnState, int x, int y) {}
virtual void OnMouseMove(WPARAM btnState, int x, int y) {}
protected:
void CreateRtvAndDsvDescriptorHeaps();
bool InitDirect3D();
void CreateCommandObjects();
void CreateSwapChain();

void FlushCommandQueue();

ID3D12Resource* CurrentBackBuffer() const;

D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView() const;
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView() const;

void CalculateFrameStats();

void LogAdapters();
void LogAdapterOutputs(IDXGIAdapter* adapter);
void LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format);

protected:
static LittleGFXWindow* mWindow;

bool m4xMsaaState = false; //是否开启4X MSAA技术
UINT m4xMsaaQuality = 0;

Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;
Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
Microsoft::WRL::ComPtr<ID3D12Device> md3dDevice;

Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
UINT64 mCurrentFence = 0;

Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;

static const int SwapChainBufferCount = 2;
int mCurrBackBuffer = 0;
Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
Microsoft::WRL::ComPtr<ID3D12Resource> mDepthStencilBuffer;

Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDsvHeap;

D3D12_VIEWPORT mScreenViewport;
D3D12_RECT mScissorRect;

UINT mRtvDescriptorSize = 0;
UINT mDsvDescriptorSize = 0;
UINT mCbvSrvUavDescriptorSize = 0;

D3D_DRIVER_TYPE md3dDriverType = D3D_DRIVER_TYPE_HARDWARE;
DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
int mClientWidth = 800;
int mClientHeight = 600;
};

其中基础的 LittleWindow 中提供一个最基础的 InitializeRun 函数
派生类 LittleGFXWindow 提供了,其中提供了 UpdateDraw 以及 OnResize 方法
分别负责
逻辑更新把命令推入命令列表第一帧或者窗口大小变更时重新设置一些数据 以及缓存了一些运行时需要用到的
DeviceCommandQueueCommandAllocatorGraphicsCommandListDescriptorHeapSwapChain 等数据


# Main 函数入口

  1. 构建一个实例
  2. 通过实例获取显卡设备
  3. 构建 Window 渲染类,通过模板元调用模板的 Initilize 方法
  4. 初始化后调用 Run 进入窗口循环 (逻辑 Update、Draw)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "header/gfx/gfx_object.h"
#include <iostream>
#include "header/Window/LittleRendererWindow.h"

int main()
{
//创建并初始化实例
auto instance = LittleFactory::Create<LittleGFXInstance>(true);
auto device = LittleFactory::Create<LittleGFXDevice>(instance->GetAdapter(0));
//创建并初始化窗口类
auto window = LittleFactory::Create<LittleRendererWindow>(L"LittleMaster", device, true);
//运行窗口类的循环
window->Run();
// 现在窗口已经关闭,我们清理窗口类
LittleFactory::Destroy(window);
// 清理实例
LittleFactory::Destroy(device);
LittleFactory::Destroy(instance);

return 0;
}

# 通过实例获取设备接口

先通过 dxgi(directX的封装与工具库) 构建工厂,并将其缓存在窗口的 pDXGIFactory 中,如果成功的话,查询所有的 硬件适配器queryAllAdapters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool LittleGFXInstance::Initialize(bool enableDebugLayer)
{
debugLayerEnabled = enableDebugLayer;
UINT flags = 0;
if (debugLayerEnabled) flags = DXGI_CREATE_FACTORY_DEBUG;
if (SUCCEEDED(CreateDXGIFactory2(flags, IID_PPV_ARGS(&pDXGIFactory))))
{
queryAllAdapters();
// If the only adapter we found is a software adapter, log error message for QA
if (!adapters.size() && foundSoftwareAdapter)
{
assert(0 && "The only available GPU has DXGI_ADAPTER_FLAG_SOFTWARE. Early exiting");
return false;
}
}
else
{
assert("[D3D12 Fatal]: Create DXGIFactory2 Failed!");
return false;
}
return true;
}

其中 queryAdapter 的实现如下 即,不断找下一个可用的 adapter,并且 在这个阶段可以获取设备的所需信息,打印出来

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
void LittleGFXInstance::queryAllAdapters()
{
IDXGIAdapter4* adapter = NULL;
// Use DXGI6 interface which lets us specify gpu preference so we dont need to use NVOptimus or AMDPowerExpress
for (UINT i = 0;
pDXGIFactory->EnumAdapterByGpuPreference(i,
DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
IID_PPV_ARGS(&adapter)) != DXGI_ERROR_NOT_FOUND;
i++)
{
LittleGFXAdapter newAdapter;
newAdapter.pDXGIAdapter = adapter;
newAdapter.instance = this;
DXGI_ADAPTER_DESC3 desc = {};
adapter->GetDesc3(&desc);

LogAdaptersOutputs(adapter);

if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
foundSoftwareAdapter = true;
SAFE_RELEASE(adapter);
}
else
{
adapters.emplace_back(newAdapter);
}
}
}

# 通过硬件适配取得设备以及创建 CommandQueue

  1. 通过 D3D12CreateDevice 传入 Adapter 构建 Device
  2. 通过构建的设备构建 依赖于设备的CommandQueue

这里提一嘴,DirectX12 很喜欢用 Desc 作为参数来对将要的事情进行各种设置

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
bool LittleGFXDevice::Initialize(LittleGFXAdapter* in_adapter)
{
this->adapter = in_adapter;
D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_12_0;
if (!SUCCEEDED(D3D12CreateDevice(adapter->pDXGIAdapter, // default adapter
featureLevel, IID_PPV_ARGS(&pD3D12Device))))
{
assert(0 && "[D3D12 Fatal]: Create D3D12Device Failed!");
return false;
}

if (LOG_DEVICE_FUNC_SUPPORT) {
LogDeviceSupport(pD3D12Device);
}

D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
if (!SUCCEEDED(pD3D12Device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&pD3D12Queue))))
{
assert(0 && "[D3D12 Fatal]: Create D3D12CommandQueue Failed!");
return false;
}
return true;
}

# 硬件层后,软件 Update 层前

在硬件层我们会对设备,命令队列等进行构建,
这个阶段之后,我们就需要对软件层的渲染进行准备,如准备 SwapChain,各种 Buffer,以及命令队列等。

  1. 第一个 Initilize 先不介绍了,这个是用来搞 Win32 窗口的。
  2. 初始化 Direct3D 相关的结构和数据
  3. 调用 Resize 进行初次设置 (设置数据,实际开始渲染)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool LittleGFXWindow::Initialize(const wchar_t* title, LittleGFXDevice* device, bool enableVsync)
{
auto succeed = LittleWindow::Initialize(title);

if (!InitDirect3D()) {
std::cout << "初始化Direct3D失败" << std::endl;
succeed = false;
}

OnResize();

return succeed;
}


# 从设备中获取各种数据

  1. 如渲染视图在硬件上的大小
  2. 深度缓存区最大大小
  3. 常量缓冲区最大大小
  4. Msaa 支持情况 (4x、6x 等)
  5. 创建渲染栅栏 (Fence)
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
//创建Fence
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));

//从Device上获取对应的描述符的大小(硬件)
//渲染目标视图
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
//深度缓冲区视图
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
//常量缓冲区视图,着色器资源视图和无序访问视图组合的描述符堆
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

//Check 4x MSAA quality support for our back buffer format.
//All Direct3D 11 capable devices support 4X MSAA for all render
//target formats,so we only need to check quality support.
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
//检查后会将支持的Msaa等级赋值给msQualityLevels
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msQualityLevels,
sizeof(msQualityLevels)
));

m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && "unexpected MSAA quality level.");

接下来的三个流程就是开始构建各种基础结构了

1
2
3
CreateCommandObjects();
CreateSwapChain();
CreateRtvAndDsvDescriptorHeaps();

# 创建命令列表

在 DirectX12 中,渲染命令会先缓存在命令列表中,然后在合适的时间推入渲染队列中
其中命令列表的构建有以下几个步骤
1. 从渲染队列中创建命令分配器 CommandAllocator
2. 从 CommandAllocator 中构建 CommandList
3. 初次未使用先调用 Close 进行关闭

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
void LittleGFXWindow::CreateCommandObjects() {
//命令队列描述符
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
//在Device上构建CommandQueue
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
//在Device上构建CommandAllocator
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())
));
//在设备上构建命令列表,并把命令分配器交给他
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(),//Associated command allocator
nullptr, //初始化PipelineStateObject
IID_PPV_ARGS(mCommandList.GetAddressOf())
));

//Start off in a closed state. This is because the first time we refer to the
//command list we will Reset it, and it needs to be closed before calling Reset.
//初次构建一个命令列表后应该先关闭它
mCommandList->Close();
}

# 创建 SwapChain

  1. 先释放之前的 SwapChain
  2. 构造 SwapChain 的描述
  3. 调用 CreateSwapChain 创建 SwapChain 同时将其绑定到对应的 CommandQueue 上作为最后的 FrameBuffer
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
void LittleGFXWindow::CreateSwapChain() {
//Release the previous swapchain we will be recreating
mSwapChain.Reset();

DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth;
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = hWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

//Note: Swap chain uses queue to perform flush.
//直接设置当前的swap chian desc给CommandQueue
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()
));
}

# 创建描述符堆

描述符堆实际上是从 Device 上创建的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void LittleGFXWindow::CreateRtvAndDsvDescriptorHeaps() {
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())
));

D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())
));
}

# 调用 OnResize 对命令列表进行各种设置

  1. 设置与改变前先刷新一下命令列表
1
2
3
//改变前刷新一下命令队列
FlushCommandQueue();
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));

其中刷新命令队列实际上就是等待上一波推入队列的命令执行完毕 (到达栅栏 Fence 指定的点处)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void LittleGFXWindow::FlushCommandQueue() {
//Advance the fence value to mark comamnds up to this fence point.
mCurrentFence++;

//Add an instruction to the command queue to set a new fence point.
//Because we are on the GPU timeline,the new fence point won't be set
//until the GPU fnishes processing all the commands prior to this Signal()
//简单来说,栅栏
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));

//Wait until the GPU has completed commands up to this fence point.
//等待GPU到达当前的栅栏点
if (mFence->GetCompletedValue() < mCurrentFence) {
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);

//Fire event when GPU hits current fence.
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));

//Wait until the GPU hits current fence event is fired
//实际的等待
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
  1. 同时 Reset 所有依赖于屏幕视口大小的 Buffer (如 SwapChain,DepthBuffer)
  2. 调用 SwapChainResizeBuffers 来初始化 SwapChain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Release the previous resources we will be recreating.
for (int i = 0; i < SwapChainBufferCount; ++i) {
mSwapChainBuffer[i].Reset();
}
mDepthStencilBuffer.Reset();

//Resize the swap chain.
ThrowIfFailed(mSwapChain->ResizeBuffers(
SwapChainBufferCount,
mClientWidth,mClientHeight,
mBackBufferFormat,
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH
));

mCurrBackBuffer = 0;

# 设置 SwapChain

  1. 先设置 RenderTarget 以及将其和描述符关联起来
1
2
3
4
5
6
7
//DXD12那本书封装的一层DXD12_HANDLE
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; ++i) {
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
rtvHeapHandle.Offset(1,mRtvDescriptorSize);
}
  1. 声明与创建一个 Resource,用作深度缓冲区和 StencilBuffer
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
//Create the depth/stencil buffer and view 
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
//Coorrection 11/12/2016: SSAO chapter requires an SRV to the depth buffer to read from
//the depth buffer. Therefore, because we need to create two views to the same resource:
// 1. SRV format: DXGI_FORMAT_R24_UNORM_X8_TYPELESS
// 2. DSV Format: DXGI_FORMAT_D24_UNORM_S8_UINT
//we need to create the depth buffer resource with a typeless format.
depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;

depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())
));
  1. 将上面创建完成的资源赋值给 Device 用作深度与 StencilBuffer, 并且推入命令列表
1
2
3
4
5
6
7
8
9
10
11
  //Create descriptor to mip level 0 of entire resource usin the format of the resource.
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsvDesc.Format = mDepthStencilFormat;
dsvDesc.Texture2D.MipSlice = 0;
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &dsvDesc, DepthStencilView());
// Transition the resource form its initial state to be used as a depth buffer.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE
));
  1. 将上述命令推入命令队列并执行 (初次初始化)
1
2
3
4
5
6
7
//Execute the resize commands.
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

//Wait until resize is complete.
FlushCommandQueue();

# 渲染管线设置

  1. 构建描述符堆
  2. 构建 ConstantBuffer
  3. 构建根签名 (用于设置 Shader 参数)
  4. 构建与编译 Shader
  5. 构建 Mesh
  6. 构建 PSO, 渲染管线对象

其中 LittleGFXWindow::Initialize 是本节之上的初始化流程

同样,在命令列表开始记录 -> 执行间

  1. Reset
  2. 构建命令列表
  3. 执行命令列表
  4. 刷新命令队列

太多了… 不想记了,这里主要说几个吧

  1. RootSignature
    注释里有这么一段,即 RootSignature 可以理解为用来设置 Shader 的参数,如常量缓冲区,贴图,采样器等
1
2
3
4
5
//Shader programs typically require resources as input(constant buffers,
//textures,samplers). The root signature defines the resources the shader
//prorams expect. If we think of the shader programs as a function,and the input resources
//as function parameters,then the root signature can be thought of as defining the
//function signature.
  1. 构建顶点片元数据时,需要创建对应的缓冲区,是 ID3DResource 类型

  2. 构建当前渲染阶段需要用到的 PSO 对象 (如设置 shader,根签名,渲染方式 (点、线、三角形等),混合方式、RenderTarget 个数等),PSO 是绑定到 Device 上的

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
void LittleRendererWindow::BuildPSO() {
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));

psoDesc.InputLayout = { mInputLayout.data(),(UINT)mInputLayout.size() };
//设置rootSignature
psoDesc.pRootSignature = mRootSignature.Get();
psoDesc.VS = {
reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
mvsByteCode->GetBufferSize()
};
psoDesc.PS = {
reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
mpsByteCode->GetBufferSize()
};
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = mBackBufferFormat;
psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
psoDesc.DSVFormat = mDepthStencilFormat;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}

总代码如下

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
bool LittleRendererWindow::Initialize(const wchar_t* title, LittleGFXDevice* device, bool enableVsync) {
if (!LittleGFXWindow::Initialize(title, device, enableVsync)) {
return false;
}

//Reset the command list to prep for initialization commands.
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));

BuildDescriptorHeaps();
BuildConstantBuffers();
BuildRootSignature();
BuildShadersAndInputLayout();
BuildBoxGeometry();
BuildPSO();

//Execute the initialization commands.
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdLists), cmdLists);

//Wait until initialization is complete.
FlushCommandQueue();

return true;
}

# 当前渲染流程结束后,开始渲染帧 -> Run

其实 Run 里面做的事情也很简单
就是接收消息,逻辑更新,渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void LittleRendererWindow::Run() {
MSG msg = { 0 };
while (msg.message != WM_QUIT) {
//处理系统消息
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//在空闲时进行我们自己的逻辑
else {
//暂时什么都不做
//Sleep 1~2ms,避免被while独占
Update();
Draw();
}
}
//如果收到了WM_QUIT消息,直接退出函数
return;
}

# 每帧渲染 -> Draw

Draw 是实际的渲染,上述阶段均是设置渲染前的各个部件,如 几何资源渲染状态深度缓冲区 等。 在 Draw 阶段,同样需要进行命令重置,不过 实际渲染的命令列表需要指定渲染状态对象 ,如下,重置 CommandList 时,将 PSO 传入了重置函数 同样,由于是实际的渲染,所以最后会进行 SwapChain 的交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void LittleRendererWindow::Draw() {
ThrowIfFailed(mDirectCmdListAlloc->Reset());

ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));

//...

//Done recording commands.
ThrowIfFailed(mCommandList->Close());

//Add the command list to the queue for execution
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

//swap the back and front buffers.
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;

//Wait until frame commands are complete. This waiting is infficient
//and is done for simplicity. Later we will show how to organize our rendering code
//so we do not have to wait per frame.
FlushCommandQueue();
}

# 实际插入的命令

如下,就不逐个描述了,但是需要注意的是, 命令列表阶段也是可以推入修改渲染状态的命令的 ,・并不是一开始设置 PSO 才会更改渲染状态・对应哪些命令会改变渲染状态,见下面连接: 在 Direct3D 12 中管理图形管道状态

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
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);

mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
CurrentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET
));

//Clear the back buffer and depth buffer
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

//Specify the buffers we are going to render to
mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());

mCommandList->DrawIndexedInstanced(
mBoxGeo->DrawArgs["box"].IndexCount,
1, 0, 0, 0
);

//Indicate a state transition on the resource usage
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));