前言
我们已经有了Direct3D开发所需要的一些预备知识,这一篇文章介绍Direct3D的初始化工作,这个系列专注于Direct3D API和相关的图形学知识,所以不会介绍Windows开发的相关知识。
Direct3D初始化流程
进行Direct3D开发的有着固定的初始化流程,这一次介绍这Direct3D初始化的流程并将Direct3D初始化流程封装,便于之后的学习中复用。
- 创建Direct 设备,并查询描述符大小
- 创建围栏
- 设置抗锯齿
- 创建命令对象(命令列表,命令队列,命令分配器)
- 描述并创建交换链
- 创建描述符堆
- 调整后台缓冲区,并为其创建渲染目标视图
- 创建深度缓冲区,并创建与之关联的深度视图
- 设置视口和剪裁矩形
创建Direct 设备
在Direct3D中,ID3D12Device是表示图形设备的接口,它在Direct3D中具有关键作用。使用Direct 设备可以实现这些功能:
- 图形设备的抽象:
ID3D12Device是DirectX 12中用于表示图形硬件设备的接口。它提供了与硬件通信的能力,允许应用程序创建和管理图形资源、命令队列、命令分配器等。 - 资源创建和管理: 通过
ID3D12Device,应用程序可以创建和管理各种图形资源,如缓冲区、纹理、渲染目标等。这包括使用设备创建资源、资源的状态迁移和资源的销毁。 - 硬件功能查询:
ID3D12Device允许应用程序查询硬件设备的特性和支持的功能,以便根据硬件能力进行适当的配置。
Direct 设备不只这些功能,这三个功能是目前在初始化阶段会使用到的功能。
我们可以使用CreateID3D12Device创建Direct 设备
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
void CreateDevice();
//用于创建设备和交换链等 DirectX 相关对象。
Microsoft::WRL::ComPtr <IDXGIFactory> mFactory;
//用于创建和管理图形资源。
Microsoft::WRL::ComPtr<ID3D12Device> mDevice;
// mRtvDescriptorSize、mDsvDescriptorSize、mCbvSrvUavDescriptorSize 分别表示
// 渲染目标视图、深度模板视图和常量缓冲、着色器资源和无序访问视图描述符堆中描述符的增量大小。
// 用于计算描述符的偏移量和创建描述符堆时的参数。
UINT mRtvDescriptorSize = 0;
UINT mDsvDescriptorSize = 0;
UINT mCbvSrvUavDescriptorSize = 0;
void D3DApp::CreateDevice()
{
// 创建 DXGI 工厂
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mFactory)));
// 创建 DirectX 12 设备
HRESULT h = D3D12CreateDevice(
nullptr, //显示设备设置,nullptr表示设置为主设备
D3D_FEATURE_LEVEL_11_0, //Direct3D最低支持级别,设置为D3D11
IID_PPV_ARGS(&mDevice)// 用于接收创建的 Device 对象的 ComPtr 指针
);
// 获取描述符堆的大小
mRtvDescriptorSize = mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvUavDescriptorSize = mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}
创建围栏
围栏是一种同步机制,用于在GPU和CPU之间进行同步。
在图形编程中,CPU和GPU是并行工作的,但有时候我们需要确保在CPU执行某些操作之前,GPU已经完成了之前的一些绘制或计算操作。通过使用围栏,CPU可以等待GPU的信号,从而确保在继续执行之前GPU已经完成了必要的任务。
在Direct3D中我们使用ID3D12Fence接口表示围栏,我们可以使用ID3D12Device::CreateFence来创建围栏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CreateFence();
// 用于同步 GPU 和 CPU 操作的 Fence 对象的智能指针
Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
void D3DApp::CreateFence()
{
// 创建 Fence 对象
ThrowIfFailed(
mDevice->CreateFence(
0, // 初始的 Fence 值
D3D12_FENCE_FLAG_NONE, // Fence 标志,这里选择无特殊标志
IID_PPV_ARGS(&mFence) // 用于接收创建的 Fence 对象的 ComPtr 指针
)
);
}
创建围栏之后我们需要解决CPU与GPU之间的同步问题,如果CPU没有与GPU正确同步可能会出现可能会导致图形渲染错误或其他不一致性。一个解决方式就是强制CPU等待,直到GPU正确执行了绘制命令。
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 FlushCommandQueue();
// 用于跟踪 GPU 的进度
UINT mCurrentFence = 0;
void D3DApp::FlushCommandQueue()
{
// 增加当前的围栏值
mCurrentFence++;
// 发出信号,通知GPU执行完所有命令
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
// 如果当前围栏值小于GPU完成值,说明GPU还在处理之前的命令
if (mFence->GetCompletedValue() < mCurrentFence)
{
// 创建一个事件句柄,用于等待GPU信号
HANDLE eventHandle = CreateEvent(nullptr, false, false, L"FenceSetDone");
// 设置GPU信号触发的事件
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));
// 等待GPU信号触发事件
WaitForSingleObject(eventHandle, INFINITE);
// 关闭事件句柄
CloseHandle(eventHandle);
}
}
使用FlushCommandQueue方法在每次CPUyuGPU同步时强制CPU等待。以避免同步问题。
设置抗锯齿
我们使用MSAA方法对图形进行反走样,MSAA是一种图形反走样技术(在减少图像中的锯齿状边缘)

