DirectX11--ComPtr智能指針

綜述

DirectX11 With Windows SDK完整目錄

歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

IUnknown接口類

DirectX11的API是由一系列的COM組件來管理的,這些前綴帶I的接口類最終都繼承自IUnknown接口類。IUnknown的三個方法如下:

方法 描述
IUnknown::AddRef 內部引用計數加1。在每次複製了一個這樣的指針後,應當調用該方法以保證計數準確性
IUnknown::QueryInterface 查詢該實例是否實現了另一個接口,如果存在則返回該接口的指針,並且對該接口的引用計數加1
IUnknown::Release 內部引用數減1。只有當內部引用數到達0時纔會真正釋放

在實際的使用情況來看,通常我們幾乎不會使用第一個方法。而用的最多的就是第三個方法了,每次用完該實例後,我們必須要使用類似下面的宏來釋放:

#define ReleaseCOM(x) { if(x){ x->Release(); x = nullptr; } }

而且如果出現了忘記釋放某個接口指針的情況話,內存泄漏的提醒就有可能夠你去調試一整天了。

ComPtr智能指針

爲了解決上述問題,從繁雜的人工釋放中解脫,在本教程中大量使用了ComPtr智能指針。而且在龍書12的教程源碼中也用到了該智能指針。該智能指針可以幫助我們來管理這些COM組件實現的接口實例,而無需過多擔心內存的泄漏。該智能指針的大小和一般的指針大小是一致的,沒有額外的內存空間佔用。所以本教程可以不需要用到接口類ID3D11Debug來協助檢查內存泄漏。

使用該智能指針需要包含頭文件wrl/client.h,並且智能指針類模板ComPtr位於名稱空間Microsoft::WRL內。

首先有五個比較常用的方法需要了解一下:

方法 描述
ComPtr::Get 該方法返回T*,並且不會觸發引用計數加1,常用在COM組件接口的函數輸入
ComPtr::GetAddressOf 該方法返回T**,常用在COM組件接口的函數輸出
ComPtr::Reset 該方法對裏面的實例調用Release方法,並將指針置爲nullptr
ComPtr::ReleaseAndGetAddressOf 該方法相當於先調用Reset方法,再調用GetAddressOf方法獲取T**,常用在COM組件接口的函數輸出,適用於實例可能會被反覆構造的情況下
ComPtr::As 一個模板函數,可以替代IUnknown::QueryInterface的調用,需要傳遞一個ComPtr實例的地址

然後是一些運算符重載的方法:

運算符 描述
& 相當於調用了ComPtr<T>::ReleaseAndGetAddressOf方法,不推薦使用
-> 和裸指針的行爲一致
= 不要將裸指針指向的實例賦給它,若傳遞的是ComPtr的不同實例則發生交換
==和!= 可以和nullptr,或者另一個ComPtr實例進行比較

注意:大致在比10.0.16299.0更早的Windows SDK版本中,ComPtr使用了一個RemoveIUnknownBase類模板將IUnknown的三個接口都設爲了private,以防止用戶直接操作這些方法,這也就使得ComPtr無法直接使用COM組件的QueryInterface方法。因此,使用ComPtr<T>::As是一種合適的選擇。

個人建議,在使用該智能指針後就應該要避免使用IUnknown提供的三個接口方法來進行操作。

雖然替換成ComPtr後代碼量變長了,但是帶來的收益肯定比你自己花費大量時間在檢查釋放內存上強的多。

