Files
XCEngine/docs/api/XCEngine/RHI/RHIFramebuffer/RHIFramebuffer.md

175 lines
6.7 KiB
Markdown
Raw Normal View History

2026-03-26 16:45:24 +08:00
# RHIFramebuffer
**命名空间**: `XCEngine::RHI`
**类型**: `class (abstract)`
**头文件**: `XCEngine/RHI/RHIFramebuffer.h`
2026-03-29 01:36:53 +08:00
**描述**: 抽象 framebuffer 对象,负责把 render pass 描述与一组具体 attachment view 绑定起来,形成一次可用于渲染输出的目标集合。
2026-03-26 16:45:24 +08:00
2026-03-29 01:36:53 +08:00
## 角色概述
2026-03-26 16:45:24 +08:00
2026-03-29 01:36:53 +08:00
`RHIFramebuffer` 不是纹理资源本体,也不是 render pass 本体。它更像是两者之间的一层“装配结果”:
2026-03-26 16:45:24 +08:00
2026-03-29 01:36:53 +08:00
- [RHIRenderPass](../RHIRenderPass/RHIRenderPass.md) 负责描述这次 pass 需要怎样的颜色/深度附件语义
- [RHIResourceView](../RHIResourceView/RHIResourceView.md) 负责把具体纹理解释为 render target / depth stencil 视图
- `RHIFramebuffer` 则把它们组合成一份真正可绑定到 GPU 的输出目标配置
2026-03-26 16:45:24 +08:00
2026-03-29 01:36:53 +08:00
这种拆分与商业引擎、现代图形 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
2026-03-26 16:45:24 +08:00
## 公共方法
2026-03-29 01:36:53 +08:00
- [Initialize](Initialize.md)
- [Shutdown](Shutdown.md)
- [GetNativeHandle](GetNativeHandle.md)
- [GetWidth](GetWidth.md)
- [GetHeight](GetHeight.md)
- [IsValid](IsValid.md)
2026-03-26 16:45:24 +08:00
## 相关文档
2026-03-29 01:36:53 +08:00
- [当前模块](../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)