一、为什么从Vulkan转向D3D12
中期项目基于Vulkan,通过vulkan-1.dllProxy DLL注入,拦截vkGetDeviceProcAddr,在vkQueuePresentKHR前插入图像处理。经过评估,最终项目转向D3D12方案,原因:
| 维度 | Vulkan | D3D12 |
|---|---|---|
| 游戏覆盖 | 部分 | 更广泛 |
| 超分SDK | FSR2较好,DLSS有限 | DLSS/FSR/XeSS全支持 |
| 开源参考 | 较少 | upscalerBridge完整参考 |
核心洞察:虽然API不同,但注入思想相同——在Present前插入自定义GPU处理。区别在于实现手段。
二、Vulkan方案回顾(中期)
Vulkan通过拦截vkGetDeviceProcAddr控制所有设备级函数:
cpp
// vk_dispatch.cpp(真实代码) PFN_vkVoidFunction VKAPI_CALL VkDispatch::WrappedGetDeviceProcAddr(VkDevice device, const char* pName) { if (strcmp(pName, "vkQueuePresentKHR") == 0) return (PFN_vkVoidFunction)&VkHooks::vkQueuePresentKHR; if (strcmp(pName, "vkCreateSwapchainKHR") == 0) return (PFN_vkVoidFunction)&VkHooks::vkCreateSwapchainKHR; return g_vkGetDeviceProcAddr(device, pName); }注入点在vkQueuePresentKHR:解析VkPresentInfoKHR获取swapchain/imageIndex,录制CommandBuffer(清屏或Blit),通过Semaphore串联同步后调用真实Present。
三、D3D12核心实现
3.1 多马甲注入
与Vulkan仅伪装vulkan-1.dll不同,D3D12支持多个系统DLL伪装:
cpp
// dllmain.cpp CheckWorkingMode()(真实代码) if (lCaseFilename == "dxgi.dll") { originalModule = LoadSystemDLL(L"dxgi.dll"); DxgiProxy::Init(originalModule); State::Instance().workingMode = WorkingMode::Dxgi; } if (lCaseFilename == "winmm.dll") { /* 类似 */ } if (lCaseFilename == "version.dll") { /* 类似 */ }3.2 LoadLibrary重定向
cpp
// LibraryLoad_Hooks.cpp(真实代码) HMODULE LibraryLoadHooks::LoadLibraryCheckW(std::wstring libName, ...) { if (CheckDllNameW(&libName, &nvngxNamesW)) { LOG_INFO("nvngx call, returning this dll!"); return dllModule; // 游戏以为加载了nvngx.dll,实际是我们 } // 其余DLL透传 return nullptr; }3.3 DXGI Factory Hook
cpp
// Dxgi_Hooks.cpp(真实代码) VALIDATE_HOOK(hkCreateDXGIFactory2, DxgiProxy::PFN_CreateDxgiFactory2) inline static HRESULT hkCreateDXGIFactory2(UINT Flags, REFIID riid, IDXGIFactory2** ppFactory) { HRESULT result = o_CreateDXGIFactory2(Flags, riid, ppFactory); if (result == S_OK) { // 包装Factory,拦截所有SwapChain创建 *ppFactory = (IDXGIFactory2*)(new WrappedIDXGIFactory7(*ppFactory)); } return result; }3.4 Present注入
cpp
// wrapped_swapchain.cpp(真实代码) HRESULT WrappedIDXGISwapChain4::Present(UINT SyncInterval, UINT Flags) { if ((Flags & DXGI_PRESENT_TEST) == 0) { // LocalPresent:ImGui覆盖层 + 帧生成 + 原始Present return LocalPresent(_real, SyncInterval, Flags, ...); } return _real->Present(SyncInterval, Flags); }3.5 状态恢复(关键难点)
D3D12注入最复杂的部分:插入GPU命令后必须恢复游戏原始状态,否则闪退/花屏。
cpp
// D3D12_Hooks.cpp(真实代码) void D3D12Hooks::RestoreRoot(ID3D12GraphicsCommandList* cmdList) { // 恢复描述符堆、管线状态、根签名及所有根参数 RestoreDescriptorHeaps(cmdList); RestorePipelineState(cmdList); RestoreComputeRootState(cmdList); RestoreGraphicsRootState(cmdList); }四、与Vulkan方案对比
| 维度 | Vulkan | D3D12 |
|---|---|---|
| 拦截目标 | vkGetDeviceProcAddr | CreateDXGIFactory→Present |
| 资源跟踪 | VkSwapchainKHR查表 | IDXGISwapChain包装 |
| 同步机制 | VkSemaphore | D3D12 Fence |
| 状态管理 | 自动 | 手动追踪+恢复 |
五、验证结果
text
[Loader] Real dxgi.dll loaded [Hook] CreateDXGIFactory2 intercepted [Hook] WrappedIDXGISwapChain4 created [Present] ImGui overlay rendered, frame=120
程序正常运行,ImGui覆盖层显示,证明注入成功。
下一篇:SwapChain包装与Present注入详解