如何通过Vulkan获取游戏运行时的Overlay表面以实现帧率计数器?
如何在Vulkan游戏中获取Overlay表面实现帧率计数器类工具
核心思路
要实现这类Overlay,本质是注入到目标游戏的Vulkan上下文,获取其设备、队列等核心资源,然后通过Hook游戏的渲染关键函数,插入自己的Overlay绘制逻辑,最终实现画面叠加效果。
具体步骤
1. 进程注入与Vulkan函数Hook
- 先通过DLL注入(比如调用
CreateRemoteThread)将工具代码注入目标游戏进程。 - Hook Vulkan核心函数,重点关注三个关键节点:
vkCreateInstance:捕获游戏创建的Vulkan实例句柄,可添加自定义调试回调辅助后续排查问题。vkCreateDevice:获取游戏的VkDevice句柄,以及用于帧呈现的队列家族和VkQueue对象。vkQueuePresentKHR:这是游戏帧提交呈现的终点,在这里插入Overlay绘制逻辑,同时可获取帧时间戳计算帧率。
2. 复用或创建Overlay的VkSurfaceKHR
- 更高效的方式是复用游戏已有的VkSurfaceKHR:通过Hook
vkQueuePresentKHR直接拿到游戏要呈现的Surface,无需额外创建新表面,避免窗口层级冲突。 - 若需独立Surface,可基于游戏窗口句柄创建(以Windows平台为例):
VkWin32SurfaceCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; createInfo.hwnd = gameWindowHandle; // 已获取的游戏窗口HWND createInfo.hinstance = GetModuleHandle(nullptr); VkSurfaceKHR overlaySurface; vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &overlaySurface);
3. 构建Overlay渲染资源
- 基于游戏的VkDevice,创建匹配游戏参数的Swapchain:
VkSwapchainCreateInfoKHR swapchainCreateInfo{}; swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchainCreateInfo.surface = gameSurface; // 复用的游戏Surface swapchainCreateInfo.imageFormat = gameSwapchainFormat; // 匹配游戏Swapchain格式 swapchainCreateInfo.imageExtent = gameSwapchainExtent; // 匹配游戏分辨率 VkSwapchainKHR overlaySwapchain; vkCreateSwapchainKHR(device, &swapchainCreateInfo, nullptr, &overlaySwapchain); - 创建自定义渲染通道、帧缓冲与管线:渲染通道需设置
VK_ATTACHMENT_LOAD_OP_LOAD,保留游戏原画面后再叠加UI元素。
4. 插入Overlay绘制逻辑
- 在Hook的
vkQueuePresentKHR函数中,按以下流程执行:// 获取游戏要呈现的图像索引 uint32_t imageIndex; vkAcquireNextImageKHR(device, gameSwapchain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); // 记录Overlay渲染命令 vkBeginCommandBuffer(cmdBuffer, &cmdBufferBeginInfo); vkCmdBeginRenderPass(cmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); // 用UI库绘制帧率文本(比如ImGui) ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmdBuffer); vkCmdEndRenderPass(cmdBuffer); vkEndCommandBuffer(cmdBuffer); // 提交Overlay命令到队列 VkSubmitInfo submitInfo{}; submitInfo.waitSemaphores = &imageAvailableSemaphore; submitInfo.commandBuffers = &cmdBuffer; vkQueueSubmit(presentQueue, 1, &submitInfo, renderFinishedSemaphore); // 调用原函数完成帧呈现 VkPresentInfoKHR presentInfo{}; presentInfo.waitSemaphores = &renderFinishedSemaphore; presentInfo.swapchains = &gameSwapchain; presentInfo.imageIndices = &imageIndex; originalVkQueuePresentKHR(presentQueue, &presentInfo); - 同步信号必须与游戏的同步逻辑兼容,避免帧卡顿或渲染异常。
5. 帧率计算实现
- 在
vkQueuePresentKHR的Hook函数中,通过时间戳计算帧率:LARGE_INTEGER currentTime, frequency; QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(¤tTime); float deltaTime = (currentTime.QuadPart - lastTime.QuadPart) / (float)frequency.QuadPart; float fps = 1.0f / deltaTime; lastTime = currentTime; - 将计算出的fps转为文本,通过UI库绘制到Overlay上即可。
关键注意事项
- 权限与反作弊:部分带反作弊的游戏会阻止外部进程注入,需确保工具权限合规。
- 版本兼容:不同游戏可能使用不同版本的Vulkan,Hook逻辑需适配对应函数签名。
- 资源清理:游戏退出时,需正确销毁所有自定义Vulkan资源(Swapchain、帧缓冲等),避免内存泄漏。
内容的提问来源于stack exchange,提问作者David Carpenter
相关产品推荐
相关产品推荐

