DirectX9(D3D9)遊戲開發:高光時燒錄製和共享紋理的踩坑

赵青青發表於2024-08-04

共享紋理

老遊戲使用directx9無法直接與cc高光sdk(d3d11)對接,但是d3d9ex有共享紋理,我們透過共享紋理把遊戲畫面共享給cc錄製,記錄一些踩坑的筆記。

共享紋理示例:

// 初始化Direct3D
void initD3D9(HWND hWnd)
{
    hr = d3d9exdev->GetRenderTarget(0, &g_d3d9RenderSurface);
    D3DSURFACE_DESC desc;
    g_d3d9RenderSurface->GetDesc(&desc);
    //關於格式說明:影像的格式必須與desc.format的格式一致,否則共享紋理的畫面是黑色的,並且pool要使用default
    hr = d3d9exdev->CreateOffscreenPlainSurface(desc.Width, desc.Height, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT,
        &g_d3d9SharedSurface, &g_d3d9SharedHandle);
    if (FAILED(hr))
    {
        OutputDebugStringA("CreateOffscreenPlainSurface failed\n");
    }
}
// 渲染一幀
void RenderD3D9(void)
{
    hr = d3d9exdev->StretchRect(g_d3d9RenderSurface, NULL, g_d3d9SharedSurface, NULL, D3DTEXF_NONE);
    if (FAILED(hr))
    {
        OutputDebugStringA("GetRenderTargetData failed\n");
    }
}
//g_d3d9SharedHandle就是共享紋理的控制代碼,傳給sdk進行錄製
long long sharedHandleAddress = reinterpret_cast<long long>(g_d3d9SharedHandle);

建立d3d9ex

只有d3d9ex才能共享紋理,d3d9無法共享紋理

LPDIRECT3D9EX d3d9ex = nullptr;
Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d9ex);

VS中正常,編譯出的exe出錯

在與cc聯調的過程中碰到一個問題:從vs中啟動共享紋理正常,而打包出來的exe共享紋理是黑色的。

最後透過各種排查,各種換方法,才定位到問題,我們遊戲載入的d3d9.dll非微軟原版的!

把我們遊戲的d3d9.dll,發給CC測試發現:遊戲程序CreateOffscreenPlainSurface建立的紋理在錄製sdk無法開啟

image-20240701125253381

那為什麼從vs裡啟動就是正常的呢?

在vs中啟動從模組視窗中可以看到載入的是系統的dll

image-20240701130544013

編譯出來的exe相同dll載入的位置已經變了!

image-20240702101140384

嘗試刪掉或替換遊戲目錄下的d3d9.dll,測試結果如下表:

d3d9.dll 獨立程序 同程序
替換d3d9.dll為微軟原版
刪除d3d9.dll

windows dll載入的順序

打包應用系統按以下順序搜尋:

  1. DLL 重定向。
  2. API 集。
  3. 桌面應用僅 (UWP 應用) 。 SxS 清單重定向。
  4. Loaded-module 列表。
  5. 已知 DLL。
  6. 程序的包依賴項關係圖。 這是應用程式的包,以及應用程式包清單的 節中指定的任何依賴項。 依賴項按它們在清單中的出現順序進行搜尋。
  7. 呼叫程序從載入的資料夾 (可執行檔案的資料夾) 。
  8. 系統資料夾 (%SystemRoot%\system32) 。

附官方文件:Dynamic-link library search order - Win32 apps | Microsoft Learn

為什麼要替換微軟的d3d9.dll?

我們有部分集顯玩家的地表會出問題,所以遊戲內有個設定使用vulkan,勾選後會使用dxvk,不過這個dll也被命名為d3d9.dll,但是在內服為了截幀所以預設替換了遊戲目錄下的d3d9.dll

doitsujin/dxvk: Vulkan-based implementation of D3D9, D3D10 and D3D11 for Linux / Wine (github.com)

關閉編譯最佳化導致錄影閃屏

在我本地打包出來的遊戲錄影偶爾閃爍,而QA跑打包機打出來的遊戲則錄影影片特別閃,遊戲內畫面正常只有錄影出現閃屏。

經過對比發現,我本地有兩個工程的vs c++編譯最佳化關閉了,也就是:專案 - 屬性 - C/C++ - 最佳化:【最大最佳化O2】改成【已禁用】

image-20240703111252668

於是嘗試開啟我本地工程的c++最佳化,果然影片也會閃屏,那為什麼這個最佳化會導致閃屏呢?

因為關閉最佳化後遊戲執行速度變慢,遊戲變慢就變成同步了從而掩蓋了共享紋理的問題。而遊戲是多執行緒的,對紋理的訪問是非同步的就會閃爍。

最終解決辦法

Moo::rc().device();獲取D3D裝置,而不是遊戲初始化透過CreateDeviceEx建立的pD3D9Ex,因為引擎裡對D3D9Ex進行了封裝,預設就開啟了D3D9Ex,並且 Moo::rc().device()處理了多執行緒的同步問題

HRESULT DXUTCreate3DEnvironment9( IDirect3DDevice9* pd3dDeviceFromApp )
{
hr = pD3D9Ex->CreateDeviceEx( pNewDeviceSettings->d3d9.AdapterOrdinal, pNewDeviceSettings->d3d9.DeviceType, \
            DXUTGetHWNDFocus(), pNewDeviceSettings->d3d9.BehaviorFlags, \
            &pNewDeviceSettings->d3d9.pp, NULL, &pd3dDevice9Ex);
}

使用process explorer檢視程式的控制代碼和載入的dll

選單點選 View - Lower Pane View - 勾選DLLS和Handles ,並且勾選 View - Show Lower Pane,然後選中某個程序後,在底部就會出現dll tab頁,裡面顯示了當前程序載入了那些dll

image-20240702142239030

相關文章