日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

DirectX11 With Windows SDK--01 DirectX11初始化

發布時間:2025/3/20 windows 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 DirectX11 With Windows SDK--01 DirectX11初始化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
DirectX11 With Windows SDK--01 DirectX11初始化 原文:DirectX11 With Windows SDK--01 DirectX11初始化

前言

由于個人覺得龍書里面第4章提供的Direct3D 初始化項目封裝得比較好,而且DirectX SDK Samples里面的初始化程序過于精簡,不適合后續使用,故選擇了以Init Direct3D項目作為框架,然后還使用了微軟提供的示例項目,兩者結合到一起。建議下載項目配合閱讀。

這一章內容大部分屬于龍書的內容,但仍有一些不同的地方。因為后續的所有項目都使用該基礎框架,你也可以直接使用第一章的項目源碼,然后需要了解以下差異部分:

  • ComPtr智能指針
  • 新的HR宏
  • D3D11.1設備的創建
  • 其中前面兩個部分在下面的鏈接可以看到:

    章節
    ComPtr智能指針
    HR宏關于dxerr庫的替代方案

    DirectX11 With Windows SDK完整目錄

    Github項目源碼

    歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。

    如何開啟新項目

    安裝準備

    如果你打算從VS2015開始,則在安裝的時候需要勾選下列選項:

    編程語言那一項會自動被勾選。

    而如果是使用VS2017或19的話,則在安裝的時候需要勾選下列選項:

    注意:VS2015使用的Windows SDK版本為10.0.14393.0,而VS2017與VS2019使用的Windows SDK版本為10.0.17763.0

    安裝完成后,新建項目需要從空項目開始。

    移除你的項目中有關DX SDK的庫路徑和包含路徑

    如果你曾經用過DX SDK來編寫DX項目,務必要把你之前配置的DX SDK庫路徑和包含路徑給清理掉,使用項目默認的庫路徑和包含路徑!

    鏈接靜態庫

    這里的每一個項目都需要包含靜態庫:d3d11.lib,dxgi.lib,dxguid.lib,D3DCompiler.lib和winmm.lib。可以在d3dApp.h添加下面的語句:

    #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")

    也可以在項目屬性-鏈接器-輸入-附加依賴項 添加上面的庫。

    字符集設置為Unicode

    在項目屬性頁中可以直接進行修改。

    Win7系統下的額外配置

    由于Win10 SDK中的某些函數在Win7是不支持的,我們還需要在屬性頁-配置屬性-C/C++ -預處理器中,添加預處理器定義以限制API集合:_WIN32_WINNT=0x601

    現在最基本的配置已經完成,你可以嘗試將本教程用到的項目01中所有的頭文件和源文件添加進你的項目,然后生成項目并運行以檢測。

    項目結構

    現在把目光拉回到我們的教程項目。目前項目中包含頭文件的具體功能如下:

    頭文件功能
    d3dApp.hDirect3D應用程序框架類
    d3dUtil.h包含一些常用頭文件及自己編寫的函數
    DXTrace.h包含了HR宏與DXTraceW函數
    GameApp.h游戲應用程序擴展類,游戲邏輯在這里實現,繼承自D3DApp類
    GameTimer.h游戲計時器類

    其中d3dApp類和GameTimer類是龍書源碼提供的,我們可以搬運過來,但是對d3dApp框架類我們還需要進行大幅度修改,畢竟我們的最終目的就是要完全脫離舊的DirectX SDK,使用Windows SDK來實現DX11。修改完成后,d3dApp就幾乎已經定型而不需要我們操心了。

    GameApp類則是我們編寫游戲邏輯的地方,這里需要進行逐幀的更新及繪制。

    D3DApp框架類

    D3DApp.h展示了框架類的聲明,這里的接口類指針全部換上了ComPtr智能指針:

    class D3DApp { public:D3DApp(HINSTANCE hInstance); // 在構造函數的初始化列表應當設置好初始參數virtual ~D3DApp();HINSTANCE AppInst()const; // 獲取應用實例的句柄HWND MainWnd()const; // 獲取主窗口句柄float AspectRatio()const; // 獲取屏幕寬高比int Run(); // 運行程序,進行游戲主循環// 框架方法。客戶派生類需要重載這些方法以實現特定的應用需求virtual bool Init(); // 該父類方法需要初始化窗口和Direct3D部分virtual void OnResize(); // 該父類方法需要在窗口大小變動的時候調用virtual void UpdateScene(float dt) = 0; // 子類需要實現該方法,完成每一幀的更新virtual void DrawScene() = 0; // 子類需要實現該方法,完成每一幀的繪制virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);// 窗口的消息回調函數 protected:bool InitMainWindow(); // 窗口初始化bool InitDirect3D(); // Direct3D初始化void CalculateFrameStats(); // 計算每秒幀數并在窗口顯示protected:HINSTANCE m_hAppInst; // 應用實例句柄HWND m_hMainWnd; // 主窗口句柄bool m_AppPaused; // 應用是否暫停bool m_Minimized; // 應用是否最小化bool m_Maximized; // 應用是否最大化bool m_Resizing; // 窗口大小是否變化bool m_Enable4xMsaa; // 是否開啟4倍多重采樣UINT m_4xMsaaQuality; // MSAA支持的質量等級GameTimer m_Timer; // 計時器// 使用模板別名(C++11)簡化類型名template <class T>using ComPtr = Microsoft::WRL::ComPtr<T>;// Direct3D 11ComPtr<ID3D11Device> m_pd3dDevice; // D3D11設備ComPtr<ID3D11DeviceContext> m_pd3dImmediateContext; // D3D11設備上下文ComPtr<IDXGISwapChain> m_pSwapChain; // D3D11交換鏈// Direct3D 11.1ComPtr<ID3D11Device1> m_pd3dDevice1; // D3D11.1設備ComPtr<ID3D11DeviceContext1> m_pd3dImmediateContext1; // D3D11.1設備上下文ComPtr<IDXGISwapChain1> m_pSwapChain1; // D3D11.1交換鏈// 常用資源ComPtr<ID3D11Texture2D> m_pDepthStencilBuffer; // 深度模板緩沖區ComPtr<ID3D11RenderTargetView> m_pRenderTargetView; // 渲染目標視圖ComPtr<ID3D11DepthStencilView> m_pDepthStencilView; // 深度模板視圖D3D11_VIEWPORT m_ScreenViewport; // 視口// 派生類應該在構造函數設置好這些自定義的初始參數std::wstring m_MainWndCaption; // 主窗口標題int m_ClientWidth; // 視口寬度int m_ClientHeight; // 視口高度 };

    而在d3dApp.cpp中,可以看到有一個全局變量g_pd3dApp:

    namespace {// This is just used to forward Windows messages from a global window// procedure to our member function window procedure because we cannot// assign a member function to WNDCLASS::lpfnWndProc.D3DApp* g_pd3dApp = 0; }

    設置該全局變量是因為在窗口創建的時候需要綁定一個回調函數,受到回調函數指針類型的限制,我們不可以綁定d3dApp::MainWndProc的成員方法,所以還需要實現一個全局函數用于回調函數的綁定:

    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 m_hMainWnd is valid.return g_pd3dApp->MsgProc(hwnd, msg, wParam, lParam); }

    D3DApp::InitWindow和D3DApp::MsgProc方法目前在這里不做過多描述,因為這不是教程的重點部分,但后續可能還要回頭修改這兩個方法。有興趣的可以去MSDN查閱這些函數和結構體的信息。

    Direct3D初始化

    注意當前項目使用的是d3d11_1.h頭文件

    Direct3D初始化階段首先需要創建D3D設備D3D設備上下文

    D3D設備(ID3D11Device)包含了創建各種所需資源的方法,最常用的有:資源類(ID3D11Resource, 包含紋理和緩沖區)視圖類以及著色器

    D3D設備上下文(ID3D11DeviceContext)可以看做是一個渲染管線,負責渲染工作,它需要綁定來自D3D設備創建的各種資源、視圖和著色器才能正常運轉,除此之外,它還能夠負責對資源的直接讀寫操作。

    而如果支持Direct3D 11.1的話,則對應的接口類為:ID3D11Device1、ID3D11DeviceContext1,它們分別繼承自上面的兩個接口類,區別在于額外提供了少數新的接口,并且接口方法的實現可能會有所區別。

    現在,我們從D3DApp::InitDirect3D方法開始,一步步進行分析。

    D3D設備與D3D設備上下文的創建

    D3D11CreateDevice函數--創建D3D設備與D3D設備上下文

    創建D3D設備、D3D設備上下文使用如下函數:

    HRESULT WINAPI D3D11CreateDevice(IDXGIAdapter* pAdapter, // [In_Opt]適配器D3D_DRIVER_TYPE DriverType, // [In]驅動類型HMODULE Software, // [In_Opt]若上面為D3D_DRIVER_TYPE_SOFTWARE則這里需要提供程序模塊UINT Flags, // [In]使用D3D11_CREATE_DEVICE_FLAG枚舉類型D3D_FEATURE_LEVEL* pFeatureLevels, // [In_Opt]若為nullptr則為默認特性等級,否則需要提供特性等級數組UINT FeatureLevels, // [In]特性等級數組的元素數目UINT SDKVersion, // [In]SDK版本,默認D3D11_SDK_VERSIONID3D11Device** ppDevice, // [Out_Opt]輸出D3D設備D3D_FEATURE_LEVEL* pFeatureLevel, // [Out_Opt]輸出當前應用D3D特性等級ID3D11DeviceContext** ppImmediateContext ); //[Out_Opt]輸出D3D設備上下文
  • 關于pAdapter(適配器),我們可以將它看做是對顯示卡設備的一層封裝,通過該參數,我們可以指定需要使用哪個顯示卡設備。通常該參數我們設為nullptr,這樣就可以交由上層驅動來幫我們決定使用哪個顯卡,或者在NVIDIA控制面板來設置當前程序要使用哪個顯卡。如果想要在應用層決定,使用IDXGIFactory::EnumAdapters方法可以枚舉當前可用的顯示卡設備。在最底下的練習題你將學會如何指定顯示卡設備來創建Direct3D 11.x設備。
  • DriverType則指定了驅動類型,不過通常大多數情況都會支持D3D_DRIVER_TYPE_HARDWARE,以享受硬件加速帶來的效益。現在我們建立一個驅動數組,然后自己通過for循環的方式進行輪詢:
  • // 驅動類型數組 D3D_DRIVER_TYPE driverTypes[] = {D3D_DRIVER_TYPE_HARDWARE, // 硬件驅動D3D_DRIVER_TYPE_WARP, // WARP驅動D3D_DRIVER_TYPE_REFERENCE, // 軟件驅動 }; UINT numDriverTypes = ARRAYSIZE(driverTypes);

    如果D3D_DRIVER_TYPE_HARDWARE不支持,則需要自己通過循環的形式再檢查D3D_DRIVER_TYPE_WARP是否支持。

    關于D3D_DRIVER_TYPE的詳細描述,可以去查閱MSDN官方文檔詳細了解一下。

  • Flags對應的是D3D11_CREATE_DEVICE_FLAG枚舉值,如果需要D3D設備調試的話(在Debug模式下),可以指定D3D11_CREATE_DEVICE_DEBUG枚舉值。指定該值后,可以在出現程序異常的時候觀察調試輸出窗口的信息。
  • pFeatureLevels是一個特性等級數組,通過函數內部進行輪詢以檢測所支持的特性等級:
  • // 特性等級數組 D3D_FEATURE_LEVEL featureLevels[] = {D3D_FEATURE_LEVEL_11_1,D3D_FEATURE_LEVEL_11_0, }; UINT numFeatureLevels = ARRAYSIZE(featureLevels);

    注意:如果你的系統支持Direct3D 11.1的API,卻把pFeatureLevels設置為nullptr,D3D11CreateDevice將創建出特性等級為D3D_FEATURE_LEVEL_11_0的設備。而如果你的系統不支持Direct3D 11.1的API,D3D11CreateDevice會立即停止特性數組的輪詢并返回E_INVALIDARG。為此,你必須要從D3D_FEATURE_LEVEL_11_0或更低特性等級開始輪詢。

    在Win10, Win8.x 或 Win7 SP1且安裝了KB2670838補丁的系統都支持Direct3D 11.1的API,而純Win7系統僅支持Direct3D 11的API

    從上面的描述我們可以得知,特性等級D3D設備的版本并不是互相對應的:

    1. 特性等級的支持情況取決于當前使用的顯示適配器

    2. D3D設備的版本取決于所處的系統

    由于該函數可以創建Direct3D 11.1(或者Direct3D 11.0)的設備與設備上下文,但都統一輸出ID3D11Device和ID3D11DeviceContext。如果想要查看是否支持Direct3D 11.1的API,可以使用下面的方式:

    ComPtr<ID3D11Device1> md3dDevice1; HRESULT hr = md3dDevice.As(&md3dDevice1);

    同理,想要查看是否支持Direct3D 11.2的API,則可以這樣:

    ComPtr<ID3D11Device2> md3dDevice2; HRESULT hr = md3dDevice.As(&md3dDevice2);

    由于每個電腦的顯示卡設備情況有所差異,該教程采用的是默認顯示卡(有可能會用到集成顯卡),而不是指定顯示卡:

    HRESULT hr = S_OK;// 創建D3D設備 和 D3D設備上下文 UINT createDeviceFlags = 0; #if defined(DEBUG) || defined(_DEBUG) createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif // 驅動類型數組 D3D_DRIVER_TYPE driverTypes[] = {D3D_DRIVER_TYPE_HARDWARE,D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_REFERENCE, }; UINT numDriverTypes = ARRAYSIZE(driverTypes);// 特性等級數組 D3D_FEATURE_LEVEL featureLevels[] = {D3D_FEATURE_LEVEL_11_1,D3D_FEATURE_LEVEL_11_0, }; UINT numFeatureLevels = ARRAYSIZE(featureLevels);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, m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContext.GetAddressOf());if (hr == E_INVALIDARG){// Direct3D 11.0 的API不承認D3D_FEATURE_LEVEL_11_1,所以我們需要嘗試特性等級11.0以及以下的版本hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,D3D11_SDK_VERSION, m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContext.GetAddressOf());}if (SUCCEEDED(hr))break; }if (FAILED(hr)) {MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);return false; }// 檢測是否支持特性等級11.0或11.1 if (featureLevel != D3D_FEATURE_LEVEL_11_0 && featureLevel != D3D_FEATURE_LEVEL_11_1) {MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);return false; }// 檢測 MSAA支持的質量等級 md3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m_4xMsaaQuality); assert(m_4xMsaaQuality > 0);

    注意:

  • 支持特性等級11_0的顯示適配器必然支持所有渲染目標紋理格式的4倍多重采樣
  • 即便m_4xMsaaQuality的返回值為1,也不代表沒法啟動4倍多重采樣,該成員只是代表模式的種類數目
  • DXGI初始化

    DXGI交換鏈

    DXGI交換鏈(IDXGISwapChain)緩存了一個或多個表面(2D紋理),它們都可以稱作后備緩沖區(backbuffer)。后備緩沖區則是我們主要進行渲染的場所,我們可以將這些緩沖區通過合適的手段成為渲染管線的輸出對象。在進行呈現(Present)的時候有兩種方法:

  • BitBlt Model(位塊傳輸模型):將后備緩沖區的數據進行BitBlt(位塊傳輸),傳入到DX應用中的桌面窗口管理器(DWM)表面,然后進行翻轉以交給前臺顯示。使用這種模型至少需要一個后備緩沖區。事實上,這也是Win32應用程序最常使用的方式,在進行呈現后,渲染管線仍然是對同一個后備緩沖區進行輸出。(支持Windows 7及更高版本)
  • Flip Model(翻轉模型):該模型可以避免上一種方式多余的復制,后備緩沖區表面可以直接與桌面窗口管理器(DWM)內的表面進行翻轉。但是需要創建至少兩個后備緩沖區,并且在每次完成呈現后切換到另一個后備緩沖區進行渲染。該模型可以用于Win32應用程序以及UWP應用程序(需要DXGI1.2,支持Windows 8及更高版本)
  • 注意:考慮到要兼容Win7系統,而且由于我們編寫的是Win32應用程序,因此這里使用的是第一種模型。同時這也是絕大多數教程所使用的。對第二種感興趣的可以了解下面的鏈接:

    DXGI翻轉模型

    接下來我們需要了解D3D與DXGI各版本的對應關系,這十分重要:

    Direct3D API支持版本對應包含DXGI版本對應DXGI接口可枚舉的顯示適配器可創建的交換鏈
    Direct3D 11.1DXGI 1.2IDXGIFactory2IDXGIAdaptor2IDXGISwapChain1
    Direct3D 11.0/10.1DXGI 1.1IDXGIFactory1IDXGIAdaptor1IDXGISwapChain
    Direct3D 10.0DXGI 1.0IDXGIFactoryIDXGIAdaptorIDXGISwapChain

    d3d與dxgi版本的對應關系你可以通過觀察這些d3d頭文件所包含的dxgi頭文件來了解。

    DXGI交換鏈的創建需要通過IDXGIFactory::CreateSwapChain方法進行。但是,如果是要創建Direct3D 11.1對應的交換鏈,則需要通過IDXGIFactory2::CreateSwapChainForHwnd方法進行。

    獲取IDXGIFactory1或IDXGIFactory2接口類

    現在我們需要先拿到包含IDXGIFactory1接口的對象,但是為了拿到該對象還需要經歷一些磨難。

    之前在創建D3D設備時使用的是默認的顯卡適配器IDXGIAdapter(對于雙顯卡的筆記本大概率使用的是集成顯卡),而創建出來的D3D設備本身實現了IDXGIDevice接口,通過該對象,我們可以獲取到當前所用的顯卡適配器IDXGIAdapter對象,這樣我們再通過查詢它的父級找到是哪個IDXGIFactory枚舉出來的適配器。

    ComPtr<IDXGIDevice> dxgiDevice = nullptr; ComPtr<IDXGIAdapter> dxgiAdapter = nullptr; ComPtr<IDXGIFactory1> dxgiFactory1 = nullptr; // D3D11.0(包含DXGI1.1)的接口類 ComPtr<IDXGIFactory2> dxgiFactory2 = nullptr; // D3D11.1(包含DXGI1.2)特有的接口類// 為了正確創建 DXGI交換鏈,首先我們需要獲取創建 D3D設備 的 DXGI工廠,否則會引發報錯: // "IDXGIFactory::CreateSwapChain: This function is being called with a device from a different IDXGIFactory." HR(m_pd3dDevice.As(&dxgiDevice)); HR(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf())));// 查看該對象是否包含IDXGIFactory2接口 hr = dxgiFactory1.As(&dxgiFactory2); // 如果包含,則說明支持D3D11.1 if (dxgiFactory2 != nullptr) {HR(m_pd3dDevice.As(&m_pd3dDevice1));HR(m_pd3dImmediateContext.As(&m_pd3dImmediateContext1));// ... 省略交換鏈IDXGISwapChain1的創建 } else {// ... 省略交換鏈IDXGISwapChain的創建 }

    同時之前也提到,如果支持Direct3D 11.1的話,我們就可以拿到DXGI 1.2的相關對象(如IDXGIFactory2)。

    這時m_pd3dDevice和m_pd3dDevice1其實都指向同一個對象,m_pd3dImmediateContext和m_pd3dImmediateContext1,m_pSwapChain和m_pSwapChain1也是一樣的,區別僅僅在于后者實現了額外的一些接口,問題不大。因此不管是Direct3D 11.1還是Direct3D 11.0,后續都主要使用m_pd3dDevice,m_pd3dImmediateContext和m_pSwapChain來進行操作。

    IDXGIFactory2::CreateSwapChainForHwnd方法--Direct3D 11.1創建交換鏈

    如果是Direct3D 11.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; // MSAA采樣數UINT Quality; // MSAA質量等級 } 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;

    填充好后,Direct3D 11.1使用的創建方法為IDXGIFactory2::CreateSwapChainForHwnd:

    HRESULT IDXGIFactory2::CreateSwapChainForHwnd(IUnknown *pDevice, // [In]D3D設備HWND hWnd, // [In]窗口句柄const DXGI_SWAP_CHAIN_DESC1 *pDesc, // [In]交換鏈描述1const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, // [In]交換鏈全屏描述,可選IDXGIOutput *pRestrictToOutput, // [In]忽略IDXGISwapChain1 **ppSwapChain); // [Out]輸出交換鏈對象

    上面第一個省略的部分代碼如下:

    // 填充各種結構體用以描述交換鏈 DXGI_SWAP_CHAIN_DESC1 sd; ZeroMemory(&sd, sizeof(sd)); sd.Width = m_ClientWidth; sd.Height = m_ClientHeight; sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 是否開啟4倍多重采樣? if (m_Enable4xMsaa) {sd.SampleDesc.Count = 4;sd.SampleDesc.Quality = m_4xMsaaQuality - 1; } else {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(m_pd3dDevice.Get(), m_hMainWnd, &sd, &fd, nullptr, m_pSwapChain1.GetAddressOf())); HR(m_pSwapChain1.As(&m_pSwapChain));

    后續我們還可以通過該交換鏈來手動指定是否需要全屏

    IDXGIFactory::CreateSwapChain方法--Direct3D 11創建交換鏈

    如果是Direct3D 11.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; // MSAA采樣數UINT Quality; // MSAA質量等級 } 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;

    Direct3D 11.0下使用的創建方法為IDXGIFactory::CreateSwapChain:

    HRESULT IDXGIFactory::CreateSwapChain(IUnknown *pDevice, // [In]D3D設備DXGI_SWAP_CHAIN_DESC *pDesc, // [In]交換鏈描述IDXGISwapChain **ppSwapChain); // [Out]輸出交換鏈對象

    第二個省略的部分代碼如下:

    // 填充DXGI_SWAP_CHAIN_DESC用以描述交換鏈 DXGI_SWAP_CHAIN_DESC sd; ZeroMemory(&sd, sizeof(sd)); sd.BufferDesc.Width = m_ClientWidth; sd.BufferDesc.Height = m_ClientHeight; 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; // 是否開啟4倍多重采樣? if (m_Enable4xMsaa) {sd.SampleDesc.Count = 4;sd.SampleDesc.Quality = m_4xMsaaQuality - 1; } else {sd.SampleDesc.Count = 1;sd.SampleDesc.Quality = 0; } sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.BufferCount = 1; sd.OutputWindow = m_hMainWnd; sd.Windowed = TRUE; sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; sd.Flags = 0; HR(dxgiFactory1->CreateSwapChain(m_pd3dDevice.Get(), &sd, m_pSwapChain.GetAddressOf()));

    禁用ALT+ENTER與全屏的關聯

    默認情況下按ALT+ENTER可以切換成全屏,如果不想要這種操作,可以使用剛才創建的dxgiFactory1,按照下面的方式來調用即可:

    dxgiFactory1->MakeWindowAssociation(mhMainWnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);

    這樣DXGI就不會監聽Windows消息隊列,并且屏蔽掉了對接收到ALT+ENTER消息的處理。

    DXGI交換鏈與Direct3D設備的交互

    在創建好上述對象后,如果窗口的大小是固定的,則需要經歷下面的步驟:

  • 獲取交換鏈后備緩沖區的ID3D11Texture2D接口對象
  • 為后備緩沖區創建渲染目標視圖ID3D11RenderTargetView
  • 通過D3D設備創建一個ID3D11Texture2D用作深度/模板緩沖區,要求與后備緩沖區等寬高
  • 創建深度/模板視圖ID3D11DepthStrenilView,綁定剛才創建的2D紋理
  • 通過D3D設備上下文,在渲染管線的輸出合并階段設置渲染目標
  • 在渲染管線的光柵化階段設置好渲染的視口區域
  • 接下來需要快速了解一遍上述步驟所需要用到的API。

    獲取交換鏈的后備緩沖區

    由于此前我們創建好的交換鏈已經包含1個后備緩沖區了,我們可以通過IDXGISwapChain::GetBuffer方法直接獲取后備緩沖區的ID3D11Texture2D接口:

    HRESULT IDXGISwapChain::GetBuffer( UINT Buffer, // [In]緩沖區索引號,從0到BufferCount - 1REFIID riid, // [In]緩沖區的接口類型IDvoid **ppSurface); // [Out]獲取到的緩沖區

    為后備緩沖區創建渲染目標視圖

    渲染目標視圖用于將渲染管線的運行結果輸出給其綁定的資源,很明顯它也只能夠設置給輸出合并階段。渲染目標視圖要求其綁定的資源是允許GPU讀寫的,因為在作為管線輸出時會通過GPU寫入數據,并且在以后進行混合操作時還需要在GPU讀取該資源。通常渲染目標是一個二維的紋理,但它依舊可能會綁定其余類型的資源。這里不做討論。

    現在我們需要將后備緩沖區綁定到渲染目標視圖,使用ID3D11Device::CreateRenderTargetView方法來創建:

    HRESULT ID3D11Device::CreateRenderTargetView( ID3D11Resource *pResource, // [In]待綁定到渲染目標視圖的資源const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, // [In]忽略ID3D11RenderTargetView **ppRTView); // [Out]獲取渲染目標視圖

    現在這里演示了獲取后備緩沖區紋理,并綁定到渲染目標視圖的過程:

    // 重設交換鏈并且重新創建渲染目標視圖 ComPtr<ID3D11Texture2D> backBuffer; HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf()))); HR(m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()));

    創建深度/模板緩沖區

    ID3D11Device::CreateTexture2D--創建一個2D紋理

    除了渲染目標視圖外,我們還需要創建深度/模板緩沖區用于深度測試。深度/模板緩沖區也是一個2D紋理,要求其寬度和高度必須要和窗口寬高保持一致。

    通過D3D設備可以新建一個2D紋理,但在此之前我們需要先描述該緩沖區的信息:

    typedef struct D3D11_TEXTURE2D_DESC {UINT Width; // 緩沖區寬度UINT Height; // 緩沖區高度UINT MipLevels; // Mip等級UINT ArraySize; // 紋理數組中的紋理數量,默認1DXGI_FORMAT Format; // 緩沖區數據格式DXGI_SAMPLE_DESC SampleDesc; // MSAA采樣描述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;

    由于要填充的內容很多,并且目前只有在初始化環節才用到,因此這部分代碼可以先粗略看一下,在后續的章節還會詳細講到。

    填充好后,這時我們就可以用方法ID3D11Device::CreateTexture2D來創建2D紋理:

    HRESULT ID3D11Device::CreateTexture2D( const D3D11_TEXTURE2D_DESC *pDesc, // [In] 2D紋理描述信息const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用于初始化的資源ID3D11Texture2D **ppTexture2D); // [Out] 獲取到的2D紋理

    下面的代碼是關于深度/模板緩沖區創建的完整過程:

    D3D11_TEXTURE2D_DESC depthStencilDesc;depthStencilDesc.Width = mClientWidth; depthStencilDesc.Height = mClientHeight; depthStencilDesc.MipLevels = 1; depthStencilDesc.ArraySize = 1; depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;// 要使用 4X MSAA? if (mEnable4xMsaa) {depthStencilDesc.SampleDesc.Count = 4;depthStencilDesc.SampleDesc.Quality = m_4xMsaaQuality - 1; } else {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;HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, m_pDepthStencilBuffer.GetAddressOf()));

    創建深度/模板視圖

    有了深度/模板緩沖區后,就可以通過ID3D11Device::CreateDepthStencilView方法將創建好的2D紋理綁定到新建的深度/模板視圖:

    HRESULT ID3D11Device::CreateDepthStencilView( ID3D11Resource *pResource, // [In] 需要綁定的資源const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc, // [In] 深度緩沖區描述,這里忽略ID3D11DepthStencilView **ppDepthStencilView); // [Out] 獲取到的深度/模板視圖

    演示如下:

    HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(), nullptr, m_pDepthStencilView.GetAddressOf()));

    為渲染管線的輸出合并階段設置渲染目標

    ID3D11DeviceContext::OMSetRenderTargets方法要求同時提供渲染目標視圖和深度/模板視圖,不過這時我們都已經準備好了:

    void ID3D11DeviceContext::OMSetRenderTargets( UINT NumViews, // [In] 視圖數目ID3D11RenderTargetView *const *ppRenderTargetViews, // [In] 渲染目標視圖數組ID3D11DepthStencilView *pDepthStencilView) = 0; // [In] 深度/模板視圖

    因此這里同樣也是一句話的事情:

    m_pd3dImmediateContext->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), m_pDepthStencilView.Get());

    視口設置

    最終我們還需要決定將整個視圖輸出到窗口特定的范圍。我們需要使用D3D11_VIEWPORT來設置視口

    typedef struct D3D11_VIEWPORT {FLOAT TopLeftX; // 屏幕左上角起始位置XFLOAT TopLeftY; // 屏幕左上角起始位置YFLOAT Width; // 寬度FLOAT Height; // 高度FLOAT MinDepth; // 最小深度,必須為0.0fFLOAT MaxDepth; // 最大深度,必須為1.0f } D3D11_VIEWPORT;

    ID3D11DeviceContext::RSSetViewports方法將設置1個或多個視口:

    void ID3D11DeviceContext::RSSetViewports(UINT NumViewports, // 視口數目const D3D11_VIEWPORT *pViewports); // 視口數組

    將視圖輸出到整個屏幕需要按下面的方式進行填充:

    m_ScreenViewport.TopLeftX = 0; m_ScreenViewport.TopLeftY = 0; m_ScreenViewport.Width = static_cast<float>(mClientWidth); m_ScreenViewport.Height = static_cast<float>(mClientHeight); m_ScreenViewport.MinDepth = 0.0f; m_ScreenViewport.MaxDepth = 1.0f;m_pd3dImmediateContext->RSSetViewports(1, &m_ScreenViewport);

    完成了這六個步驟后,基本的初始化就完成了。但是,如果涉及到窗口大小變化的情況,那么前面提到的后備緩沖區、深度/模板緩沖區、視口都需要重新調整大小。

    D3DApp::OnResize方法

    已知深度模板緩沖區和視口都可以直接重新創建一份來進行替換。至于后備緩沖區,我們可以通過IDXGISwapChain::ResizeBuffers來重新調整后備緩沖區的大小:

    HRESULT IDXGISwapChain::ResizeBuffers(UINT BufferCount, // [In]緩沖區數目UINT Width, // [In]緩沖區寬度UINT Height, // [In]緩沖區高度DXGI_FORMAT NewFormat, // [In]DXGI格式UINT SwapChainFlags // [In]忽略 );

    下面的方法演示了在窗口大小發生改變后,以及初次調用時進行的操作:

    void D3DApp::OnResize() {assert(m_pd3dImmediateContext);assert(m_pd3dDevice);assert(m_pSwapChain);if (m_pd3dDevice1 != nullptr){assert(m_pd3dImmediateContext1);assert(m_pd3dDevice1);assert(m_pSwapChain1);}// 釋放交換鏈的相關資源m_pRenderTargetView.Reset();m_pDepthStencilView.Reset();m_pDepthStencilBuffer.Reset();// 重設交換鏈并且重新創建渲染目標視圖ComPtr<ID3D11Texture2D> backBuffer;HR(m_pSwapChain->ResizeBuffers(1, m_ClientWidth, m_ClientHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0));HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())));HR(m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()));backBuffer.Reset();D3D11_TEXTURE2D_DESC depthStencilDesc;depthStencilDesc.Width = m_ClientWidth;depthStencilDesc.Height = m_ClientHeight;depthStencilDesc.MipLevels = 1;depthStencilDesc.ArraySize = 1;depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;// 要使用 4X MSAA? --需要給交換鏈設置MASS參數if (m_Enable4xMsaa){depthStencilDesc.SampleDesc.Count = 4;depthStencilDesc.SampleDesc.Quality = m_4xMsaaQuality - 1;}else{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;// 創建深度緩沖區以及深度模板視圖HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, m_pDepthStencilBuffer.GetAddressOf()));HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(), nullptr, m_pDepthStencilView.GetAddressOf()));// 將渲染目標視圖和深度/模板緩沖區結合到管線m_pd3dImmediateContext->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), m_pDepthStencilView.Get());// 設置視口變換m_ScreenViewport.TopLeftX = 0;m_ScreenViewport.TopLeftY = 0;m_ScreenViewport.Width = static_cast<float>(m_ClientWidth);m_ScreenViewport.Height = static_cast<float>(m_ClientHeight);m_ScreenViewport.MinDepth = 0.0f;m_ScreenViewport.MaxDepth = 1.0f;m_pd3dImmediateContext->RSSetViewports(1, &m_ScreenViewport); }

    在后續的部分,該框架的代碼基本上不會有什么太大的變動。因此后續代碼的添加主要在GameApp類實現。如果現在對上面的一些過程不理解,也是正常的,可以在后續學習到視圖相關的知識后再來回看這一整個過程。

    GameApp類

    對于一個初始化應用程序來說,目前GameApp類的非常簡單:

    class GameApp : public D3DApp { public:GameApp(HINSTANCE hInstance);~GameApp();bool Init();void OnResize();void UpdateScene(float dt);void DrawScene(); };

    GameApp::DrawScene方法--每幀畫面的繪制

    ID3D11DeviceContext::ClearRenderTargetView方法--清空需要繪制的緩沖區

    在每一幀畫面繪制的操作中,我們需要清理一遍渲染目標視圖綁定的緩沖區

    void ID3D11DeviceContext::ClearRenderTargetView(ID3D11RenderTargetView *pRenderTargetView, // [In]渲染目標視圖const FLOAT ColorRGBA[4]); // [In]指定覆蓋顏色

    這里的顏色值范圍都是0.0f到1.0f

    比如我們要對后備緩沖區(R8G8B8A8)使用藍色進行清空,可以這樣寫:

    float blue[4] = {0.0f, 0.0f, 1.0f, 1.0f} m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), blue);

    ID3D11DeviceContext::ClearDepthStencilView方法--清空深度/模板緩沖區

    同樣在進行渲染之前,我們也要清理一遍深度/模板緩沖區

    void ID3D11DeviceContext::ClearDepthStencilView(ID3D11DepthStencilView *pDepthStencilView, // [In]深度/模板視圖UINT ClearFlags, // [In]D3D11_CLEAR_FLAG枚舉FLOAT Depth, // [In]深度UINT8 Stencil); // [In]模板初始值

    若要清空深度緩沖區,則需要指定D3D11_CLEAR_DEPTH,模板緩沖區則是D3D11_CLEAR_STENCIL。

    每一次清空我們需要將深度值設為1.0f,模板值設為0.0f。其中深度值1.0f表示距離最遠處:

    m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

    IDXGISwapChain::Present方法--前后臺緩沖區交換并呈現

    完成一切繪制操作后就可以調用該方法了

    HRESULT ID3D11DeviceContext::Present( UINT SyncInterval, // [In]通常為0UINT Flags); // [In]通常為0

    GameApp::DrawScene的實現如下:

    void GameApp::DrawScene() {assert(m_pd3dImmediateContext);assert(m_pSwapChain);static float blue[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; // RGBA = (0,0,255,255)m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), blue);m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);HR(m_pSwapChain->Present(0, 0)); }

    最終繪制的效果應該如下:

    程序退出后的清理

    因為之前我們用的是智能指針,所以D3DApp的析構函數十分簡單,只需要通過ID3D11DeviceContext::ClearState方法來恢復D3D設備上下文到默認狀態,卸下所有綁定的資源即可。剩下的事情就交給COM智能指針完成:

    D3DApp::~D3DApp() {// 恢復所有默認設定if (m_pd3dImmediateContext)m_pd3dImmediateContext->ClearState(); }

    練習題

    粗體字為自定義題目

  • 嘗試修改項目代碼,讓窗口內的顯示變紅。
  • 某些電腦可能有多于一個的顯示適配器(顯卡)。首先要通過CreateDXGIFactory創建IDXGIFactory,然后使用IDXGIFactory::EnumAdapters來枚舉顯示適配器。嘗試通過這種方式查看你的電腦有多少個顯示適配器(IDXGIAdapter),并察看它們的信息。
  • 一個顯示適配器可能關聯了多個輸出設備(IDXGIOutput),你可以使用IDXGIAdapter::EnumOutputs方法來枚舉出特定的輸出,嘗試觀察它們的信息。
  • 對于給定的像素格式,一個輸出設備可以支持許多種顯示模式(DXGI_MODE_DESC),通過它可以看到全屏寬度、高度、刷新率。嘗試使用IDXGIOutput::GetDisplayModeList方法觀察所有支持的模式(傳遞DXGI_FORMAT_R8G8B8A8_UNORM格式進去)。
  • 默認情況下的窗口程序是可以通過ALT+ENTER來進入/退出全屏的。此外,我們可以通過IDXGISwapChain來動態設置窗口全屏屬性,找到對應的方法并嘗試一下。
  • 現在嘗試指定顯示適配器來創建D3D設備。通過CreateDXGIFactory函數來創建IDXGIFactory,通常它會包含接口IDXGIFactory1,但有可能它也會包含接口IDXGIFactory2。在沒有創建D3D設備的情況下,這種方式就可以幫助我們了解是否可以創建出Direct3D 11.1的設備。為了能夠指定顯示適配器來創建D3D11設備,我們需要將D3D_DRIVER_TYPE強行設置為D3D_DRIVER_TYPE_UNKNOWN,否則在創建設備的時候會得到如下報錯信息:DX ERROR: D3D11CreateDevice: When creating a device from an existing adapter (i.e. pAdapter is non-NULL), DriverType must be D3D_DRIVER_TYPE_UNKNOWN. [ INITIALIZATION ERROR #3146141: ]。
  • DirectX11 With Windows SDK完整目錄

    Github項目源碼

    歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。

    posted on 2019-05-05 09:46 NET未來之路 閱讀(...) 評論(...) 編輯 收藏

    轉載于:https://www.cnblogs.com/lonelyxmas/p/10811308.html

    總結

    以上是生活随笔為你收集整理的DirectX11 With Windows SDK--01 DirectX11初始化的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。