写代码前个人注意点
环境配置
1.在Visual Studio创建新项目时要选择 Windows 桌面应用程序。
2.创建项目后在项目属性找c/c++,找到语言,找到符合模式选否。
应用程序框架示例
全书的程序都使用了 d3dUtil.h、d3dUtil.cpp、d3dApp.h 和 d3dApp.cpp 中的框架代码。
d3dUtil.h 和 d3dUtil.cpp 文件中含有程序所需的实用工具代码。
d3dApp.h 和 d3dApp.cpp 文件内包含 Direct3D 应用程序类核心代码。
构建此框架的目的:隐去窗口创建和 Direct3D 初始化的具体细节。
D3DApp类
作用:提供创建应用程序主窗口、运行程序消息循环、处理窗口消息以及初始化 Direct3D 等多种功能的函数。该类还为应用程序例程定义了一种框架函数。
d3dApp.h:
//***************************************************************************************
// d3dApp.h by Frank Luna (C) 2015 All Rights Reserved.
//***************************************************************************************
#pragma once
#if defined(DEBUG) || defined(_DEBUG) // 为调试版本开启运行时内存检测,方便监督内存泄露的情况
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif
// 代码 p123
#include "d3dUtil.h"
#include "GameTimer.h"
// 链接所需的 d3d12 库
#pragma comment(lib,"d3dcompiler.lib")
#pragma comment(lib, "D3D12.lib")
#pragma comment(lib, "dxgi.lib")
class D3DApp
{
protected:
D3DApp(HINSTANCE hInstance); // D3DApp:这个析构函数只是简单地将数据成员初始化为默认值。
D3DApp(const D3DApp& rhs) = delete;
D3DApp& operator=(const D3DApp& rhs) = delete;
virtual ~D3DApp(); // ~D3DApp:这个析构函数用于释放 D3DApp 中所用的 COM 接口对象并刷新命令队列。
public:
static D3DApp* GetApp();
HINSTANCE AppInst()const; // AppInst:简单的存取函数,返回应用程序实例句柄。
HWND MainWnd()const; // MainWnd:简单的存取函数,返回主窗口句柄。
float AspectRatio()const; // AspectRatio:这个纵横比(横向尺寸/纵向尺寸)定义的是后台缓冲区的宽度与高度之比。
bool Get4xMsaaState()const; // Get4xMsaaState:如果启用 4X MSAA 就返回 true,否则返回 false。
void Set4xMsaaState(bool value); // Set4xMsaaState:开启或禁用 4X MSAA 功能。
int Run(); // Run:这个方法封装了应用程序的消息循环。
virtual bool Initialize(); // Initialize:通过此方法为程序编写初始化代码,例如分配资源、初始化对象和建立 3D 场景等。
virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); // MsgProc:该方法用于实现应用程序主窗口的窗口过程函数 (procedure function)。
protected:
virtual void CreateRtvAndDsvDescriptorHeaps(); // CreateRtvAndDsvDescriptorHeaps:此虚函数用于创建应用程序所需的 RTV 和 DSV 描述符堆。
virtual void OnResize(); // OnResize:当 D3DApp::MsgProc 函数接收到 WM_SIZE 消息时便会调用此方法。
virtual void Update(const GameTimer& gt)=0; // 在绘制每一帧时都会调用该抽象方法,我们通过它来随着时间的推移而更新 3D 应用程序(如呈现动画、移动摄像机、做碰撞检测以及检查用户的输入等)。
virtual void Draw(const GameTimer& gt)=0; // 在绘制每一帧时都会调用的抽象方法。我们在该方法中发出渲染命令,将当前帧真正地绘制到后台缓冲区中。当完成帧的绘制后,再调用 IDXGISwapChain::Present方法将后台缓冲区的内容显示在屏幕上。
// 便于重写鼠标输入消息的处理流程
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){ } // 处理鼠标移动事件
// 若希望处理鼠标消息,只需重写这几种方法,而不必重写 MsgProc 方法。
// 这 3 个处理鼠标消息方法的第一个参数都是 WPARAM,它存储了鼠标按键的状态(即鼠标事件发生时,哪个键被按下)。
// 第二个和第三个参数则表示鼠标指针在工作区的坐标 (x,y)。
protected:
bool InitMainWindow(); // InitMainWindow:初始化应用程序主窗口。
bool InitDirect3D(); // InitDirect3D:完成 Direct3D 的初始化
void CreateCommandObjects(); // CreateCommandObjects:建命令队列、命令列表分配器和命令列表。
void CreateSwapChain(); // CreateSwapChain:创建交换链。
void FlushCommandQueue(); // FlushCommandQueue:强制 CPU 等待 GPU,直到GPU处理完队列中所有的命令。
ID3D12Resource* CurrentBackBuffer()const; // CurrentBackBuffer:返回交换链中当前后台缓冲区的 ID3D12Resource。
D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView()const; // CurrentBackBufferView:返回当前后台缓冲区的 RTV。
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView()const; // DepthStencilView:返回主深度/模板缓冲区的 DSV。
void CalculateFrameStats(); // CalculateFrameStats:计算每秒的平均帧数以及每帧平均的毫秒时长。
void LogAdapters(); // LogAdapters:枚举系统中所有的适配器。
void LogAdapterOutputs(IDXGIAdapter* adapter); // LogAdapterOutputs:枚举指定适配器的全部显示输出。
void LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format); // LogOutputDisplayModes:枚举某个显示输出对特定格式支持的所有显示模式。
protected:
static D3DApp* mApp;
HINSTANCE mhAppInst = nullptr; // 应用程序实例句柄
HWND mhMainWnd = nullptr; // 主窗口句柄
bool mAppPaused = false; // 应用程序是否暂停
bool mMinimized = false; // 应用程序是否最小化
bool mMaximized = false; // 应用程序是否最大化
bool mResizing = false; // 大小调整栏是否受到拖拽
bool mFullscreenState = false;// 是否开启全屏模式
// 若将该选项设置为 true,则使用 4X MSAA 技术(参考1.8). 默认值为 false.
bool m4xMsaaState = false; // 是否开启 4X MSAA
UINT m4xMsaaQuality = 0; // 4X MSAA 的质量级别
// 用于记录 "delta-time" (帧之间的时间间隔) 和游戏总时间(参考4.4).
GameTimer mTimer;
// p104
Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;
Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
Microsoft::WRL::ComPtr<ID3D12Device> md3dDevice;
// p98~99
Microsoft::WRL::ComPtr<ID3D12Fence> mFence;
UINT64 mCurrentFence = 0;
// p105
Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> mCommandList;
// p108
static const int SwapChainBufferCount = 2;
int mCurrBackBuffer = 0;
// p109
Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
Microsoft::WRL::ComPtr<ID3D12Resource> mDepthStencilBuffer;
// p107
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;
// 用户应该在派生类的派生构造函数中自定义这些初始值
std::wstring mMainWndCaption = L"d3d App";
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;
};
d3dApp.cpp:
//***************************************************************************************
// d3dApp.cpp by Frank Luna (C) 2015 All Rights Reserved.
//***************************************************************************************
#include "d3dApp.h"
#include <WindowsX.h> // 为了使用 GET_X_LPARAM 和 GET_Y_LPARAM 两个宏,我们必须引入 #include <WindowsX.h> (参考p131)
using Microsoft::WRL::ComPtr;
using namespace std;
using namespace DirectX;
// 窗口过程。代码参考 p708
// hwnd:接收此消息的窗口的句柄。
// msg:标识特定消息的预定值。
// wParam:与具体消息相关的额外信息。
// lParam:与具体消息相关的额外信息。
LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Forward hwnd on because we can get messages (e.g., WM_CREATE)
// before CreateWindow returns, and thus before mhMainWnd is valid.
return D3DApp::GetApp()->MsgProc(hwnd, msg, wParam, lParam);
}
D3DApp* D3DApp::mApp = nullptr;
D3DApp* D3DApp::GetApp()
{
return mApp;
}
// 概念参考 p126
// D3DApp:这个析构函数只是简单地将数据成员初始化为默认值。
D3DApp::D3DApp(HINSTANCE hInstance)
: mhAppInst(hInstance)
{
// 只能构建一个 D3DApp
assert(mApp == nullptr);
mApp = this;
}
// 概念参考 p126
// ~D3DApp:这个析构函数用于释放 D3DApp 中所用的 COM 接口对象并刷新命令队列。
// 在析构函数中刷新命令队列的原因是:在摧毁 GPU 引用的资源以前,必须等待 GPU 处理完队列中的所有命令。
// 否则,可能造成应用程序在退出时崩溃。
D3DApp::~D3DApp()
{
if(md3dDevice != nullptr)
FlushCommandQueue();
}
// 概念参考 p126
// AppInst:简单的存取函数,返回应用程序实例句柄。
HINSTANCE D3DApp::AppInst()const
{
return mhAppInst;
}
// 概念参考 p126
// MainWnd:简单的存取函数,返回主窗口句柄。
HWND D3DApp::MainWnd()const
{
return mhMainWnd;
}
// 概念参考 p126
// AspectRatio:这个纵横比(横向尺寸/纵向尺寸)定义的是后台缓冲区的宽度与高度之比。
float D3DApp::AspectRatio()const
{
return static_cast<float>(mClientWidth) / mClientHeight;
}
// 概念参考 p126
// Get4xMsaaState:如果启用 4X MSAA 就返回 true,否则返回 false。
bool D3DApp::Get4xMsaaState()const
{
return m4xMsaaState;
}
// 概念参考 p126
// Set4xMsaaState:开启或禁用 4X MSAA 功能。
void D3DApp::Set4xMsaaState(bool value)
{
if(m4xMsaaState != value)
{
m4xMsaaState = value;
// Recreate the swapchain and buffers with new multisample settings.
CreateSwapChain();
OnResize();
}
}
// 概念参考 p126
// Run:这个方法封装了应用程序的消息循环。它使用的是 Win32 的 PeekMessage 的函数,
// 当没有窗口消息到来时就会处理我们的游戏逻辑部分。
// 代码参考 p119
// Trick 函数被调用于消息循环之中:
int D3DApp::Run()
{
MSG msg = {0};
mTimer.Reset();
while(msg.message != WM_QUIT)
{
// 如果有窗口消息就进行处理
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// 否则就执行动画与游戏的相关逻辑
else
{
mTimer.Tick();
if( !mAppPaused )
{
CalculateFrameStats();
Update(mTimer);
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}
return (int)msg.wParam;
}// 采用这种方案时,我们需要在每一帧都计算 Δt,将其送入 Update 方法。
// 只有这样,才可以根据前一动画帧所花费的时间对场景进行更新。
// 概念参考 p127
// Initialize:通过此方法为程序编写初始化代码,例如分配资源、初始化对象和建立 3D 场景等。
// D3DApp 类实现的初始化方法会调用 InitMainWindow 和 InitDirect3D,因此,
// 我们在自己实现的初始化派生方法中,应当首先像下面那样来调用 D3DApp 类中的初始化方法:
bool D3DApp::Initialize()
{
if(!InitMainWindow())
return false;
if(!InitDirect3D())
return false;
// Do the initial resize code.
OnResize();
return true;
} // 这样一来,我们的初始化代码才能访问到 D3DApp 类中的初始化成员。
// 概念参考 p127
// CreateRtvAndDsvDescriptorHeaps:此虚函数用于创建应用程序所需的 RTV 和 DSV 描述符堆。
// 默认的实现是创建一个含有 SwapChainBufferCount 个 RTV 描述符的 RTV 堆
// (为交换链中的缓冲区而创建),
// 以及具有一个 DSV 描述符的 DSV 堆(为深度/模板缓冲区而创建)。
// 该方法的默认实现足以满足大多数的实例,但是,为了使用多渲染目标( multiple render targets)
// 这种高等高级技术,届时仍将重写此方法。
void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
//代码参考 p107
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())));
}
// 概念参考 p128
// OnResize:当 D3DApp::MsgProc 函数接收到 WM_SIZE 消息时便会调用此方法。
// 若窗口的大小发生了改变,一些与工作区大小有关的 Direct3D 属性也需要随之调整。
// 特别是后台缓冲区以及深度/模板(depth/stencil)缓冲区(buffer),为了匹配窗口工作区调整后的大小需要对其重新创建。
// 我们可以通过调用 IDXGISwapChain::ResizeBuffers 方法来调整后台缓冲区的尺寸。
// 对于深度/模板缓冲区而言,则需要在销毁后根据新的工作区大小进行重建。
// 另外,渲染目标(render target)和深度/模板的视图(view)也应重新创建。
// D3DApp 类中 OnResize 方法实现的功能即为调整后台缓冲区和深度/模板缓冲区的尺寸,我们可直接查阅其源代码来研究相关细节。
// 除了这些缓冲区以外,依赖于工作区大小的其他属性(如投影矩阵,projection matrix)也要在此做相应的修改。
// 由于在调整窗口大小时,客户端代码可能还需执行一些它自己的逻辑代码,因此该方法亦属于框架的一部分。
void D3DApp::OnResize()
{
assert(md3dDevice);
assert(mSwapChain);
assert(mDirectCmdListAlloc);
// Flush before changing any resources.
FlushCommandQueue();
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
// 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;
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);
}
// 创建深度/模板缓冲区及其试图。代码参考 p112
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;
depthStencilDesc.Format = mDepthStencilFormat;
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())));
// 利用此资源的格式,为整个资源的第 0 mip 层创建描述符
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), nullptr, DepthStencilView());
// 将资源从初始状态转换为深度缓冲区
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));
// Execute the resize commands.
// 代码参考 p96
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
// Wait until resize is complete.
FlushCommandQueue();
// 代码参考 p115
// Update the viewport transform to cover the client area.
mScreenViewport.TopLeftX = 0;
mScreenViewport.TopLeftY = 0;
mScreenViewport.Width = static_cast<float>(mClientWidth);
mScreenViewport.Height = static_cast<float>(mClientHeight);
mScreenViewport.MinDepth = 0.0f;
mScreenViewport.MaxDepth = 1.0f;
mScissorRect = { 0, 0, mClientWidth, mClientHeight };
}
// 概念参考 p127
// MsgProc:该方法用于实现应用程序主窗口的窗口过程函数 (procedure function)。
// 一般来说,如果需要处理在 D3DApp::MsgProc 中没有得到处理(或者不能如我们所愿进行处理)的消息,
// 只要重写此方法即可。
// 此外,如果对该方法进行了重写,那么其中并未处理的消息都应当转交至D3DApp::MsgProc。
// 代码参考 p130
LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch( msg )
{
// WM_ACTIVATE:当一个程序被激活 (activated) 或进入非活动状态 (deactivated) 时便会发送此信息。
// We pause the game when the window is deactivated and unpause it
// when it becomes active.
case WM_ACTIVATE:
if( LOWORD(wParam) == WA_INACTIVE )
{
mAppPaused = true;
mTimer.Stop();
}
else
{
mAppPaused = false;
mTimer.Start();
}
return 0;
// WM_SIZE is sent when the user resizes the window.
case WM_SIZE:
// Save the new client area dimensions.
mClientWidth = LOWORD(lParam);
mClientHeight = HIWORD(lParam);
if( md3dDevice )
{
if( wParam == SIZE_MINIMIZED )
{
mAppPaused = true;
mMinimized = true;
mMaximized = false;
}
else if( wParam == SIZE_MAXIMIZED )
{
mAppPaused = false;
mMinimized = false;
mMaximized = true;
OnResize();
}
else if( wParam == SIZE_RESTORED )
{
// Restoring from minimized state?
if( mMinimized )
{
mAppPaused = false;
mMinimized = false;
OnResize();
}
// Restoring from maximized state?
else if( mMaximized )
{
mAppPaused = false;
mMaximized = false;
OnResize();
}
else if( mResizing )
{
// If user is dragging the resize bars, we do not resize
// the buffers here because as the user continuously
// drags the resize bars, a stream of WM_SIZE messages are
// sent to the window, and it would be pointless (and slow)
// to resize for each WM_SIZE message received from dragging
// the resize bars. So instead, we reset after the user is
// done resizing the window and releases the resize bars, which
// sends a WM_EXITSIZEMOVE message.
}
else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
{
OnResize();
}
}
}
return 0;
// 当用户抓取调整栏时发送 WM_EXITSIZEMOVE 消息
case WM_ENTERSIZEMOVE:
mAppPaused = true;
mResizing = true;
mTimer.Stop();
return 0;
// 当用户释放调整栏时发送 WM_EXITSIZEMOVE 消息
// 此处根据新的窗户大小重置相关对象(如缓冲区、视图等)
case WM_EXITSIZEMOVE:
mAppPaused = false;
mResizing = false;
mTimer.Start();
OnResize();
return 0;
// 当窗口被摧毁时发送 WM_DESTROY 消息
case WM_DESTROY:
PostQuitMessage(0);
return 0;
// 当某一菜单处于激活状态,而且用户按下的既不是助记键 (mnemonic key) 也不是加速键 (acceleratorkey) 时,
// 就发送 WM_MENUCHAR 消息
case WM_MENUCHAR:
// 当按下组合键 alt-enter 时不发出 beep 蜂鸣声
return MAKELRESULT(0, MNC_CLOSE);
// 捕获此消息以防窗口变得过小
case WM_GETMINMAXINFO:
((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
return 0;
// 为了在代码中调用我们自己编写的鼠标输入虚函数,要以下方式来处理与鼠标有关的消息:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_MOUSEMOVE:
OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_KEYUP:
if(wParam == VK_ESCAPE)
{
PostQuitMessage(0);
}
else if((int)wParam == VK_F2)
Set4xMsaaState(!m4xMsaaState);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
// 概念参考 p127
// InitMainWindow:初始化应用程序主窗口。本书假设读者熟悉基本的Win32窗口初始化流程。
// 代码参考 p700 定义参考 p704
bool D3DApp::InitMainWindow()
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = mhAppInst;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"MainWnd";
if( !RegisterClass(&wc) )
{
MessageBox(0, L"RegisterClass Failed.", 0, 0);
return false;
}
// Compute window rectangle dimensions based on requested client area dimensions.
RECT R = { 0, 0, mClientWidth, mClientHeight };
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
int width = R.right - R.left;
int height = R.bottom - R.top;
mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(),
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, mhAppInst, 0);
if( !mhMainWnd )
{
MessageBox(0, L"CreateWindow Failed.", 0, 0);
return false;
}
ShowWindow(mhMainWnd, SW_SHOW);
UpdateWindow(mhMainWnd);
return true;
}
// InitDirect3D:完成 Direct3D 的初始化。
// 代码参考 p103
bool D3DApp::InitDirect3D()
{
// 创建设备
#if defined(DEBUG) || defined(_DEBUG)
// 启用 D3D12 的调试层
{
ComPtr<ID3D12Debug> debugController;
ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
debugController->EnableDebugLayer();
}
#endif
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));
// 尝试创建硬件设备
HRESULT hardwareResult = D3D12CreateDevice(
nullptr, // 默认适配器
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice));
// 回退至 WARP 设备
if(FAILED(hardwareResult))
{
ComPtr<IDXGIAdapter> pWarpAdapter;
ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));
ThrowIfFailed(D3D12CreateDevice(
pWarpAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice)));
}
// 创建围栏并获取描述符的大小
// 代码参考 p104
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(&mFence)));
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);
// 检测对 4X MSAA 质量级别的支持
// 在一切支持 Direct3D 11 的设备上,所有的渲染目标格式就皆已支持4X MSAA了。
// 因此,凡是支持 Direct3D 11 的硬件,都会保证此项功能的正常开启,我们也就无须再对此进行检验了。
// 但是,对质量级别的检测还是不可或缺
// 代码参考 p104
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msQualityLevels,
sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels;
// 平台肯定能支持 4X MSAA,所以返回值应该也总是大于0。
assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");
#ifdef _DEBUG
LogAdapters();
#endif
CreateCommandObjects();
CreateSwapChain();
CreateRtvAndDsvDescriptorHeaps();
return true;
}
// CreateCommandObjects:建命令队列、命令列表分配器和命令列表。
// 代码参考 p105
void D3DApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // 关联命令分配器
nullptr, // 初始化流水线状态对象。由于我们不会发起任何绘制命令,所以不会用到流水线状态对象
IID_PPV_ARGS(mCommandList.GetAddressOf())));
// 首先要将命令列表置于关闭状态。这是因为在第一次引用命令列表时,我们要对它进行重置,而在调用
// 重置方法之前又需先将其关闭
mCommandList->Close();
}
// CreateSwapChain:创建交换链。
// 代码参考 p107
void D3DApp::CreateSwapChain()
{
// 释放之前所创的交换链,随后再进行重建
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 = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
// 注意:交换链需要通过命令队列对其进行刷新
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
}
// FlushCommandQueue:强制 CPU 等待 GPU,直到GPU处理完队列中所有的命令。
// 代码参考 p99
void D3DApp::FlushCommandQueue()
{
// 增加围栏值,接下来将命令标记到此围栏点
mCurrentFence++;
// 向命令队列中添加一条用来设置新围栏点的命令
// 由于这条命令要交由 GPU 处理(即由 GPU 端来修改围栏值),所以在GPU处理完
// 以前的所有命令之前,它并不会设置新的围栏点
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
// 在 CPU 端等待 GPU,直到后者执行完这个围栏点之前的所有命令
if(mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
// 若 GPU 命中当前的围栏(即执行到 Signal()指令,修改了围栏值),则激发预定事件
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));
// 等待 GPU 命中围栏,激发事件
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
// CurrentBackBuffer:返回交换链中当前后台缓冲区的 ID3D12Resource。
ID3D12Resource* D3DApp::CurrentBackBuffer()const
{
return mSwapChainBuffer[mCurrBackBuffer].Get();
}
// CurrentBackBufferView:返回当前后台缓冲区的 RTV。
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferView()const
{
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),
mCurrBackBuffer,
mRtvDescriptorSize);
}
// DepthStencilView:返回主深度/模板缓冲区的 DSV。
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView()const
{
return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}
// CalculateFrameStats:计算每秒的平均帧数以及每帧平均的毫秒时长。
// 代码参考 p128
void D3DApp::CalculateFrameStats()
{
// 这段代码计算了每秒的平均帧数,也计算了每帧的平均渲染时间
// 这些统计值都会被附加到窗口的标题栏中
static int frameCnt = 0;
static float timeElapsed = 0.0f;
frameCnt++;
// 以 1 秒为统计周期来计算平均帧数以及每帧的平均渲染时间
if( (mTimer.TotalTime() - timeElapsed) >= 1.0f )
{
float fps = (float)frameCnt; // fps = frameCnt / 1
float mspf = 1000.0f / fps;
wstring fpsStr = to_wstring(fps);
wstring mspfStr = to_wstring(mspf);
wstring windowText = mMainWndCaption +
L" fps: " + fpsStr +
L" mspf: " + mspfStr;
SetWindowText(mhMainWnd, windowText.c_str());
// 为计算下一组平均值而重置
frameCnt = 0;
timeElapsed += 1.0f;
}
}
// LogAdapters:枚举系统中所有的适配器。
// 代码参考 p89
void D3DApp::LogAdapters()
{
UINT i = 0;
IDXGIAdapter* adapter = nullptr;
std::vector<IDXGIAdapter*> adapterList;
while(mdxgiFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
{
DXGI_ADAPTER_DESC desc;
adapter->GetDesc(&desc);
std::wstring text = L"***Adapter: ";
text += desc.Description;
text += L"\n";
OutputDebugString(text.c_str());
adapterList.push_back(adapter);
++i;
}
for(size_t i = 0; i < adapterList.size(); ++i)
{
LogAdapterOutputs(adapterList[i]);
ReleaseCom(adapterList[i]);
}
}// 运行上述代码会输出类似于下面的信息:
// ***Adapter: NVIDIA GeForce GTX 760
// ***Adapter: Microsoft Basic Render Driver
// LogAdapterOutputs:枚举指定适配器的全部显示输出。
// 代码参考 p90
void D3DApp::LogAdapterOutputs(IDXGIAdapter* adapter)
{
UINT i = 0;
IDXGIOutput* output = nullptr;
while(adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
{
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);
std::wstring text = L"***Output: ";
text += desc.DeviceName;
text += L"\n";
OutputDebugString(text.c_str());
LogOutputDisplayModes(output, mBackBufferFormat);
ReleaseCom(output);
++i;
}
}// 注意:官方文档中指出,在系统显卡驱动正常工作的情况下,"Microsoft Basic Render Driver" 不会关联任何显示输出。
// LogOutputDisplayModes:枚举某个显示输出对特定格式支持的所有显示模式。
// 代码参考 p91
void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
UINT count = 0;
UINT flags = 0;
// 以 nullptr 作为参数调用此函数来获取符合条件的显示模式的个数
output->GetDisplayModeList(format, flags, &count, nullptr);
std::vector<DXGI_MODE_DESC> modeList(count);
output->GetDisplayModeList(format, flags, &count, &modeList[0]);
for(auto& x : modeList)
{
UINT n = x.RefreshRate.Numerator;
UINT d = x.RefreshRate.Denominator;
std::wstring text =
L"Width = " + std::to_wstring(x.Width) + L" " +
L"Height = " + std::to_wstring(x.Height) + L" " +
L"Refresh = " + std::to_wstring(n) + L"/" + std::to_wstring(d) +
L"\n";
::OutputDebugString(text.c_str());
}
}
GameTimer类
作用:为了精确地计算时间。
GameTimer 类的实现位于 GameTimer.h 和 GameTimer.cpp 文件之中。
GameTimer.h:
//***************************************************************************************
// GameTimer.h by Frank Luna (C) 2011 All Rights Reserved.
//***************************************************************************************
#ifndef GAMETIMER_H
#define GAMETIMER_H
class GameTimer
{
public:
GameTimer();
float TotalTime()const; // 以秒作为单位
float DeltaTime()const; // 以秒作为单位
void Reset(); // 在开始消息循环之前调用
void Start(); // 解除计时器暂停时调用
void Stop(); // 暂停计时器时调用
void Tick(); // 每帧都要调用
private:
double mSecondsPerCount;
double mDeltaTime;
__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;
__int64 mPrevTime;
__int64 mCurrTime;
bool mStopped;
};
// 此类的构造函数会查询性能计数器的频率。
#endif // GAMETIMER_H
GameTimer.cpp:
//***************************************************************************************
// GameTimer.cpp by Frank Luna (C) 2011 All Rights Reserved.
//***************************************************************************************
#include <windows.h>
#include "GameTimer.h"
// p118
GameTimer::GameTimer()
: mSecondsPerCount(0.0), mDeltaTime(-1.0), mBaseTime(0),
mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
mSecondsPerCount = 1.0 / (double)countsPerSec;
}
// p122
// 这是一种自应用程序开始,不计其中暂停时间的时间总和。
// 用成员函数 TotalTime 返回自调用 Reset 函数开始不计暂停时间的总时间了
// Returns the total time elapsed since Reset() was called, NOT counting any
// time when the clock is stopped.
float GameTimer::TotalTime()const
{
// 如果正处于停止状态,则忽略本次停止时刻至当前时刻的这段时间。
// 此外,如果之前已有过暂停的情况,
// 那么也不应统计 mStopTime – mBaseTime 这段时间内的暂停时间
// 为了做到这一点,可以从 mStopTime 中再减去暂停时间 mPausedTime
//
// |<--前一次暂停时间-->|
// ----*---------------*--------------------*------------*------------*------> 时间
// mBaseTime mStopTime startTime mStopTime mCurrTime
if( mStopped )
{
return (float)(((mStopTime - mPausedTime)-mBaseTime)*mSecondsPerCount);
}
// 我们并不希望统计 mCurrTime – mBaseTime 内的暂停时间
// 可以通过从mCurrTime中再减去暂停时间mPausedTime来实现这一点
//
// (mCurrTime - mPausedTime) - mBaseTime
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------> time
// mBaseTime mStopTime startTime mCurrTime
else
{
return (float)(((mCurrTime-mPausedTime)-mBaseTime)*mSecondsPerCount);
}
}
float GameTimer::DeltaTime()const
{
return (float)mDeltaTime;
}
// p120
// 将调用 Reset 时会将 mPrevTime 初始化为当前时刻。
// 这一步十分关键,由于在第一帧画面之前没有任何的动画帧,所以此帧的前一个时间戳 t(i-1) 并不存在。
// 因此,在消息循环开始之前,需要通过 Reset 方法对 mPrevTime 的值进行初始化。
void GameTimer::Reset()
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mBaseTime = currTime;
mPrevTime = currTime;
mStopTime = 0;
mStopped = false;
}
// 在调用 Reset 函数之时,会将 mBaseTime 初始化为当前时刻。
// 我们可以把这个时刻当作应用程序的开始时刻。
// 在大多数情况下,Reset 函数只会在消息循环开始之前调用一次,
// 所以在应用程序的整个生命周期里,mBaseTime 一般会保持不变。
// 变量 mPausedTime 存储的是所有暂停时间之和。
// 这个累积时间很有存在的必要:为了得到不统计暂停时间的总时间,我们可以用程序的总运行时间减去这个累加时间算出。
// 变量 mStopTime 会给出计时器停止(暂停)的时刻,借此即可记录暂停的时间。
// p121~122
// Stop 和 Start 是 GameTimer 类中的两个关键方法。
// 当应用程序分别处于暂停或未暂停的状态时,
// 我们就可以依情况调用它们,以此令 GameTimer 能够记录暂停的时间。
void GameTimer::Start()
{
__int64 startTime;
QueryPerformanceCounter((LARGE_INTEGER*)&startTime);
// 累加调用 stop 和 start 这对方法之间的暂停时刻间隔
//
// |<-------d------->|
// ----*---------------*-----------------*------------> time
// mBaseTime mStopTime startTime
// 如果从停止状态继续计时的话……
if( mStopped )
{
// 累加暂停时间
mPausedTime += (startTime - mStopTime);
// 在重新开启计时器时,前一帧的时间 mPrevTime 是无效的,
// 这是因为它存储的是暂停时前一帧的开始时刻,
// 因此需要将它重置为当前时刻
mPrevTime = startTime;
// 已不再是停止状态……
mStopTime = 0;
mStopped = false;
}
}
void GameTimer::Stop()
{
// 如果已经处于停止状态,那就什么也不做
if( !mStopped )
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
// 否则,保存停止的时刻,并设置布尔标志,指示计时器已经停止
mStopTime = currTime;
mStopped = true;
}
}
// p118
// 帧与帧之间的时间间隔
// 当渲染动画帧时,我们需要知道每帧之间的时间间隔,以此来根据时间的流逝对游戏对象进行更新。
// 计算帧与帧之间间隔的流程如下。
// 假设在开始显示第 i 帧画面时,性能计数器返回的时刻为 ti;
// 而此前的一帧开始显示时,性能计数器返回的时刻为 t(i-1)。
// 那么这两帧的时间间隔就是 Δt = ti - t(i-1)。
// 对于实时渲染来说,为了保证动画的流畅性至少需要每秒刷新30帧,
// 所以 Δt = ti - t(i-1) 往往是个较小的数值。
void GameTimer::Tick()
{
if( mStopped )
{
mDeltaTime = 0.0;
return;
}
// 获得本帧开始显示的时刻
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mCurrTime = currTime;
// 本帧与前一帧的时间差
mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;
// 准备计算本帧与下一帧的时间差
mPrevTime = mCurrTime;
// 使时间差为非负值。
// DXSDK 中的 CDXUTTimer 示例注释里提到:如果处理器处于节能模式,
// 或者在计算两帧间时间差的过程中切换到另一个处理器时
// (即QueryPerformanceCounter 函数的两次调用并非在同一处理器上),
// 则 mDeltaTime 有可能会成为负值
if(mDeltaTime < 0.0)
{
mDeltaTime = 0.0;
}
}
初始化 Direct3D 演示程序
从 D3DApp 中派生出自己的类,实现框架函数并在此为示例编写特定的代码。
//***************************************************************************************
// Init Direct3D.cpp by Frank Luna (C) 2015 All Rights Reserved.
//
// Demonstrates the sample framework by initializing Direct3D, clearing
// the screen, and displaying frame stats.
//
//***************************************************************************************
// 代码 p132
#include "../Day4/Common/d3dApp.h"
#include <DirectXColors.h>
using namespace DirectX;
class InitDirect3DApp : public D3DApp
{
public:
InitDirect3DApp(HINSTANCE hInstance);
~InitDirect3DApp();
virtual bool Initialize()override;
private:
virtual void OnResize()override;
virtual void Update(const GameTimer& gt)override;
virtual void Draw(const GameTimer& gt)override;
};
// 在 Windows 应用程序中 WinMain 函数就相当于大多数语言中的 main() 函数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// 为调试版本开启运行时内存检测,方便监督内存泄露的情况
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
try
{
InitDirect3DApp theApp(hInstance);
if (!theApp.Initialize())
return 0;
return theApp.Run();
}
catch (DxException& e)
{
MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
return 0;
}
}
InitDirect3DApp::InitDirect3DApp(HINSTANCE hInstance)
: D3DApp(hInstance)
{
}
InitDirect3DApp::~InitDirect3DApp()
{
}
bool InitDirect3DApp::Initialize()
{
if (!D3DApp::Initialize())
return false;
return true;
}
void InitDirect3DApp::OnResize()
{
D3DApp::OnResize();
}
void InitDirect3DApp::Update(const GameTimer& gt)
{
}
void InitDirect3DApp::Draw(const GameTimer& gt)
{
// 重复使用记录命令的相关内存
// 只有当与 GPU 关联的命令列表执行完成时,我们才能将其重置
ThrowIfFailed(mDirectCmdListAlloc->Reset());
// 在通过 ExecuteCommandList 方法将某个命令列表加入命令队列后,我们便可以重置该命令列表。
// 以此来复用命令列表及其内存
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.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);
// 清除后台缓冲区和深度缓冲区
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
// ClearRenderTargetView 方法:将指定的渲染目标清理为给定的颜色。
// ClearDepthStencilView 方法:用于清理指定的深度/模板缓冲区。
//
// ClearRenderTargetView( RenderTargetView,ColorRGBA,NumRects,pRects )
// RenderTargetView:待清除的资源 RTV。
// ColorRGBA:定义即将为渲染目标填充的颜色。
// NumRects:pRects 数组中的元素数量。此值可以为0。
// pRects:一个D3D12_RECT 类型的数组,指定了渲染目标将要被清除的多个矩形区域。若设定此参数为 nullptr,则表示清除整个渲染目标。
// 在每帧为了刷新场景而开始绘制之前,我们总是要清除后台缓冲区渲染目标和深度/模板缓冲区。
// ClearDepthStencilView( DepthStencilView,ClearFlags,Depth,Stencil,NumRects,pRects)
// DepthStencilView:待清除的深度/模板缓冲区DSV。
// ClearFlags:该标志用于指定即将清除的是深度缓冲区还是模板缓冲区。
// 我们可以将此参数设置为 D3D12_CLEAR_FLAG_DEPTH 或 D3D12_CLEAR_FLAG_STENCIL,
// 也可以用按位或运算符连接两者,表示同时清除这两种缓冲区。
// Depth:以此值来清除深度缓冲区。
// Stencil:以此值来清除模板缓冲区。
// NumRects:pRects数组内的元素数量。可以将此值设置为0。
// pRects:一个D3D12_RECT类型的数组,用以指定资源视图将要被清除的多个矩形区域。将此值设置为nullptr,则表示清除整个渲染目标。
// 参考p134
// 指定将要渲染的缓冲区
mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());
// OMSetRenderTargets 方法:通过此方法即可设置我们希望在渲染流水线上使用的渲染目标和深度/模板缓冲区。
// (到目前为止,我们仅是把当前的后台缓冲区作为渲染目标,并只设置了一个主深度/模板缓冲区。
// 但在本书的后续章节里,我们还将运用多渲染目标技术)。
// OMSetRenderTargets(NumRenderTargetDescriptors,pRenderTargetDescriptors,RTsSingleHandleToDescriptorRange,pDepthStencilDescriptor)
// NumRenderTargetDescriptors:待绑定的 RTV 数量,即 pRenderTargetDescriptors 数组中的元素个数。
// 在使用多渲染目标这种高级技术时会涉及此参数。就目前来说,我们总是使用一个 RTV。
// pRenderTargetDescriptors:指向 RTV 数组的指针,用于指定我们希望绑定到渲染流水线上的渲染目标。
// RTsSingleHandleToDescriptorRange:如果 pRenderTargetDescriptors 数组中的所有 RTV 对象在描述符堆中都是连续存放的,就将此值设为 true,否则设为 false。
// pDepthStencilDescriptor:指向一个 DSV 的指针,用于指定我们希望绑定到渲染流水线上的深度/模板缓冲区。
// 参考p135
// 再次对资源状态进行转换,将资源从渲染目标状态转换回呈现状态
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;
// Present 方法:通过IDXGISwapChain::Present方法来交换前、后台缓冲区。
// 与此同时,我们也必须对索引进行更新,使之一直指向交换后的当前后台缓冲区。
// 这样一来,我们才可以正确地将下一帧场景渲染到新的后台缓冲区。
// 等待此帧的命令执行完毕。当前的实现没有什么效率,也过于简单
// 我们在后面将重新组织渲染部分的代码,以免在每一帧都要等待
FlushCommandQueue();
}
运行结果: