# RHIFramebuffer **命名空间**: `XCEngine::RHI` **类型**: `class (abstract)` **头文件**: `XCEngine/RHI/RHIFramebuffer.h` **描述**: 抽象 framebuffer 对象,负责把 render pass 描述与一组具体 attachment view 绑定起来,形成一次可用于渲染输出的目标集合。 ## 角色概述 `RHIFramebuffer` 不是纹理资源本体,也不是 render pass 本体。它更像是两者之间的一层“装配结果”: - [RHIRenderPass](../RHIRenderPass/RHIRenderPass.md) 负责描述这次 pass 需要怎样的颜色/深度附件语义 - [RHIResourceView](../RHIResourceView/RHIResourceView.md) 负责把具体纹理解释为 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](GetNativeHandle.md) 当前返回 `nullptr` - [IsValid](IsValid.md) 的判断条件只是 `m_renderPass != nullptr` - 当前实现不会主动校验 attachment 数量、尺寸、格式是否与 render pass 严格匹配 - 即使宽高是 `0`,对象也可能创建成功 换句话说,D3D12 路径里的 `RHIFramebuffer` 更像“供 command list 使用的一组 RTV/DSV 句柄快照”,而不是一个强校验、强约束的独立 native 对象。 ### OpenGL OpenGL 路径会创建真正的 FBO,并把 resource view 中记录的附件信息挂到 FBO 上,然后调用 `glCheckFramebufferStatus()` 做完整性检查。 这条路径的特点是: - [GetNativeHandle](GetNativeHandle.md) 返回的是 OpenGL framebuffer 对象名 - [IsValid](IsValid.md) 反映的是 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 > 0` 时 `colorAttachments` 不能为 `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](../RHIDevice/CreateFramebuffer.md) 创建,并以裸指针返回。 它只拥有自己内部的 native / backend 状态,不拥有: - `RHIRenderPass` - `RHIResourceView` - 底层 `RHITexture` 销毁 framebuffer 不等于销毁附件纹理或其视图。当前最稳妥的释放方式仍然是: 1. [Shutdown](Shutdown.md) 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 framebuffer,对 [GetNativeHandle](GetNativeHandle.md) 不能做可移植假设 - OpenGL 路径的 `renderPass` 当前主要起抽象层占位作用,不承担严格匹配校验 - Vulkan 路径最严格,意味着上层若想写出稳定的跨后端代码,最好按 Vulkan 约束准备 attachment ## 公共方法 - [Initialize](Initialize.md) - [Shutdown](Shutdown.md) - [GetNativeHandle](GetNativeHandle.md) - [GetWidth](GetWidth.md) - [GetHeight](GetHeight.md) - [IsValid](IsValid.md) ## 相关文档 - [当前模块](../RHI.md) - [RHIRenderPass](../RHIRenderPass/RHIRenderPass.md) - [RHIResourceView](../RHIResourceView/RHIResourceView.md) - [RHITexture](../RHITexture/RHITexture.md) - [RHIDevice](../RHIDevice/RHIDevice.md) - [RHICommandList](../RHICommandList/RHICommandList.md) - [API 总索引](../../../main.md)