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

6.7 KiB
Raw Blame History

RHIFramebuffer

命名空间: XCEngine::RHI

类型: class (abstract)

头文件: XCEngine/RHI/RHIFramebuffer.h

描述: 抽象 framebuffer 对象,负责把 render pass 描述与一组具体 attachment view 绑定起来,形成一次可用于渲染输出的目标集合。

角色概述

RHIFramebuffer 不是纹理资源本体,也不是 render pass 本体。它更像是两者之间的一层“装配结果”:

  • RHIRenderPass 负责描述这次 pass 需要怎样的颜色/深度附件语义
  • RHIResourceView 负责把具体纹理解释为 render target / depth stencil 视图
  • RHIFramebuffer 则把它们组合成一份真正可绑定到 GPU 的输出目标配置

这种拆分与商业引擎、现代图形 API 的思路一致。它的价值在于:

  • 同一个 render pass 可以反复搭配不同纹理视图
  • swap chain resize 或离屏目标切换时,只需要重建 framebuffer而不必重写整套 pass 语义
  • 后端可以各自用最合适的 native 方式存储这组 attachment 绑定关系

输入语义

当前抽象接口上的 Initialize() 需要这些核心输入:

  • renderPass
  • width
  • height
  • colorAttachmentCount
  • colorAttachments
  • depthStencilAttachment

这几个参数共同表达的是:

  • 这份 framebuffer 应当匹配哪种 pass 附件布局
  • 它的目标尺寸是多少
  • 它绑定了哪些颜色视图
  • 是否还绑定了深度/模板视图

当前后端差异

D3D12

当前 D3D12 实现并不会创建真正的 native framebuffer 对象。

它做的事情非常轻量:

  • 保存 D3D12RenderPass*
  • 保存宽高
  • 从每个 RHIResourceView 中提取 CPU descriptor handle
  • 把颜色 RTV handle 和深度 DSV handle 缓存起来

有几个后果非常重要:

  • GetNativeHandle 当前返回 nullptr
  • IsValid 的判断条件只是 m_renderPass != nullptr
  • 当前实现不会主动校验 attachment 数量、尺寸、格式是否与 render pass 严格匹配
  • 即使宽高是 0,对象也可能创建成功

换句话说D3D12 路径里的 RHIFramebuffer 更像“供 command list 使用的一组 RTV/DSV 句柄快照”,而不是一个强校验、强约束的独立 native 对象。

OpenGL

OpenGL 路径会创建真正的 FBO并把 resource view 中记录的附件信息挂到 FBO 上,然后调用 glCheckFramebufferStatus() 做完整性检查。

这条路径的特点是:

  • GetNativeHandle 返回的是 OpenGL framebuffer 对象名
  • IsValid 反映的是 FBO 是否已成功创建
  • 会根据 resource view 的附件目标、mip、layer 去做实际挂接
  • 当前实现最多处理前 16 个颜色附件

还需要特别注意一个实现事实:

当前 OpenGLFramebuffer::Initialize(RHIRenderPass*, ...) 里,renderPass 参数实际上没有被用来做匹配校验。也就是说OpenGL 的“有效性”更多来自 FBO 自身的 GL 完整性检查,而不是来自 render pass 与 framebuffer 的抽象层一致性校验。

Vulkan

Vulkan 是当前校验最严格的一条路径。

在真正创建 VkFramebuffer 前,当前实现会检查:

  • VkDevice 是否有效
  • renderPass 是否非空且 VkRenderPass 有效
  • width / height 是否大于 0
  • colorAttachmentCount > 0colorAttachments 不能为 nullptr
  • colorAttachmentCount 必须与 render pass 的颜色附件数量一致
  • 深度附件的有无必须与 render pass 是否声明 depth/stencil 一致
  • 每个传入的 VulkanResourceView 都必须持有有效 VkImageView

只有全部满足时才会创建 VkFramebuffer

因此当前最保守、最可移植的工程习惯是:按 Vulkan 的严格要求去构造 framebuffer而不要依赖 D3D12 / OpenGL 某些较宽松的容忍行为。

测试体现出的真实语义

tests/RHI/unit/test_framebuffer.cpp 目前覆盖了这些场景:

  • 单颜色附件创建
  • 宽高查询
  • IsValid() 基本语义
  • 显式 Shutdown()
  • 颜色 + 深度模板附件
  • 多颜色附件
  • 非法尺寸
  • 颜色附件指针为 nullptr

测试里一个很关键的信号是:对于非法尺寸和空颜色视图,测试并没有要求“一定创建失败”,而是采用“如果创建成功则检查其返回状态”的写法。

这恰恰说明当前抽象层对非法输入没有统一拒绝语义,后端差异必须在文档里写明,而不能假装它已经完全抹平。

生命周期与所有权

RHIFramebuffer 通常通过 RHIDevice::CreateFramebuffer 创建,并以裸指针返回。

它只拥有自己内部的 native / backend 状态,不拥有:

  • RHIRenderPass
  • RHIResourceView
  • 底层 RHITexture

销毁 framebuffer 不等于销毁附件纹理或其视图。当前最稳妥的释放方式仍然是:

  1. Shutdown
  2. delete

线程语义

当前接口没有声明线程安全保证。从源码和图形 API 约束出发,更安全的理解是:

  • 创建、销毁和绑定相关操作应放在渲染线程或 RHI 线程
  • 不要跨线程同时修改同一个 framebuffer
  • 不要假设 resize、swap chain 重建、attachment 替换与旧 framebuffer 并发安全

设计理解

把 framebuffer 作为独立对象,而不是把 attachment 直接塞进 command list 调用里,是现代渲染架构里很常见的一种收敛方式。

它的好处在于:

  • 附件绑定关系可以被缓存和复用
  • render pass 语义与具体图像资源可以分离
  • resize、离屏渲染、GBuffer、多目标输出这类场景更容易组织

如果从 Unity 风格去理解,它更接近“引擎内部组织 render target set 的对象”,而不是用户层直接面对的某个高层渲染资产。

当前实现限制

  • 抽象接口没有提供“创建后反查 attachment 列表”的通用 API调用方需要自己记住输入
  • D3D12 路径没有真正的 native framebufferGetNativeHandle 不能做可移植假设
  • OpenGL 路径的 renderPass 当前主要起抽象层占位作用,不承担严格匹配校验
  • Vulkan 路径最严格,意味着上层若想写出稳定的跨后端代码,最好按 Vulkan 约束准备 attachment

公共方法

相关文档