通过在渲染过程中对多个采样点进行采样,然后根据这些采样点的颜色值来确定最终的像素颜色,以减少走样。4xMSAA是同时对像素临近四个采样点进行采样,以四个采样点的平均颜色作为像素的最终颜色。
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
void Set4xMsaa();
// 存储4x MSAA(4倍多重采样抗锯齿)的质量级别
UINT m4xMsaaQuality = 0;
void D3DApp::Set4xMsaa()
{
// 结构体用于存储多重采样质量级别信息
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaQualityLevels;
msaaQualityLevels.Format = mBackBufferFormat;
msaaQualityLevels.SampleCount = 4; // 设置为4倍多重采样
msaaQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msaaQualityLevels.NumQualityLevels = 0;
// 检查硬件是否支持指定的多重采样配置
ThrowIfFailed(
mDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msaaQualityLevels,
sizeof(msaaQualityLevels))
);
// 确保存在有效的多重采样质量级别
assert(msaaQualityLevels.NumQualityLevels > 0);
// 将多重采样质量级别存储以备后用
m4xMsaaQuality = msaaQualityLevels.NumQualityLevels;
}
创建命令对象
在上一篇中已经介绍了Direct3D中的命令对象,所以这一次直接来介绍如何创建命令对象的方法
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
void CreateCommandObject();
// 用于提交命令列表至 GPU
Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
// 用于分配命令列表所需的内存
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mCommandAllocator;
// 用于存储和提交 GPU 命令
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;
void D3DApp::CreateCommandObject()
{
// 创建命令队列描述符
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; // 指定为直接命令列表
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; // 无特殊标志
// 创建命令队列
ThrowIfFailed(
mDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue))
);
// 创建命令分配器(为了分配命令列表内存)
ThrowIfFailed(
mDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT, // 与命令队列类型相同
IID_PPV_ARGS(&mCommandAllocator)
)
);
// 创建命令列表
ThrowIfFailed(
mDevice->CreateCommandList(
0, // 通常为0,表示单线程提交命令列表
D3D12_COMMAND_LIST_TYPE_DIRECT, // 与命令队列类型相同
mCommandAllocator.Get(), // 关联命令分配器
nullptr, // 初始流水线状态,暂时设置为空
IID_PPV_ARGS(&mCommandList)
)
);
// 关闭命令列表,准备好接收新的命令
mCommandList->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
27
28
29
30
31
32
33
34
35
36
37
38
39
void CreateSwapChain();
// 交换链后台缓冲区数量
static const int SwapChainBufferCount = 2;
// 用于切换前台后台缓冲区
Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
void D3DApp::CreateSwapChain()
{
// 重置已存在的交换链
mSwapChain.Reset();
// DXGI_SWAP_CHAIN_DESC 结构体用于描述交换链属性
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 = 1; // 多重采样的数量
sd.SampleDesc.Quality = 0; // 多重采样的质量
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 缓冲区的使用方式
sd.BufferCount = SwapChainBufferCount; // 缓冲区的数量
sd.OutputWindow = mhMainWnd; // 渲染窗口的句柄
sd.Windowed = true; // 是否窗口化
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // 交换链的效果
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // 允许模式切换
// 创建交换链
ThrowIfFailed(
mFactory->CreateSwapChain(
mCommandQueue.Get(), // 命令队列
&sd, // 交换链属性
&mSwapChain // 输出的交换链对象
)
);
}
创建描述符堆
接下来我们需要为我们使用到的描述符(视图)创建描述符堆,现在我们需要为交换链中的缓冲区创建渲染目标视图,还需要创建用于深度测试的深度视图(之后的创建深度缓冲区介绍深度缓冲)
描述符堆的相关信息也在预备知识中介绍了,直接创建描述符堆
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
void CreateDescriptorHeap();
// 当前后台缓冲区索引
int mCurrBackBuffer = 0;
// 渲染目标视图 (RTV) 描述符堆
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;
// 深度模板视图 (DSV) 描述符堆
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDsvHeap;
void D3DApp::CreateDescriptorHeap()
{
// 创建渲染目标视图 (RTV) 的描述符堆
D3D12_DESCRIPTOR_HEAP_DESC rtvHeap;
rtvHeap.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; // 描述符堆的标志,此处无特殊标志
rtvHeap.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; // 描述符堆的类型为渲染目标视图 (RTV) 类型
rtvHeap.NodeMask = 0; // 节点遮罩,此处表示在单节点系统上使用
rtvHeap.NumDescriptors = SwapChainBufferCount; // 描述符堆中的描述符数量,与交换链后台缓冲区数量一致
ThrowIfFailed(
mDevice->CreateDescriptorHeap(
&rtvHeap,
IID_PPV_ARGS(&mRtvHeap)
)
);
// 创建深度模板视图 (DSV) 的描述符堆
D3D12_DESCRIPTOR_HEAP_DESC dsvHeap;
dsvHeap.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; // 描述符堆的标志,此处无特殊标志
dsvHeap.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; // 描述符堆的类型为深度模板视图 (DSV) 类型
dsvHeap.NodeMask = 0; // 节点遮罩,此处表示在单节点系统上使用
dsvHeap.NumDescriptors = 1; // 描述符堆中的描述符数量,此处为深度模板视图 (DSV) 的数量
ThrowIfFailed(
mDevice->CreateDescriptorHeap(
&dsvHeap,
IID_PPV_ARGS(&mDsvHeap)
)
);
}
调整后台缓冲区,并为其创建渲染目标视图
由于资源无法与渲染流水线直接绑定,所以需要为资源创建视图,并将视图绑定在渲染流水线上。
首先来创建渲染目标视图(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
void CreateRTV();
//用于储存交换链中的缓冲区资源
Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
void D3DApp::CreateRTV()
{
// 获取渲染目标视图堆的CPU句柄
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(
mRtvHeap->GetCPUDescriptorHandleForHeapStart()
);
// 遍历交换链中的后台缓冲,为每个后台缓冲创建渲染目标视图
for (int i = 0; i < SwapChainBufferCount; i++)
{
// 获取交换链中的后台缓冲
ThrowIfFailed(
mSwapChain->GetBuffer(
i,
IID_PPV_ARGS(&mSwapChainBuffer[i])
)
);
// 使用设备接口创建渲染目标视图,将其关联到渲染目标视图堆的当前句柄位置
mDevice->CreateRenderTargetView(
mSwapChainBuffer[i].Get(), // 后台缓冲资源
nullptr, // 渲染目标视图的描述符
rtvHeapHandle // 当前渲染目标视图堆句柄位置
);
// 偏移至下一个渲染目标视图堆句柄位置
rtvHeapHandle.Offset(mRtvDescriptorSize);
}
}
创建深度缓冲区,并创建与之关联的深度视图
深度缓冲区是在图形渲染中用于存储场景中每个像素的深度信息的缓冲区。深度信息表示相机到场景中各个物体表面的距离,通常以从相机到物体的距离来度量。深度缓冲区是一种二维图像,其尺寸与屏幕的宽度和高度相同。

深度缓冲区会记录距离观察窗口最近的物体深度信息(最小深度值),如图所示,在P点记录的就是P1的深度值。
借助深度缓冲区我们可以实现很多不错的效果,比如:
- 深度测试(Depth Testing):通过深度测试,不需要考虑绘制顺序,可以确保只有最接近相机的像素被绘制,实现真实感的渲染。
- 阴影映射(Shadow Mapping): 深度缓冲区还常用于生成阴影。在阴影映射中,深度缓冲区首先用于从光源的透视图中渲染场景的深度信息,然后该深度信息被用于后续的光照计算,以确定物体是否在阴影中。
接下来需要创建深度缓冲区并为其创建深度视图,创建深度视图的方法与渲染目标视图相似,前面说过,深度缓冲区本质上是2D纹理,我们使用创建2D纹理的方法创建深度缓冲区。
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
void D3DApp::CreateDSV();
//用于储存深度缓冲区
Microsoft::WRL::ComPtr<ID3D12Resource> mDepthStencilBuffer;
void D3DApp::CreateDSV()
{
// 定义深度模板缓冲区资源描述符
D3D12_RESOURCE_DESC rd;
rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; // 指定维度为二维纹理
rd.Width = mClientWidth; // 指定宽度
rd.Height = mClientHeight; // 指定高度
rd.DepthOrArraySize = 1; // 深度和纹理数组的数量
rd.MipLevels = 1; // Mipmap 层级数
rd.Alignment = 0; // 对齐方式
rd.Format = mDepthStencilFormat; // 深度模板格式
rd.SampleDesc.Count = 1; // 采样数
rd.SampleDesc.Quality = 0; // 质量级别
rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; // 纹理布局
rd.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; // 标志,表示用于深度和模板
// 定义深度模板缓冲区的初始清除值
D3D12_CLEAR_VALUE cv;
cv.Format = mDepthStencilFormat; // 深度模板格式
cv.DepthStencil.Depth = 1.0f; // 初始深度值
cv.DepthStencil.Stencil = 0; // 初始模板值
// 创建深度模板缓冲区
ThrowIfFailed(
mDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), // 堆属性
D3D12_HEAP_FLAG_NONE, // 堆标志
&rd, // 资源描述符
D3D12_RESOURCE_STATE_COMMON, // 初始资源状态
&cv, // 清除值
IID_PPV_ARGS(&mDepthStencilBuffer) // 输出深度模板缓冲区
)
);
// 定义深度模板视图描述符
D3D12_DEPTH_STENCIL_VIEW_DESC dsvd;
dsvd.Flags = D3D12_DSV_FLAG_NONE; // 标志,此处表示无特殊标志
dsvd.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; // 视图维度为二维纹理
dsvd.Format = mDepthStencilFormat; // 深度模板格式
dsvd.Texture2D.MipSlice = 0; // Mipmap 切片
// 创建深度模板视图
mDevice->CreateDepthStencilView(
mDepthStencilBuffer.Get(), // 对应深度模板缓冲区
&dsvd, // 深度模板视图描述符
DepthStencilView() // 输出深度模板视图
);
}
设置视口和剪裁矩形
初始化Direct3D的最后一部分设置视口和剪裁矩形
在Direct3D中视口定义了渲染目标的输出区域。渲染得到的结果需要投影到屏幕上,而视口指定了在屏幕上的位置和大小。视口通常用于将3D场景中的坐标映射到屏幕上的2D坐标;而剪裁矩形定义了渲染目标上允许绘制的区域。在某些情况下,希望只在屏幕的特定区域内进行渲染,这时就需要使用剪裁矩形来对渲染区域约束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void SetViewportAndScissor();
//用于设置视口
D3D12_VIEWPORT mScreenViewport;
//用于设置剪裁矩形
D3D12_RECT mScissorRect;
void D3DApp::SetViewportAndScissor()
{
// 设置屏幕视口
mScreenViewport.TopLeftX = 0; // 视口左上角 X 坐标
mScreenViewport.TopLeftY = 0; // 视口左上角 Y 坐标
mScreenViewport.Width = static_cast<float>(mClientWidth); // 视口宽度
mScreenViewport.Height = static_cast<float>(mClientHeight); // 视口高度
mScreenViewport.MinDepth = 0.0f; // 视口最小深度
mScreenViewport.MaxDepth = 1.0f; // 视口最大深度
// 设置剪裁矩形
mScissorRect.top = 0; // 剪裁矩形顶部坐标
mScissorRect.left = 0; // 剪裁矩形左侧坐标
mScissorRect.right = mClientWidth / 2.0; // 剪裁矩形右侧坐标(宽度的一半)
mScissorRect.bottom = mClientHeight / 2.0; // 剪裁矩形底部坐标(高度的一半)
}
绘制
经过上面的初始化流程,接下来就可以进入绘制阶段,在这一阶段会将场景中的几何数据转换为最终的图像,到现在内容已经太多了,本次只会到初始化画布。
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
void DirectX12_Init::Draw()
{
// 重置命令分配器
ThrowIfFailed(mCommandAllocator->Reset());
// 重置命令列表
ThrowIfFailed(mCommandList->Reset(mCommandAllocator.Get(), nullptr));
// 切换渲染目标状态为渲染目标
mCommandList->ResourceBarrier(
1,
&CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET)
);
// 设置视口和剪裁矩形
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);
// 获取当前后台缓冲的RTV和DSV句柄
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),
mCurrBackBuffer,
mRtvDescriptorSize
);
D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = mDsvHeap->GetCPUDescriptorHandleForHeapStart();
// 清除渲染目标和深度模板缓冲
mCommandList->ClearRenderTargetView(
rtvHandle,
Colors::Sienna,
0,
nullptr
);
mCommandList->ClearDepthStencilView(
dsvHandle,
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
1.0f,
0,
0,
nullptr
);
// 将渲染目标和深度模板缓冲绑定到管线
mCommandList->OMSetRenderTargets(
1,
&rtvHandle,
true,
&dsvHandle
);
// 切换渲染目标状态为呈现目标
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
// 关闭命令列表
ThrowIfFailed(mCommandList->Close());
// 执行命令列表
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// 呈现当前帧
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;
// 刷新命令队列,确保命令执行完成
FlushCommandQueue();
}

这样我们Direct3D的初始化就完成了,之后终于可以进入到激动人心的绘制环节了