6.7 KiB
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() 需要这些核心输入:
renderPasswidthheightcolorAttachmentCountcolorAttachmentsdepthStencilAttachment
这几个参数共同表达的是:
- 这份 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是否大于 0colorAttachmentCount > 0时colorAttachments不能为nullptrcolorAttachmentCount必须与 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 状态,不拥有:
RHIRenderPassRHIResourceView- 底层
RHITexture
销毁 framebuffer 不等于销毁附件纹理或其视图。当前最稳妥的释放方式仍然是:
- Shutdown
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 framebuffer,对 GetNativeHandle 不能做可移植假设
- OpenGL 路径的
renderPass当前主要起抽象层占位作用,不承担严格匹配校验 - Vulkan 路径最严格,意味着上层若想写出稳定的跨后端代码,最好按 Vulkan 约束准备 attachment