DirectX9(D3D9)游戏开发:高光时刻录制和共享纹理的踩坑
共享纹理
老游戏使用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无法打开
那为什么从vs里启动就是正常的呢?
在vs中启动从模块窗口中可以看到加载的是系统的dll
编译出来的exe相同dll加载的位置已经变了!
尝试删掉或替换游戏目录下的d3d9.dll,测试结果如下表:
d3d9.dll | 独立进程 | 同进程 |
---|---|---|
替换d3d9.dll为微软原版 | √ | √ |
删除d3d9.dll | √ | √ |
windows dll加载的顺序
打包应用系统按以下顺序搜索:
- DLL 重定向。
- API 集。
- 桌面应用仅 (UWP 应用) 。 SxS 清单重定向。
- Loaded-module 列表。
- 已知 DLL。
- 进程的包依赖项关系图。 这是应用程序的包,以及应用程序包清单的 节
中指定的任何依赖项 。 依赖项按它们在清单中的出现顺序进行搜索。 - 调用进程从加载的文件夹 (可执行文件的文件夹) 。
- 系统文件夹 (%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】改成【已禁用】
于是尝试打开我本地工程的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