下面的D3DApp將所有COM組件指針都換成了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 mhAppInst;        // 應用實例句柄
    HWND      mhMainWnd;        // 主窗口句柄
    bool      mAppPaused;       // 應用是否暫停
    bool      mMinimized;       // 應用是否最小化
    bool      mMaximized;       // 應用是否最大化
    bool      mResizing;        // 窗口大小是否變化
    bool      mEnable4xMsaa;    // 是否開啓4倍多重採樣
    UINT      m4xMsaaQuality;   // MSAA支持的質量等級


    GameTimer mTimer;           // 計時器

    // 使用模板別名(C++11)簡化類型名
    template <class T>
    using ComPtr = Microsoft::WRL::ComPtr<T>;
    // DX11
    ComPtr<ID3D11Device> md3dDevice;                    // D3D11設備
    ComPtr<ID3D11DeviceContext> md3dImmediateContext;   // D3D11設備上下文
    ComPtr<IDXGISwapChain> mSwapChain;                  // D3D11交換鏈
    // DX11.1
    ComPtr<ID3D11Device1> md3dDevice1;                  // D3D11.1設備
    ComPtr<ID3D11DeviceContext1> md3dImmediateContext1; // D3D11.1設備上下文
    ComPtr<IDXGISwapChain1> mSwapChain1;                // D3D11.1交換鏈
    // 常用資源
    ComPtr<ID3D11Texture2D> mDepthStencilBuffer;        // 深度模板緩衝區
    ComPtr<ID3D11RenderTargetView> mRenderTargetView;   // 渲染目標視圖
    ComPtr<ID3D11DepthStencilView> mDepthStencilView;   // 深度模板視圖
    D3D11_VIEWPORT mScreenViewport;                     // 視口

    // 派生類應該在構造函數設置好這些自定義的初始參數
    std::wstring mMainWndCaption;                       // 主窗口標題
    int mClientWidth;                                   // 視口寬度
    int mClientHeight;                                  // 視口高度
};

下面的代碼演示了ComPtr的大多數函數用法:

bool D3DApp::InitDirect3D()
{
    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, 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;
    }

    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, &m4xMsaaQuality);
    assert(m4xMsaaQuality > 0);




    ComPtr<IDXGIDevice> dxgiDevice = nullptr;
    ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
    ComPtr<IDXGIFactory1> dxgiFactory1 = nullptr;   // DX11.0(包含DXGI1.1)的接口類
    ComPtr<IDXGIFactory2> dxgiFactory2 = nullptr;   // DX11.1(包含DXGI1.2)特有的接口類

    // 爲了正確創建 DXGI交換鏈,首先我們需要獲取創建 D3D設備 的 DXGI工廠,否則會引發報錯:
    // "IDXGIFactory::CreateSwapChain: This function is being called with a device from a different IDXGIFactory."
    // 從屬關係爲 DXGI工廠-> DXGI適配器 -> DXGI設備 {D3D11設備}
    HR(md3dDevice.As(&dxgiDevice));
    HR(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()));
    HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf())));

    // 查看該對象是否包含IDXGIFactory2接口
    hr = dxgiFactory1.As(&dxgiFactory2);
    // 如果包含,則說明支持DX11.1
    if (dxgiFactory2 != nullptr)
    {
        HR(md3dDevice.As(&md3dDevice1));
        HR(md3dImmediateContext.As(&md3dImmediateContext1));
        // 填充各種結構體用以描述交換鏈
        DXGI_SWAP_CHAIN_DESC1 sd;
        ZeroMemory(&sd, sizeof(sd));
        sd.Width = mClientWidth;
        sd.Height = mClientHeight;
        sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        // 是否開啓4倍多重採樣?
        if (mEnable4xMsaa)
        {
            sd.SampleDesc.Count = 4;
            sd.SampleDesc.Quality = m4xMsaaQuality - 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(md3dDevice.Get(), mhMainWnd, &sd, &fd, nullptr, mSwapChain1.GetAddressOf()));
        HR(mSwapChain1.As(&mSwapChain));
    }
    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;
        // 是否開啓4倍多重採樣?
        if (mEnable4xMsaa)
        {
            sd.SampleDesc.Count = 4;
            sd.SampleDesc.Quality = m4xMsaaQuality - 1;
        }
        else
        {
            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()));
    }

    

    // 可以禁止alt+enter全屏
    dxgiFactory1->MakeWindowAssociation(mhMainWnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);


    // 每當窗口被重新調整大小的時候,都需要調用這個OnResize函數。現在調用
    // 以避免代碼重複
    OnResize();

    return true;
}

DirectX11 With Windows SDK完整目錄

歡迎加入QQ羣: 727623616 可以一起探討DX11,以及有什麼問題也可以在這裏彙報。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章