Files
XCEngine/docs/api/XCEngine/RHI/RHIScreenshot/RHIScreenshot.md
2026-03-29 01:36:53 +08:00

5.7 KiB
Raw Blame History

RHIScreenshot

命名空间: XCEngine::RHI

类型: class (abstract)

头文件: XCEngine/RHI/RHIScreenshot.h

描述: 截图辅助对象,用于把当前 swap chain back buffer 读回到 CPU 并保存为文件,主要面向调试、测试和工具场景。

角色概述

RHIScreenshot 不是核心渲染管线对象,而是一条诊断与工具链路径。

它解决的问题很直接:

  • 当前画面到底渲染出了什么
  • swap chain 在 present 前后是否正确
  • 集成测试能否把输出结果落盘

从定位上看,它更接近“引擎内置的轻量截图抓取辅助”,而不是面向最终玩家的完整截图系统。当前实现明显偏 debug / test 风格,而不是面向高性能运行时捕获。

创建方式

当前统一入口是:

RHIScreenshot::Create(RHIType) 会按后端返回不同实现:

  • D3D12 当前始终可创建
  • OpenGL 取决于 XCENGINE_SUPPORT_OPENGL
  • Vulkan 取决于 XCENGINE_SUPPORT_VULKAN
  • 其他未实现后端返回 nullptr

这意味着截图能力和 RHI 工厂一样,也受编译开关与后端建设状态影响。

当前主工作流

对上层来说,最典型的使用顺序是:

  1. 拿到有效的 RHIDevice
  2. 拿到有效的 RHISwapChain
  3. 调用 Capture
  4. 用完后 Shutdowndelete

tests/RHI/unit/test_screenshot.cpp 也主要围绕这条路径进行验证:

  • 可以创建截图对象
  • 可以对当前 swap chain 进行基本截图
  • Present(0, 0) 之后依然可以截图

当前输出行为

这是最需要提前告诉使用者的实现事实。

当前三套后端的截图实现都会把数据写成二进制 PPM也就是 P6 格式:

  • 文件头写入 P6
  • 紧接着写宽高与 255
  • 再写 RGB 像素数据

这意味着:

  • 即使你把文件名写成 .png.jpg 或其他扩展名,当前写出的内容依然是 PPM 数据
  • 当前抽象层没有提供输出格式选择、压缩、alpha 保留或 HDR 导出能力

这是同步、阻塞的调试路径

当前各后端实现都会显式等待 GPU / 驱动完成读回:

  • OpenGL 调用 glFinish()
  • D3D12 提交临时命令列表并等待 fence
  • Vulkan 提交复制命令并 vkQueueWaitIdle()

所以 RHIScreenshot 现在应当被视为:

  • 调试工具路径
  • 自动化测试辅助路径
  • Editor / 开发期排障路径

而不是适合每帧调用的实时功能。

后端差异

D3D12

当前 D3D12 会:

  • 创建 readback buffer
  • 临时创建 command allocator / command list
  • 把 back buffer 从 render target 转成 copy source
  • 执行 CopyTextureRegion
  • 再切回 render target
  • 用 fence 等待 GPU 完成
  • Map readback buffer 并写出 PPM

这条路径的关键现实约束是:

  • 当前实现假定被捕获资源能从 D3D12_RESOURCE_STATE_RENDER_TARGET 转到 copy source 再切回去
  • 它更偏向当前 swap chain back buffer 这种调试用途
  • 它是一次完整的同步读回流程,代价较高

OpenGL

OpenGL 路径会:

  • 显式 MakeContextCurrent()
  • glFinish()
  • 根据 swap chain 当前 back buffer 状态,选择直接从 GL_BACK 读,或者临时创建 read FBO 从 back buffer texture 读
  • 调用 glReadPixels()
  • 逐行倒序写出文件

“逐行倒序写出”这一点很重要,它说明 OpenGL 路径当前会主动做一次垂直翻转,以适配 OpenGL 常见的左下角原点读取习惯。

Vulkan

Vulkan 路径会:

  • 创建 staging buffer 与 host visible 内存
  • 创建临时 command pool / command buffer
  • 把 swap chain image 从 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 转成 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
  • 执行 vkCmdCopyImageToBuffer
  • 再切回 present layout
  • 提交命令并等待 graphics queue idle
  • Map staging 内存并写出 PPM

当前 Vulkan 写文件时是按内存中的行顺序直接写出,没有像 OpenGL 那样做显式垂直翻转。

因此当前跨后端截图结果在“图像朝向”上不应被假定完全一致。OpenGL 路径显式翻转,而 D3D12 / Vulkan 路径当前没有这一层统一处理。

与商业引擎工作流的关系

商业级引擎里通常会有多层截图路径:

  • 轻量级 back buffer dump
  • 离屏 render target 抓取
  • 异步 readback
  • 带格式转换、压缩、UI 叠加或平台分享的最终用户截图

当前 RHIScreenshot 只覆盖了其中最基础的一层,也就是“把当前 swap chain back buffer 读回并存盘”。这对于底层 bring-up 和图形测试已经很有价值,但还远不是完整截图系统。

生命周期

当前三套后端的 Shutdown 基本都是 no-op但仍然建议统一按接口风格调用

  1. Shutdown
  2. delete

这样后续即使截图对象持有更多缓存资源,也不需要改调用习惯。

线程语义

当前接口没有声明线程安全保证。从源码行为看,更稳妥的工程规则是:

  • 在渲染线程或 RHI 线程使用
  • 调用时确保 device / swap chain 处于有效状态
  • 不要并发对同一个 swap chain 做截图和重建

当前实现限制

  • 当前抽象接口只能抓 swap chain不支持直接抓任意 RHITexture
  • 只有同步读回,没有异步 capture 队列
  • 只有 PPM 输出,没有 PNG/JPG/HDR 等格式支持
  • 没有颜色空间、gamma、alpha、UI 合成等高级控制
  • 输出目录若不存在,后端会直接失败,不会自动创建目录

公共方法

相关文档