01 DirectX11初始化
由於個人覺得龍書裏面第4章提供的Direct3D 初始化項目封裝得比較好,而且Direct SDK Samples裏面的初始化程序過於精簡,不適合後續使用,故選擇了結合兩者的代碼,並做進一部簡化處理。
項目源碼點此
鏈接靜態庫
這裏的每一個項目都需要包含靜態庫:d3d11.lib,dxgi.lib,dxguid.lib,D3DCompiler.lib和winmm.lib。可以用下面語句:
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "D3DCompiler.lib")
#pragma comment(lib, "winmm.lib")
也可以在項目屬性-鏈接器-輸入-附加依賴項 添加上面的庫。
移植新的dxerr.h和dxerr.cpp
在directx-sdk-samples-master\DXUT\Core中可以找到dxerr.h和dxerr.cpp,把它們拉進我們的項目中。然後使用下面的宏來進行檢查
#if defined(DEBUG) | defined(_DEBUG)
#ifndef HR
#define HR(x) { HRESULT hr = (x); if(FAILED(hr)) { DXTrace(__FILEW__, (DWORD)__LINE__, hr, L#x, true); } }
#endif
#else
#ifndef HR
#define HR(x) (x)
#endif
#endif
由於新的dxerr僅提供了DXTrace的Unicode字符集版本,需要將原來的__FILE__替換為__FILEW__,並在項目屬性頁中將字符集設置為Unicode。
COM組件智能指針
考慮到DirectX11的API是由一系列的COM組件來管理的,我們可以使用智能指針來管理這些對象,而無需過多擔心內存的泄漏。
使用該智能指針需要包含頭文件wrl/client.h,並且智能指針類模板ComPtr位於名稱空間Microsoft::WRL內。我們主要關註下面這幾個方法:
ComPtr<Interface>::Get方法返回Interface*
ComPtr<Interface>::GetAddressOf方法返回Interface**,也可以用重載的&運算符來獲取
ComPtr<Interface>::Reset方法將對裏面的對象調用Release方法,並將指針置為nullptr
初始化DirectX 11
現在假定你的電腦已經支持DirectX 11,但同時也有可能支持DirectX 11.1。因此我們在這使用的頭文件是d3d11_1.h。
要初始化DirectX11,我們需要創建這三樣東西:D3D設備、D3D設備上下文和DXGI交換鏈。
D3D設備包含了創建各種所需資源的方法。
D3D設備上下文負責對緩沖區進行渲染,綁定D3D設備創建的各種資源到不同的渲染管線。
DXGI交換鏈可以包含兩個或多個緩沖區,通常一個用於前端顯示,其余的用於後端渲染。前臺緩沖區通常是只讀的,而後備緩沖區則是我們主要進行渲染的場所。當後備緩沖區渲染完成後,通過呈現方式將前後臺緩沖區交換,在屏幕上顯示出原來剛繪制好的畫面。
這三樣東西對應的接口類為:ID3D11Device、ID3D11DeviceContext、IDXGISwapChain
而如果支持DirectX11.1的話,則對應的接口類為:ID3D11Device1、ID3D11DeviceContext1、IDXGISwapChain1,它們分別繼承自上面的三個接口類,區別在於額外提供了少數新的接口。
創建D3D設備、D3D設備上下文使用如下函數:
HRESULT WINAPI D3D11CreateDevice(
IDXGIAdapter* pAdapter, // [InOpt]適配器
D3D_DRIVER_TYPE DriverType, // [In]驅動類型
HMODULE Software, // [In]若上面為D3D_DRIVER_TYPE_SOFTWARE則這裏需要提供程序模塊
UINT Flags, // [In]使用D3D11_CREATE_DEVICE_FLAG枚舉類型
D3D_FEATURE_LEVEL* pFeatureLevels, // [In]若為nullptr則為默認特性等級,否則需要提供特性等級數組
UINT FeatureLevels, // [In]特性等級數組的元素數目
UINT SDKVersion, // [In]SDK版本,默認D3D11_SDK_VERSION
ID3D11Device** ppDevice, // [Out]輸出D3D設備
D3D_FEATURE_LEVEL* pFeatureLevel, // [Out]輸出當前應用D3D特性等級
ID3D11DeviceContext** ppImmediateContext ); //[Out]輸出D3D設備上下文
該函數可以創建DirectX11.1或者DirectX11.0的設備與設備上下文,取決於最終應用的D3D特性等級。
首先需要創建驅動類型數組進行輪詢,不過通常大多數情況都會支持D3D_DRIVER_TYPE_HARDWARE,以享受硬件加速帶來的效益:
// 驅動類型數組
D3D_DRIVER_TYPE driverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
UINT numDriverTypes = ARRAYSIZE(driverTypes);
然後就是提供特性等級數組,這裏只考慮DirectX11:
// 特性等級數組
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
};
UINT numFeatureLevels = ARRAYSIZE(featureLevels);
最後就會可以創建D3D設備和設備上下文了:
// 應用程序類中的DX11成員
Microsoft::WRL::ComPtr<ID3D11Device> md3dDevice;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> md3dImmediateContext;
Microsoft::WRL::ComPtr<IDXGISwapChain> mSwapChain;
// 應用程序類中的DX11.1成員
Microsoft::WRL::ComPtr<ID3D11Device1> md3dDevice1;
Microsoft::WRL::ComPtr<ID3D11DeviceContext1> md3dImmediateContext1;
Microsoft::WRL::ComPtr<IDXGISwapChain1> mSwapChain1;
D3D_FEATURE_LEVEL featureLevel;
D3D_DRIVER_TYPE d3dDriverType;
for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
{
d3dDriverType = driverTypes[driverTypeIndex];
hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
D3D11_SDK_VERSION, md3dDevice.GetAddressOf(), &featureLevel, md3dImmediateContext.GetAddressOf());
if (hr == E_INVALIDARG)
{
// DirectX 11.0 平臺不承認D3D_FEATURE_LEVEL_11_1所以我們需要嘗試特性等級11.0
hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,
D3D11_SDK_VERSION, md3dDevice.GetAddressOf(), &featureLevel, md3dImmediateContext.GetAddressOf());
}
if (SUCCEEDED(hr))
break;
}
如果支持DirectX11.1的話,featureLevel的結果應該為D3D_FEATURE_LEVEL_11_1,並且md3dDevice指向的是一個包含ID3D11Device1接口的對象,以及md3dImmediateContext指向的是一個包含ID3D11DeviceContext1接口的對象;而如果只支持DirectX11.0的話則為D3D_FEATURE_LEVEL_11_0。
接下來是需要創建DXGI交換鏈。
如果是DirectX11.1的話,需要先填充DXGI_SWAP_CHAIN_DESC1和DXGI_SWAP_CHAIN_FULLSCREEN_DESC這兩個結構體:
typedef struct DXGI_SWAP_CHAIN_DESC1
{
UINT Width; // 緩沖區寬度
UINT Height; // 緩沖區高度
DXGI_FORMAT Format; // 緩沖區數據格式
BOOL Stereo; // 忽略
DXGI_SAMPLE_DESC SampleDesc; // 采樣描述
DXGI_USAGE BufferUsage; // 緩沖區用途
UINT BufferCount; // 緩沖區數目
DXGI_SCALING Scaling; // 忽略
DXGI_SWAP_EFFECT SwapEffect; // 交換效果
DXGI_ALPHA_MODE AlphaMode; // 忽略
UINT Flags; // 使用DXGI_SWAP_CHAIN_FLAG枚舉類型
} DXGI_SWAP_CHAIN_DESC1;
typedef struct DXGI_SAMPLE_DESC
{
UINT Count; // 采樣數
UINT Quality; // 質量等級
} DXGI_SAMPLE_DESC;
typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
{
DXGI_RATIONAL RefreshRate; // 刷新率
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // 忽略
DXGI_MODE_SCALING Scaling; // 忽略
BOOL Windowed; // 是否窗口化
} DXGI_SWAP_CHAIN_FULLSCREEN_DESC;
typedef struct DXGI_RATIONAL
{
UINT Numerator; // 刷新率分子
UINT Denominator; // 刷新率分母
} DXGI_RATIONAL;
而如果是DirectX11.0的話,需要先填充DXGI_SWAP_CHAIN_DESC結構體:
typedef struct DXGI_SWAP_CHAIN_DESC
{
DXGI_MODE_DESC BufferDesc; // 緩沖區描述
DXGI_SAMPLE_DESC SampleDesc; // 采樣描述
DXGI_USAGE BufferUsage; // 緩沖區用途
UINT BufferCount; // 後備緩沖區數目
HWND OutputWindow; // 輸出窗口句柄
BOOL Windowed; // 窗口化?
DXGI_SWAP_EFFECT SwapEffect; // 交換效果
UINT Flags; // 使用DXGI_SWAP_CHAIN_FLAG枚舉類型
} DXGI_SWAP_CHAIN_DESC;
typedef struct DXGI_SAMPLE_DESC
{
UINT Count; // 采樣數
UINT Quality; // 質量等級
} DXGI_SAMPLE_DESC;
typedef struct DXGI_MODE_DESC
{
UINT Width; // 緩沖區寬度
UINT Height; // 緩沖區高度
DXGI_RATIONAL RefreshRate; // 刷新率分數表示法
DXGI_FORMAT Format; // 緩沖區數據格式
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // 忽略
DXGI_MODE_SCALING Scaling; // 忽略
} DXGI_MODE_DESC;
typedef struct DXGI_RATIONAL
{
UINT Numerator; // 刷新率分子
UINT Denominator; // 刷新率分母
} DXGI_RATIONAL;
接下來我們需要創建交換鏈。
DirectX11.0下使用的創建方法為IDXGIFactory::CreateSwapChain:
HRESULT IDXGIFactory::CreateSwapChain(
IUnknown *pDevice, // [In]D3D設備
DXGI_SWAP_CHAIN_DESC *pDesc, // [In]交換鏈描述
IDXGISwapChain **ppSwapChain); // [Out]輸出交換鏈對象
而DirectX11.1使用的創建方法為IDXGIFactory2::CreateSwapChainForHwnd:
HRESULT IDXGIFactory2::CreateSwapChainForHwnd(
IUnknown *pDevice, // [In]D3D設備
HWND hWnd, // [In]窗口句柄
const DXGI_SWAP_CHAIN_DESC1 *pDesc, // [In]交換鏈描述1
const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, // [In]交換鏈全屏描述,可選
IDXGIOutput *pRestrictToOutput, // [In]忽略
IDXGISwapChain1 **ppSwapChain); // [Out]輸出交換鏈對象
但現在我們需要先拿到包含IDXGIFactory或者IDXGIFactory2接口的對象:
ComPtr<IDXGIDevice> dxgiDevice = nullptr;
ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
ComPtr<IDXGIFactory1> dxgiFactory1 = nullptr;
ComPtr<IDXGIDevice1> dxgiDevice1 = nullptr;
ComPtr<IDXGIAdapter1> dxgiAdapter1 = nullptr;
ComPtr<IDXGIFactory2> dxgiFactory2 = nullptr;
HR(md3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(dxgiDevice.GetAddressOf())));
HR(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()));
HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf())));
這時候可以確定dxgiFactory1包含接口IDXGIFactory1,然後檢查它是否包含接口IDXGIFactory2,包含的話就說明支持DirectX11.1,然後獲取ID3D11Device1和ID3D11DeviceContext1接口對象並創建包含IDXGISwapChain1接口的對象,否則就創建IDXGISwapChain接口的對象:
// 如果包含,則說明支持DX11.1
if (dxgiFactory2 != nullptr)
{
HR(md3dDevice->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(md3dDevice1.GetAddressOf())));
HR(md3dImmediateContext->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>(md3dImmediateContext1.GetAddressOf())));
// 填充各種結構體用以描述交換鏈
DXGI_SWAP_CHAIN_DESC1 sd;
ZeroMemory(&sd, sizeof(sd));
sd.Width = mClientWidth;
sd.Height = mClientHeight;
sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 1;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
sd.Flags = 0;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fd;
fd.RefreshRate.Numerator = 60;
fd.RefreshRate.Denominator = 1;
fd.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
fd.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
fd.Windowed = TRUE;
// 為當前窗口創建交換鏈
HR(dxgiFactory2->CreateSwapChainForHwnd(md3dDevice.Get(), mhMainWnd, &sd, &fd, nullptr, mSwapChain1.GetAddressOf()));
mSwapChain1->QueryInterface(__uuidof(IDXGISwapChain), reinterpret_cast<void**>(mSwapChain.GetAddressOf()));
}
else
{
// 填充DXGI_SWAP_CHAIN_DESC用以描述交換鏈
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferDesc.Width = mClientWidth;
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
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 = 1;
sd.OutputWindow = mhMainWnd;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
sd.Flags = 0;
HR(dxgiFactory1->CreateSwapChain(md3dDevice.Get(), &sd, mSwapChain.GetAddressOf()));
}
這時候,如果支持DirectX11.1的話,md3dDevice和md3dDevice1其實都指向同一個對象,md3dImmediateContext和md3dImmediateContext1,mSwapChain和mSwapChain1也是一樣的,區別僅僅在於後者實現了額外的一些接口,問題不大。因此不管是DirectX11.1還是DirectX11.0,後續都主要使用md3dDevice,md3dImmediateContext和mSwapChain來進行操作。
設置全屏
默認情況下按ALT+ENTER可以切換成全屏,如果不想要這種操作,可以使用剛才創建的dxgiFactory1,按照下面的方式來調用即可:
dxgiFactory1->MakeWindowAssociation(mhMainWnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);
這樣DXGI就不會監聽Windows消息隊列,並且屏蔽掉了對接收到ALT+ENTER消息的處理。
渲染目標視圖(RenderTargetView)和深度模板視圖(DepthStencilView)
接下來我們需要創建渲染目標視圖(RenderTargetView)並綁定到渲染管線的輸出合並階段,使得交換鏈可以操作後備緩沖區。此時我們創建好的交換鏈已經包含1個後備緩沖區了,我們需要使用渲染目標視圖來綁定這個後備緩沖區先。
使用IDXGISwapChain::GetBuffer來獲取交換鏈的後備緩沖區:
HRESULT IDXGISwapChain::GetBuffer(
UINT Buffer, // [In]緩沖區索引號,從0到BufferCount - 1
REFIID riid, // [In]緩沖區的接口類型ID
void **ppSurface); // [Out]獲取到的緩沖區
然後再使用下面的方法來獲取渲染目標視圖:
HRESULT ID3D11Device::CreateRenderTargetView(
ID3D11Resource *pResource, // [In]緩沖區資源
const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, // 忽略
ID3D11RenderTargetView **ppRTView); // [Out]獲取渲染目標視圖
// 重設交換鏈並且重新創建渲染目標視圖
ComPtr<ID3D11Texture2D> backBuffer;
HR(mSwapChain->ResizeBuffers(1, mClientWidth, mClientHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0));
HR(mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())));
HR(md3dDevice->CreateRenderTargetView(backBuffer.Get(), 0, mRenderTargetView.GetAddressOf()));
除了渲染目標視圖外,我們還需要創建深度緩沖區用於深度測試。通過D3D設備可以新建一個緩沖區,但在此之前我們需要先描述該緩沖區的信息:
typedef struct D3D11_TEXTURE2D_DESC
{
UINT Width; // 緩沖區寬度
UINT Height; // 緩沖區高度
UINT MipLevels; // Mip等級
UINT ArraySize; // 紋理數組中的紋理數量,默認1
DXGI_FORMAT Format; // 緩沖區數據格式
DXGI_SAMPLE_DESC SampleDesc; // 忽略
D3D11_USAGE Usage; // 數據的CPU/GPU訪問權限
UINT BindFlags; // 使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型
UINT CPUAccessFlags; // 使用D3D11_CPU_ACCESS_FLAG枚舉來決定CPU訪問權限
UINT MiscFlags; // 使用D3D11_RESOURCE_MISC_FLAG枚舉,這裏默認0
} D3D11_TEXTURE2D_DESC;
我們可以這樣填充:
D3D11_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
這時候我們就可以用方法ID3D11Device::CreateTexture2D來創建2D紋理:
HRESULT ID3D11Device::CreateTexture2D(
const D3D11_TEXTURE2D_DESC *pDesc, // [In] 2D紋理描述信息
const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用於初始化的資源
ID3D11Texture2D **ppTexture2D); // [Out] 獲取到的2D紋理
再通過方法ID3D11Device::CreateDepthStencilView來創建深度模板視圖,綁定剛創建的深度緩沖區:
HRESULT ID3D11Device::CreateDepthStencilView(
ID3D11Resource *pResource, // [In] 需要綁定的資源
const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc, // [In] 深度緩沖區描述
ID3D11DepthStencilView **ppDepthStencilView); // [Out] 獲取到的深度模板視圖
下面兩句話演示了創建深度模板緩沖區和深度模板視圖:
// 創建深度緩沖區以及深度模板視圖
HR(md3dDevice->CreateTexture2D(&depthStencilDesc, 0, mDepthStencilBuffer.GetAddressOf()));
HR(md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), 0, mDepthStencilView.GetAddressOf()));
最後,我們需要調用方法ID3D11DeviceContext::OMSetRenderTargets,將渲染目標視圖和深度模板視圖一同綁定到輸出合並階段:
void ID3D11DeviceContext::OMSetRenderTargets(
UINT NumViews, // [In] 視圖數目
ID3D11RenderTargetView *const *ppRenderTargetViews, // [In] 渲染目標視圖數組
ID3D11DepthStencilView *pDepthStencilView) = 0; // [In] 深度模板視圖
下面演示了這些操作:
md3dImmediateContext->OMSetRenderTargets(1, mRenderTargetView.GetAddressOf(), mDepthStencilView.Get());
視口
最終我們還需要決定將整個視圖輸出到特定的範圍。因此我們需要使用D3D11_VIEWPORT來設置視口
typedef struct D3D11_VIEWPORT
{
FLOAT TopLeftX; // 屏幕左上角起始位置X
FLOAT TopLeftY; // 屏幕左上角起始位置Y
FLOAT Width; // 寬度
FLOAT Height; // 高度
FLOAT MinDepth; // 最小深度,必須為0.0f
FLOAT MaxDepth; // 最大深度,必須為1.0f
} D3D11_VIEWPORT;
ID3D11DeviceContext::RSSetViewports方法將設置1個或多個視口:
void ID3D11DeviceContext::RSSetViewports(
UINT NumViewports, // 視口數目
const D3D11_VIEWPORT *pViewports); // 視口數組
將視圖輸出到整個屏幕需要進行下面的操作:
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;
md3dImmediateContext->RSSetViewports(1, &mScreenViewport);
畫面繪制
在每一幀畫面繪制的操作中,我們需要清理一遍渲染目標視圖,
可以通過ID3D11DeviceContext::ClearRenderTargetView方法:
void ID3D11DeviceContext::ClearRenderTargetView(
ID3D11RenderTargetView *pRenderTargetView, // 渲染目標視圖
const FLOAT ColorRGBA[4]); // 指定覆蓋顏色
我們用藍色進行清屏,即RGBA=(0.0,0.0,1.0,1.0)的值操作:
float blue[4] = {0.0f, 0.0f, 1.0f, 1.0f);
md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast<const float*>(&blue));
然後我們還要使用ID3D11DeviceContext::ClearDepthStencilView來清空深度模板緩沖區:
void ID3D11DeviceContext::ClearDepthStencilView(
ID3D11DepthStencilView *pDepthStencilView, // 深度模板視圖
UINT ClearFlags, // [In]D3D11_CLEAR_FLAG枚舉
FLOAT Depth, // [In]深度
UINT8 Stencil); // [In]模板初始值
例如:
md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
最後我們直接調用IDXGISwapChain::Present方法進行前後臺交換的呈現:
mSwapChain->Present(0, 0);
完成初始化後效果應該如下:
01 DirectX11初始化