docs(rhi): sync buffer init and sample quality docs

This commit is contained in:
2026-04-10 17:59:59 +08:00
parent cba3823ea2
commit 85b8b3e583
13 changed files with 418 additions and 483 deletions

View File

@@ -10,19 +10,66 @@
```cpp
RHIBuffer* CreateBuffer(const BufferDesc& desc) override;
RHIBuffer* CreateBuffer(
const BufferDesc& desc,
const void* initialData,
size_t initialDataSize,
ResourceStates finalState = ResourceStates::GenericRead) override;
```
## 作用
更新 `m_device`
在 D3D12 后端创建 `RHIBuffer`。基础重载负责分配资源本体;带初始数据的重载额外负责把初始内容上传到默认堆缓冲,并把资源切换到目标状态
## 当前实现
## 基础重载的当前实现
- 会更新 `m_device`
- 当前实现会调用 `D3D12Buffer``Initialize``SetStride``SetBufferType`
- 包含条件分支,并可能提前返回。
- 包含 `nullptr` 相关分支。
`CreateBuffer(const BufferDesc&)` 主要按 `BufferType` 决定堆类型:
- `ReadBack` 使用 `D3D12_HEAP_TYPE_READBACK`
- `Constant``Vertex``Index` 使用 `D3D12_HEAP_TYPE_UPLOAD`
- 其他情况默认使用 `D3D12_HEAP_TYPE_DEFAULT`
这条路径更像“只把资源壳创建出来”,后续是否还要做数据上传、状态切换,由调用方继续安排。
## 带初始数据重载的当前实现
带初始数据的重载是一个真实的 D3D12 上传路径,而不是简单转调基类默认实现。它会:
1. 对输入做前置校验:
- `initialData` 不能为空
- `initialDataSize` 不能为 `0`
- `initialDataSize` 不能超过 `desc.size`
- `desc.size` 不能超出 `size_t` 可表达范围
- `ReadBack` 缓冲直接拒绝
2. 创建默认堆目标缓冲,初始状态固定为 `CopyDst`
3. 临时创建 direct queue、allocator 和 command list
4. 创建 upload heap 缓冲并映射
5. 先把整个目标尺寸补零,再把传入字节流复制进去
6. 录制 `CopyBufferRegion`
7. 按需插入从 `CopyDst``finalState` 的 transition barrier
8. 提交命令并 `WaitForIdle()`
9. 返回已经处于 `finalState` 的目标缓冲
## 为什么这么实现
对 D3D12 来说,“默认堆资源不能直接 CPU 写入”是基本现实,因此如果想在 `CreateBuffer` 阶段就得到一个可用于 GPU 访问、且已经带初始内容的缓冲,最自然的做法就是:
- 默认堆承载最终资源
- 上传堆承载 CPU 可写的 staging 数据
- 命令列表执行 copy
- barrier 完成状态收尾
这也是商业引擎里最常见的 D3D12 初始化资源路径。
## 使用建议
- 适合创建静态顶点缓冲、索引缓冲、常量缓冲和其他启动期 GPU 资源。
- 不适合大规模批量流式上传,因为它每次调用都会创建一套临时上传上下文并阻塞等待完成。
- `ReadBack` 缓冲不支持这条重载,因为这种资源方向和上传初始化目标相反。
## 相关文档
- [D3D12Device](D3D12Device.md)
- [RHIDevice::CreateBuffer](../../RHIDevice/CreateBuffer.md)
- [D3D12Buffer](../D3D12Buffer/D3D12Buffer.md)

View File

@@ -6,88 +6,95 @@
**头文件**: `XCEngine/RHI/D3D12/D3D12Device.h`
**描述**: D3D12 后端的设备根对象,负责创建 DXGI factory、选择适配器、创建 `ID3D12Device`,并作为所有 D3D12 资源工厂的统一入口
**描述**: `RHIDevice` 在 Direct3D 12 后端上的具体实现,负责创建设备、查询能力,并把统一的 `RHI` 创建请求翻译成 D3D12 资源与管线对象
## 概览
## 角色概述
如果把抽象层里的 [`RHIDevice`](../../RHIDevice/RHIDevice.md) 理解成“跨后端设备契约,那么 `D3D12Device` 就是契约在 Direct3D 12 上的落地实现
它承担三类职责:
- 初始化 D3D12 运行时根对象:`IDXGIFactory4``IDXGIAdapter1``ID3D12Device`
- 缓存设备信息与能力信息,供测试、日志和上层系统查询
- 把 RHI 的资源创建请求翻译成 D3D12 后端对象,例如 buffer、texture、command queue、command list、pipeline state、descriptor heap 与 descriptor set
如果把 [RHIDevice](../../RHIDevice/RHIDevice.md) 理解跨后端契约,那么 `D3D12Device` 就是这套契约在 D3D12 上的落地层。它既是设备持有者,也是后端资源工厂
## 生命周期
推荐顺序
推荐顺序如下
1. 构造 `D3D12Device`
2. 调用 [`Initialize`](Initialize.md)
3. 通过设备创建队列、命令列表、资源和状态对象
4. 先销毁设备创建出来的对象
2. 调用 [Initialize](Initialize.md)
3. 通过设备创建命令队列、命令列表、资源、描述符和管线状态
4. 先销毁设备创建出来的对象
5. 最后调用 `Shutdown()`
析构函数会调用 `Shutdown()`,但工程上仍应把显式关闭视为标准用法
析构函数会兜底调用 `Shutdown()`,但工程上的正确用法仍然是显式关闭
## 当前实现的真实行为
## 当前实现要点
### 适配器与调试层
- `Initialize()` 会先创建 DXGI factory然后从 `adapterIndex` 开始枚举适配器。
- `Initialize()` 会先创建 DXGI factory再按 `adapterIndex` 起点枚举适配器。
- 软件适配器会被跳过。
- 当前实现选择“第一个能以 `D3D_FEATURE_LEVEL_11_0` 创建设备的硬件适配器”,而不是做更复杂的打分排序
- `enableDebugLayer` 会尝试启用 `ID3D12Debug``DXGI_CREATE_FACTORY_DEBUG`,这是真正接线了的;但它仍然属于基础调试层支持,不等同于完整 GPU validation 工作流。
- `enableDebugLayer` 会尝试启用 D3D12 调试层与 DXGI 调试 factory
### 资源工厂策略
这说明当前实现更强调“稳定创建设备并接起调试能力”,而不是复杂的适配器评分系统。
- [`CreateBuffer`](CreateBuffer.md) 会按 `BufferType` 粗粒度选择 heap
### 缓冲创建策略
[CreateBuffer](CreateBuffer.md) 现在有两条路径:
- 基础重载按 `BufferType` 选择堆类型:
- `ReadBack` -> `D3D12_HEAP_TYPE_READBACK`
- `Constant / Vertex / Index` -> `D3D12_HEAP_TYPE_UPLOAD`
- 其他 -> `D3D12_HEAP_TYPE_DEFAULT`
- [`CreateTexture`](CreateTexture.md) 对深度格式自动补 `ALLOW_DEPTH_STENCIL`,对常规 2D 纹理自动补 `ALLOW_RENDER_TARGET`
- 带初始数据的 `CreateTexture()` 会临时创建 upload queue、allocator、command list同步上传后再 `WaitForIdle()`,因此这是一个阻塞式初始化路径。
- [`CreateCommandList`](CreateCommandList.md) 会先创建专用 `D3D12CommandAllocator`,再创建 `D3D12CommandList`;命令列表内部通过 `ComPtr` 持有 allocator。
- [`CreateDescriptorSet`](CreateDescriptorSet.md) 实际上把分配工作转交给 `D3D12DescriptorHeap::AllocateSet()`
- `Constant` / `Vertex` / `Index` -> `D3D12_HEAP_TYPE_UPLOAD`
- 其他默认走 `D3D12_HEAP_TYPE_DEFAULT`
- 带初始数据的重载会专门建立上传上下文,把数据复制进默认堆缓冲,然后切换到目标状态
### 能力与设备信息
第二条路径是这次文档必须强调的新行为。它不是简单地调用 `SetData`,而是显式做了一次 D3D12 风格的 upload-copy-transition 流程。
- `QueryAdapterInfo()` 会填充 `AdapterInfo``RHIDeviceInfo`
- 能力查询覆盖了 ray tracing、mesh shader、conservative rasterization、shader model 等字段
- 一部分上限值直接来自 D3D12 常量,而不是运行时逐项探测
### 带初始数据的 CreateBuffer
## 所有权与资源管理
当前实现会:
- 绝大多数 `Create*()` 返回裸指针,调用方负责 `Shutdown()``delete`
- `D3D12Device` 不做集中式对象注册和统一回收
- 这更接近商业引擎底层 backend 的“轻工厂”模式:好处是后端简单直接,代价是上层必须严格控制释放顺序
1. 拒绝 `nullptr`、零大小、越界大小以及 `ReadBack` 缓冲。
2. 创建一个默认堆目标缓冲,初始状态为 `CopyDst`
3. 临时创建 `D3D12CommandQueue``D3D12CommandAllocator``D3D12CommandList` 作为上传上下文。
4. 创建上传堆缓冲,把整个目标大小区域先补零,再拷贝 `initialData`
5. 录制 `CopyBufferRegion`
6. 如果 `finalState` 不是 `CopyDst`,再显式插入一次 transition barrier。
7. `Close()` 命令列表并 `WaitForIdle()`,最后返回已处于目标状态的缓冲。
## 线程语义
这条路径非常适合静态资源引导,但它是阻塞式的、每次调用都临时创建上传上下文,因此不应该被当成批量流式上传系统。
`D3D12Device` 自身没有显式加锁。更稳妥的使用方式是:
### 纹理与管线创建
- 初始化与关闭在单线程串行完成
- 资源创建由调用方保证外部同步
- 不要把它当成天然线程安全的全局服务
- `CreateTexture` 会根据描述决定资源 flag并支持带初始数据的同步上传路径。
- `CreatePipelineState` 会把 `GraphicsPipelineDesc::sampleCount``sampleQuality` 一并下传给 `D3D12PipelineState`
## 当前限制
后一点很关键,因为 `sampleQuality` 已经不再是“只有描述结构里有,后端不消费”的字段了。
- 适配器选择仍是“从起始索引往后取第一个可用硬件设备”
- 带初始数据的纹理创建走同步上传路径,初始化成本偏高
- 某些能力字段是“基础探测 + 常量填充”的组合,不是完整 profile
## 设计理解
`D3D12Device` 的实现风格很典型地体现了现代显式图形 API 后端的工程习惯:
- 上层看到的是统一工厂接口。
- 后端真正承担上传缓冲、命令录制、状态转换和 native 对象创建。
- 为了减少上层复杂度,部分便捷路径允许用同步、阻塞式实现换取简单和确定性。
这种设计在商业引擎里非常常见。真正成熟后,后续通常会继续把“便捷初始化路径”和“高吞吐上传系统”进一步拆分。
## 重点公共方法
- [Initialize](Initialize.md)
- [CreateBuffer](CreateBuffer.md)
- [CreateTexture](CreateTexture.md)
- [CreateCommandQueue](CreateCommandQueue.md)
- [CreateCommandList](CreateCommandList.md)
- [CreatePipelineState](CreatePipelineState.md)
- [CreateDescriptorPool](CreateDescriptorPool.md)
- [CreateDescriptorSet](CreateDescriptorSet.md)
## 相关文档
- [D3D12](../D3D12.md)
- [当前目录](../D3D12.md)
- [RHIDevice](../../RHIDevice/RHIDevice.md)
- [D3D12Buffer](../D3D12Buffer/D3D12Buffer.md)
- [D3D12PipelineState](../D3D12PipelineState/D3D12PipelineState.md)
- [D3D12CommandQueue](../D3D12CommandQueue/D3D12CommandQueue.md)
- [D3D12CommandList](../D3D12CommandList/D3D12CommandList.md)
- [D3D12DescriptorSet](../D3D12DescriptorSet/D3D12DescriptorSet.md)
- [API 总索引](../../../../main.md)

View File

@@ -6,62 +6,69 @@
**头文件**: `XCEngine/RHI/D3D12/D3D12PipelineState.h`
**描述**: D3D12 pipeline state 封装,保存 Unity SRP 风格的管线配置,并在需要时生成 graphics / compute PSO
**描述**: D3D12 后端的 `RHIPipelineState` 实现,负责缓存跨后端管线描述,并在需要时生成 graphics / compute `ID3D12PipelineState`
## 概述
## 角色概述
`D3D12PipelineState` 是 D3D12 后端的 PSO 包装。它不是单纯的原生句柄壳,而是先缓存一整套与引擎 RHI 对齐的状态描述,再在 `EnsureValid()` 或初始化阶段把这些配置固化 D3D12 graphics / compute pipeline state object。头文件里当前可见的职责包括
`D3D12PipelineState` 不是一层薄包装。它会先缓存输入布局、光栅化、混合、深度模板、render target 格式、多重采样配置和 shader bytecode然后在初始化或 `EnsureValid()` 阶段把这些状态固化 D3D12 PSO。
- 存储 rasterizer、blend、depth-stencil、input layout、render target format 等状态
- 接收 graphics / compute shader bytecode
- 管理 root signature 和最终 `ID3D12PipelineState`
- 为命令列表绑定前提供 hash、类型和有效性查询
这和商业级引擎常见的做法一致:高层管线描述统一,后端负责把描述落到 native PSO。
## 声明概览
## 当前实现要点
| 声明 | 类型 | 说明 |
|------|------|------|
| `D3D12PipelineState` | `class` | D3D12 后端的 graphics / compute PSO 封装。 |
### sample quality 已经进入真实后端语义
## 公共方法
这是本轮需要同步到文档的重点变化。`SetSampleQuality` 不再只是接口层占位:
| 方法 | 描述 |
|------|------|
| [D3D12PipelineState()](Constructor.md) | 构造 `D3D12PipelineState` 实例。 |
| [~D3D12PipelineState()](Destructor.md) | 执行 `Shutdown(...)` 相关流程。 |
| [Initialize](Initialize.md) | 执行 `clear``push_back``EnsureValid` 协同流程。 |
| [SetInputLayout](SetInputLayout.md) | 更新 `m_inputLayoutDesc`。 |
| [SetRasterizerState](SetRasterizerState.md) | 写入 `m_rasterizerDesc`。 |
| [SetBlendState](SetBlendState.md) | 写入 `m_blendDesc`。 |
| [SetDepthStencilState](SetDepthStencilState.md) | 写入 `m_depthStencilDesc`。 |
| [SetTopology](SetTopology.md) | 写入 `m_topologyType`。 |
| [SetRenderTargetFormats](SetRenderTargetFormats.md) | 执行该公开方法对应的当前实现。 |
| [SetSampleCount](SetSampleCount.md) | 写入 `m_sampleCount`。 |
| [SetSampleQuality](SetSampleQuality.md) | 写入 `m_sampleQuality`,并影响最终 `D3D12_SAMPLE_DESC` 与 hash。 |
| [SetComputeShader](SetComputeShader.md) | 执行 `Reset``IsValid` 协同流程。 |
| [SetRootSignature](SetRootSignature.md) | 写入 `m_rootSignature`。 |
| [GetRasterizerState](GetRasterizerState.md) | 返回 `m_rasterizerDesc` 当前值。 |
| [GetBlendState](GetBlendState.md) | 返回 `m_blendDesc` 当前值。 |
| [GetDepthStencilState](GetDepthStencilState.md) | 返回 `m_depthStencilDesc` 当前值。 |
| [GetInputLayout](GetInputLayout.md) | 返回 `m_inputLayoutDesc` 当前值。 |
| [GetHash](GetHash.md) | 返回 `hash`。 |
| [GetComputeShader](GetComputeShader.md) | 返回 `m_computeShader` 当前值。 |
| [HasComputeShader](HasComputeShader.md) | 返回 `m_csBytecode.pShaderBytecode != nullptr && m_csBytecode.BytecodeLength > 0`。 |
| [IsValid](IsValid.md) | 返回 `m_finalized` 当前值。 |
| [EnsureValid](EnsureValid.md) | 执行 `HasComputeShader``CreateD3D12ComputePSO``CreateD3D12PSO` 协同流程。 |
| [SetShaderBytecodes](SetShaderBytecodes.md) | 执行该公开方法对应的当前实现。 |
| [SetComputeShaderBytecodes](SetComputeShaderBytecodes.md) | 写入 `m_csBytecode`。 |
| [Shutdown](Shutdown.md) | 执行 `Reset` 协同流程。 |
| [GetPipelineState](GetPipelineState.md) | 返回 `m_pipelineState.Get()` 的结果。 |
| [GetComputePipelineState](GetComputePipelineState.md) | 返回 `m_computePipelineState.Get()` 的结果。 |
| [GetRootSignature](GetRootSignature.md) | 返回 `m_rootSignature.Get()` 的结果。 |
| [GetNativeHandle](GetNativeHandle.md) | 返回 `m_pipelineState.Get()` 的结果。 |
| [GetType](GetType.md) | 返回 `HasComputeShader() ? PipelineType::Compute : PipelineType::Graphics`。 |
| [Bind](Bind.md) | 当前实现为空。 |
| [Unbind](Unbind.md) | 当前实现为空。 |
| [CreateInputElement](CreateInputElement.md) | 执行 `ToD3D12(...)` 相关流程。 |
- `D3D12PipelineState` 现在有独立的 `m_sampleQuality`
- `Initialize(...)` 会从 `D3D12_GRAPHICS_PIPELINE_STATE_DESC::SampleDesc.Quality` 回填该值
- `SetSampleQuality(...)` 会更新缓存状态
- `CreateD3D12PSO()` 会把它写回 `desc.SampleDesc.Quality`
- `GetHash()` 现在同时把 `renderTargetCount``depthStencilFormat``sampleCount``sampleQuality` 编进 hash
这意味着对 D3D12 后端来说,不同的 MSAA quality 已经是不同的管线状态,而不只是无关紧要的附属参数。
### 图形与计算路径
- 图形路径依赖输入布局、固定功能状态和 VS/PS bytecode 共同创建 graphics PSO
- 计算路径依赖 compute shader bytecode 创建 compute PSO
- `GetType()` 仍然通过“是否有 compute shader”区分 `Graphics``Compute`
### 有效性
`IsValid()` 表示当前对象是否已经具备可用的 native PSO`EnsureValid()` 则负责在需要时推进到该状态。对 D3D12 来说,这种语义比较接近“真正的 PSO 是否已经可绑定”。
## 设计理解
`sampleQuality` 被正式纳入 D3D12 实现后,这个类更完整地承担了“把统一描述翻译成 D3D12 PSO”的职责。这样做的好处有两个
- 上层渲染器不需要为了 quality 字段单独写 D3D12 分支
- 后端 hash 与 PSO 创建语义保持一致,避免把实际上不同的 MSAA 配置错误地视为同一个管线
这类一致性对商业引擎非常重要,因为 PSO 缓存、材质系统和渲染图规划都依赖稳定而精确的状态键。
## 重点公共方法
- [Initialize](Initialize.md)
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
- [SetBlendState](SetBlendState.md)
- [SetDepthStencilState](SetDepthStencilState.md)
- [SetTopology](SetTopology.md)
- [SetRenderTargetFormats](SetRenderTargetFormats.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
- [SetComputeShader](SetComputeShader.md)
- [SetRootSignature](SetRootSignature.md)
- [GetHash](GetHash.md)
- [EnsureValid](EnsureValid.md)
- [GetPipelineState](GetPipelineState.md)
- [GetComputePipelineState](GetComputePipelineState.md)
## 相关文档
- [当前目录](../D3D12.md) - 返回 `D3D12` 平行目录
- [API 总索引](../../../../main.md) - 返回顶层索引
- [当前目录](../D3D12.md)
- [RHIPipelineState](../../RHIPipelineState/RHIPipelineState.md)
- [D3D12Device](../D3D12Device/D3D12Device.md)
- [D3D12CommandList](../D3D12CommandList/D3D12CommandList.md)
- [API 总索引](../../../../main.md)

View File

@@ -14,15 +14,23 @@ void SetSampleQuality(uint32_t quality) override;
## 作用
更新 D3D12 后端缓存的 sample quality
设置 D3D12 管线状态缓存MSAA quality 级别
## 当前实现
-把参数写入 `m_sampleQuality`
- 该值会进入 `GetHash()``renderTargetHash`
- 在 graphics PSO 创建路径里,最终会写入 `D3D12_GRAPHICS_PIPELINE_STATE_DESC::SampleDesc.Quality`
当前实现会直接把参数写入 `m_sampleQuality`这个值随后会在两个关键位置被消费:
1. `GetHash()` 会把 `m_sampleQuality` 纳入 hash 计算
2. `CreateD3D12PSO()` 会把它写入 `D3D12_GRAPHICS_PIPELINE_STATE_DESC::SampleDesc.Quality`
因此它已经是影响 PSO 身份和 native 创建结果的真实状态,而不是占位字段。
## 为什么重要
如果只记录 `sampleCount`,却忽略 quality那么不同的 D3D12 多重采样配置可能会被错误地视为同一个管线状态。把 quality 纳入缓存和 hash才能保证高层描述、PSO 创建结果和缓存键保持一致。
## 相关文档
- [D3D12PipelineState](D3D12PipelineState.md)
- [RHIPipelineState::SetSampleQuality](../../RHIPipelineState/SetSampleQuality.md)
- [SetSampleCount](SetSampleCount.md)

View File

@@ -6,56 +6,44 @@
**头文件**: `XCEngine/RHI/OpenGL/OpenGLPipelineState.h`
**描述**: OpenGL 后端的 CPU 侧管线状态容器;它统一跨后端 PSO 概念,但真实行为仍然是“在绑定时把缓存状态写回 OpenGL 上下文”
**描述**: OpenGL 后端的 `RHIPipelineState` 实现。它统一接受跨后端管线描述,但真实行为更接近“绑定时写回状态”的 CPU 侧状态容器,而不是显式 API 意义上的原生 PSO
## 概览
## 角色概述
`OpenGLPipelineState` 和 D3D12/Vulkan 的原生 PSO 有本质区别。显式 API 的 PSO 更像“预编译不可变的 GPU 对象”;当前 OpenGL 实现则是一个运行时状态包,里面保存的是:
OpenGL 没有 D3D12/Vulkan 那样的标准化 native PSO 语义,因此 `OpenGLPipelineState` 的职责不是预编译一个不可变的 GPU 管线对象,而是把高层状态先缓存下来,在 `Bind()` 时按需写回 OpenGL 上下文。
- graphics program 或 compute program 句柄
- 输入布局描述
- 深度/模板状态
- 混合状态
- 光栅化状态
- viewport / scissor / clear color 等附加缓存
这类设计在商业引擎里非常常见:抽象层仍然保持现代 PSO 形状,后端实现则承认旧式状态机 API 的现实。
真正的 OpenGL 调用发生在 [Bind](Bind.md) 和各个 `Apply*` 方法里,而不是在对象创建时一次性固化。
## 当前实现要点
## 当前实现的真实行为
- `Bind()` 会优先绑定 compute program否则绑定 graphics program
- `Apply()` 负责把缓存的深度/模板、混合和光栅化状态写回 OpenGL
- viewport 与 scissor 仍由独立方法处理
- [Bind](Bind.md) 会优先绑定 compute program否则绑定 graphics program
- `Bind()` 之后只会调用 [Apply](Apply.md),即深度/模板、混合、光栅化三类状态
- [ApplyViewport](ApplyViewport.md) 与 [ApplyScissor](ApplyScissor.md) 需要单独调用
- [SetRenderTargetFormats](SetRenderTargetFormats.md)、[SetSampleCount](SetSampleCount.md)、[SetSampleQuality](SetSampleQuality.md)、[GetHash](GetHash.md) 目前基本是占位实现
- [IsValid](IsValid.md) 永远返回 `true`
- [EnsureValid](EnsureValid.md) 为空实现
- [GetNativeHandle](GetNativeHandle.md) 只返回 graphics `m_program`
同时需要明确几件事情:
## 设计背景
- [SetRenderTargetFormats](SetRenderTargetFormats.md) 当前基本是占位接口
- [SetSampleCount](SetSampleCount.md) 当前基本是占位接口
- [SetSampleQuality](SetSampleQuality.md) 当前同样是占位接口
- [GetHash](GetHash.md) 目前也不是严格的跨状态稳定键
- [IsValid](IsValid.md) 近似恒为 `true`
- [EnsureValid](EnsureValid.md) 当前为空实现
商业引擎在做 OpenGL 后端时,通常不会试图伪造一套完全等价于 D3D12/Vulkan 的原生 PSO而是会保留一个“高层 PSO 接口 + 低层状态写回器”的折中结构。当前实现就属于这一类设计:
## 为什么仍然保留 SetSampleQuality
- 高层渲染代码仍可依赖统一的 `RHIPipelineState`
- OpenGL 后端把真实成本集中到绑定阶段
- 这样既保留了跨后端 API 一致性,也避免在 OpenGL 上硬造不存在的驱动对象
虽然当前 OpenGL 后端并没有把 sample quality 落到真实状态,但接口仍然保留,原因并不奇怪:
这和 Unity、Unreal 等商业引擎在老式状态机 API 上常用的策略是一致的:抽象层保持现代接口形状,后端实现接受状态机现实。
- 上层渲染器希望使用统一的 `RHIPipelineState` 契约
- 文档需要如实说明“该字段在 OpenGL 后端尚未真正消费”
- 将来如果后端补充更细的多重采样策略,不需要再改上层 API
## 生命周期
这正是重构期商业引擎常见的渐进式契约策略。
- [OpenGLPipelineState()](Constructor.md) 初始化为空容器
- 上层通过 [SetInputLayout](SetInputLayout.md)、[SetBlendState](SetBlendState.md)、[SetDepthStencilState](SetDepthStencilState.md)、[SetRasterizerState](SetRasterizerState.md) 等接口缓存状态
- 设备创建 graphics pipeline 时,会通过 [SetOwnedGraphicsShader](SetOwnedGraphicsShader.md) 把编译好的 `OpenGLShader` 交给它管理
- 命令提交阶段由 [Bind](Bind.md) 把状态真正写入 OpenGL
- [Shutdown](Shutdown.md) 释放自有 graphics shader并重置程序句柄与标记位
## 重点限制
## 重要限制
- 不是原生 OpenGL pipeline object
- 不是不可变对象,状态可以随时被覆盖
- 不自动应用 viewport / scissor
- 部分 RHI 描述字段只被缓存,没有进入真实 OpenGL 调用
- 没有稳定的 hash 和严格的创建期验证
- 它不是原生 OpenGL pipeline object
- 它不是不可变对象,状态可以被持续覆盖
- MSAA 相关接口当前只保留抽象层形状,不代表后端已经完整支持
## 关键方法
@@ -66,9 +54,13 @@
- [SetBlendState](SetBlendState.md)
- [SetDepthStencilState](SetDepthStencilState.md)
- [SetRasterizerState](SetRasterizerState.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
## 相关文档
- [OpenGLShader](../OpenGLShader/OpenGLShader.md)
- [OpenGLCommandList](../OpenGLCommandList/OpenGLCommandList.md)
- [OpenGLDevice](../OpenGLDevice/OpenGLDevice.md)
- [RHIPipelineState](../../RHIPipelineState/RHIPipelineState.md)
- [API 总索引](../../../../main.md)

View File

@@ -1,4 +1,12 @@
# OpenGLPipelineState::SetSampleQuality()
# OpenGLPipelineState::SetSampleQuality
**命名空间**: `XCEngine::RHI`
**类型**: `method`
**头文件**: `XCEngine/RHI/OpenGL/OpenGLPipelineState.h`
## 签名
```cpp
void SetSampleQuality(uint32_t quality) override;
@@ -6,15 +14,20 @@ void SetSampleQuality(uint32_t quality) override;
## 作用
为跨后端 `RHIPipelineState` 接口保留 sample quality 入口
接受抽象层传入的 MSAA quality 参数
## 当前实现行为
## 当前实现
- 空实现
- `quality` 当前不会被缓存,也不会映射到额外的 OpenGL 状态。
- 现阶段 OpenGL 后端的多采样行为仍主要落在 rasterizer 状态里的 `multisampleEnable`
当前实现是空函数,`quality` 不会被保存,也不会落到真实 OpenGL 状态。它的主要意义是让 `OpenGLPipelineState` 满足统一的 `RHIPipelineState` 契约
## 这意味着什么
- 上层可以继续用统一接口描述多重采样配置
- 但不能把该调用理解成“OpenGL 后端已经支持 sample quality 级别控制”
- 当前如果逻辑严格依赖 quality 的差异化行为,应避免在 OpenGL 后端做此类假设
## 相关文档
- [OpenGLPipelineState](OpenGLPipelineState.md)
- [RHIPipelineState::SetSampleQuality](../../RHIPipelineState/SetSampleQuality.md)
- [SetSampleCount](SetSampleCount.md)

View File

@@ -20,26 +20,44 @@ virtual RHIBuffer* CreateBuffer(
## 作用
创建 `RHIBuffer`当前既有后端必须实现的基础重载,也有基类直接提供的“创建并写入初始数据”便捷重载
创建 `RHIBuffer`基础重载只创建资源本体;带初始数据的重载额外负责把给定字节流写入缓冲,并把资源切换到目标状态
## 当前实现
## 默认实现语义
- `CreateBuffer(const BufferDesc& desc)`
- 仍然是纯虚接口,具体后端必须提供基础 buffer 创建逻辑。
- `CreateBuffer(desc, initialData, initialDataSize, finalState)`
-`initialData == nullptr``initialDataSize == 0` 时,会回退到基础重载。
-`initialDataSize > desc.size` 时,返回 `nullptr`
- 成功创建后会先调用 `buffer->SetData(...)` 写入初始数据。
-`initialDataSize < desc.size`,会把剩余空间补零。
- 最后会调用 `buffer->SetState(finalState)`
抽象基类只把第一种重载保留为纯虚接口。第二种重载在基类里已经提供了统一默认实现,流程如下:
当前 `VulkanDevice` / `OpenGLDevice` 只实现了基础重载,因此会走基类默认实现;`D3D12Device` 另外提供了后端自定义重载
1. 如果没有初始数据,直接转发到 `CreateBuffer(desc)`
2. 如果 `initialDataSize > desc.size`,返回 `nullptr`
3. 创建基础缓冲。
4. 通过 `RHIBuffer::SetData` 写入初始数据。
5. 如果缓冲比初始数据更大,则把剩余区域补零。
6. 调用 `SetState(finalState)`,让返回对象带着上层期望的资源状态离开工厂。
## 真实调用点
这意味着“带初始化数据的创建”在抽象层已经成为正式契约,而不是某个后端的私有便利函数。
- `engine/src/Rendering/Caches/RenderResourceCache.cpp`
- 当前体积 payload 上传路径会用这个重载创建 storage buffer并把最终状态设置为 `GenericRead`
## 设计意图
这个重载的价值不是取代完整的上传系统,而是给渲染器、资源加载器和测试代码一个统一、直接的入口:
- 小型静态常量缓冲和顶点数据可以一步创建完成。
- 各后端都能保留自己的最佳实现空间。
- 上层不需要为“先建空缓冲,再手动上传”重复铺设样板逻辑。
商业引擎底层通常都会保留这种“便捷初始化路径”,因为它能降低资源引导阶段的复杂度;真正的大规模流式上传则仍然会走专门的上传器和队列系统。
## 后端实现差异
- `D3D12Device` 已经重写该重载,使用上传缓冲和拷贝命令完成初始化,而不是依赖默认的 `SetData` 路径。
- 其他设备实现可以继续继承默认语义,也可以在需要时重写以获得更好的性能或更严格的状态控制。
## 使用建议
- 适合启动期、测试、工具链或中小型静态数据。
- 不要把它当成高频 streaming API。
- 调用方仍然负责对象生命周期管理:成功返回后需要在合适时机 `Shutdown()` 并销毁缓冲。
## 相关文档
- [RHIDevice](RHIDevice.md)
- [RHIBuffer](../RHIBuffer/RHIBuffer.md)
- [D3D12Device::CreateBuffer](../D3D12/D3D12Device/CreateBuffer.md)

View File

@@ -6,94 +6,36 @@
**头文件**: `XCEngine/RHI/RHIDevice.h`
**描述**: 抽象图形设备接口,负责设备初始化、能力查询,并作为大多数 RHI 对象的统一创建中心
**描述**: `RHI` 设备抽象,负责初始化后端、暴露能力信息,并作为大多数资源、命令与管线对象的统一创建入口
## 角色概述
`RHIDevice` 当前 `RHI` 抽象层的中心对象。它不像某些引擎那样把资源工厂、提交器、能力查询器分拆成多套接口,而是采用一种更集中的“设备即工厂”设计:
`RHIDevice` 当前架构里承担的是“设备即工厂”的职责。渲染器不需要分别依赖 buffer 工厂、texture 工厂、pipeline 工厂和 descriptor 工厂,而是统一通过一个设备对象创建后端资源。这种做法和很多商业级引擎底层渲染架构一致,优点是上层入口集中、后端差异被压缩在实现层,代价是 `RHIDevice` 接口面会比较大。
- 初始化后端上下文或 native device
- 持有能力信息和设备信息
- 创建资源对象、命令对象、pipeline 对象和 descriptor 对象
- 提供必要的 native handle 泄露口
## 为什么这样设计
这和原生图形 API 的心智模型比较接近,也符合很多商业引擎底层渲染后端的设计习惯。好处是统一、直接,代价是接口面比较大
- 上层渲染代码只需要依赖一套 `RHI` 入口,不必按后端分叉创建流程
- `BufferDesc``TextureDesc``GraphicsPipelineDesc` 这些描述结构可以直接作为跨后端契约。
- 同一个创建请求可以在不同后端走不同实现细节,例如直接 CPU 写入、上传缓冲复制、延迟编译或立即创建。
这和 Unity SRP、Unreal RHI 一类设计的核心思路一致:高层描述保持统一,真正的后端差异放在设备和资源实现里消化。
## 生命周期
### 初始化
`Initialize(const RHIDeviceDesc&)` 是创建后的第一步。当前 `RHIDeviceDesc` 只暴露了三个设备级开关:
- `enableDebugLayer`
- `enableGPUValidation`
- `adapterIndex`
这说明当前设备初始化配置还比较克制,没有把所有平台差异都堆进入口参数里。
`Initialize(const RHIDeviceDesc&)` 负责建立后端上下文并填充能力与设备信息。当前设备描述主要控制调试层、GPU 验证和适配器选择,说明引擎仍然把设备创建视为底层、工程化的初始化步骤,而不是面向最终用户的复杂配置界面。
### 关闭
`Shutdown()` 负责销毁设备级状态。当前测试习惯,调用方应在删除设备之前显式调用它,而不是依赖析构函数
`Shutdown()` 负责释放设备级状态。当前 `RHI` 仍然以显式 `Shutdown()` 为主,而不是完全依赖析构自动回收,因此调用方应把“显式关闭后再销毁对象”视为标准用法
### 查询
## 缓冲创建模型
初始化成功后,调用方通常会继续读取
- [GetCapabilities](GetCapabilities.md)
- [GetDeviceInfo](GetDeviceInfo.md)
- [GetNativeDevice](GetNativeDevice.md)
其中 `GetNativeDevice()` 明确是一个“打破抽象边界”的出口,供后端专用逻辑或调试逻辑使用。
## 它负责创建什么
当前接口覆盖了 RHI 里绝大多数对象类别。
### 资源与展示
- [CreateBuffer](CreateBuffer.md)
- [CreateTexture](CreateTexture.md)
- [CreateSwapChain](CreateSwapChain.md)
`CreateTexture()` 还有一个带 `initialData``rowPitch` 的重载,允许直接创建并上传初始数据。
`CreateBuffer()` 现在也有一个由基类提供默认实现的带初始数据重载,可在创建后立即写入 payload、补零剩余区间并设置最终资源状态。
### 执行与同步
- [CreateCommandList](CreateCommandList.md)
- [CreateCommandQueue](CreateCommandQueue.md)
- [CreateFence](CreateFence.md)
### shader 与管线
- [CreateShader](CreateShader.md)
- [CreatePipelineState](CreatePipelineState.md)
- [CreatePipelineLayout](CreatePipelineLayout.md)
- [CreateSampler](CreateSampler.md)
### render pass 与 framebuffer
- [CreateRenderPass](CreateRenderPass.md)
- [CreateFramebuffer](CreateFramebuffer.md)
### descriptor 与资源视图
- [CreateDescriptorPool](CreateDescriptorPool.md)
- [CreateDescriptorSet](CreateDescriptorSet.md)
- [CreateVertexBufferView](CreateVertexBufferView.md)
- [CreateIndexBufferView](CreateIndexBufferView.md)
- [CreateRenderTargetView](CreateRenderTargetView.md)
- [CreateDepthStencilView](CreateDepthStencilView.md)
- [CreateShaderResourceView](CreateShaderResourceView.md)
- [CreateUnorderedAccessView](CreateUnorderedAccessView.md)
从接口形状就能看出,当前 `RHIDevice` 同时承担了资源创建器、视图工厂、descriptor 工厂和 pipeline 工厂的职责。
### Buffer 初始化重载
`RHIDevice.h` 当前新增了:
`CreateBuffer` 现在有两种形态,且它们表达的是两种不同层级的需求
```cpp
virtual RHIBuffer* CreateBuffer(const BufferDesc& desc) = 0;
virtual RHIBuffer* CreateBuffer(
const BufferDesc& desc,
const void* initialData,
@@ -101,58 +43,43 @@ virtual RHIBuffer* CreateBuffer(
ResourceStates finalState = ResourceStates::GenericRead);
```
它不是新的纯虚接口,而是基类直接给出的默认实现。对调用方来说,这意味着:
### 基础重载
1. 非 D3D12 后端即使只实现了基础 `CreateBuffer(const BufferDesc&)`,也天然获得了“创建后写入初始数据”的统一入口
2. 这个重载会在必要时自动把剩余空间补零,并把 buffer 状态切到 `finalState`
3. 当前 `RenderResourceCache` 的体积 payload 上传已经在使用这一路径。
`CreateBuffer(const BufferDesc&)` 只负责创建资源本体,不附带初始数据。具体放在哪种堆、初始状态是什么、后端如何实现,都由具体设备类决定
## 所有权约定
### 带初始数据的重载
是这页最关键的现实语义。
个重载是最近补进来的便捷契约,目的是让上层可以用一条调用完成“创建 + 上传初始内容 + 进入目标状态”。基类默认实现会:
当前 `RHIDevice` 的所有创建接口都返回裸指针。按 `tests/RHI/unit/test_device.cpp``test_command_list.cpp` 等现有测试来看,推荐使用方式是:
1.`initialData == nullptr` 或大小为 `0` 时退化到基础重载。
2. 拒绝 `initialDataSize > desc.size` 的非法输入。
3. 先创建基础缓冲。
4. 对前半段调用 `SetData(initialData, initialDataSize)`
5. 如果 `desc.size` 更大,则把剩余区间补零。
6. 最后把资源状态设置为 `finalState`
1. 通过 `RHIDevice` 创建对象
2. 使用对象执行初始化、录制或资源操作。
3. 结束前调用对象自己的 `Shutdown()`
4. 最后 `delete`
这个默认实现强调的是统一语义,而不是最优性能。它适合小型静态数据、启动期资源或测试代码,不适合高频流式上传
这同样适用于 `buffer``texture``queue``command list``fence``sampler``render pass``framebuffer``descriptor pool``descriptor set``resource view` 等对象。
## 后端差异
如果带着现代 RAII 预期来使用这层接口,很容易误判释放责任。当前文档必须把这一点写死
- `D3D12Device` 对带初始数据的 `CreateBuffer` 做了专门重写,会显式创建上传缓冲、录制拷贝命令并做状态转换
- 其他后端当前只需要满足统一契约,不一定都实现了同等级别的上传路径优化。
## 当前设计理解
这正是抽象层的价值所在:上层只说“我要一个带初始内容的缓冲”,至于后端是 CPU 直写、上传队列复制还是暂时退化实现,都留给设备实现层。
从架构方向看,`RHIDevice` 的定位是合理的:
## 所有权与使用约定
- 抽象层统一把后端对象创建集中到设备上,方便 renderer 只依赖一套入口。
- 设备负责暴露能力信息,便于上层在初始化阶段建立 feature branch。
- 资源视图和 descriptor 也由设备创建,符合 D3D12 / Vulkan 风格资源管理直觉。
当前 `RHI` 仍然大量返回裸指针,调用方负责:
但从当前实现成熟度看,也要认识到几个边界:
1. 在使用结束前调用对象自己的 `Shutdown()`
2. 在确认资源不再被引用后显式 `delete`
- 还没有单独的内存分配器 / residency / heap 管理抽象
- 资源创建描述符虽然统一,但很多字段仍是 `uint32_t` 承载枚举值,而不是更强类型的 API。
- `GetNativeDevice()` 表明上层仍可能在必要时下钻到后端实现。
因此文档和代码都不应把这层接口描述成“天然 RAII 安全”的现代封装。它更接近商业引擎底层后端常见的显式生命周期模型
这说明当前阶段的目标更偏“稳定支撑后端实现和测试”,而不是已经完成最终抽象收敛。
## 公开方法
## 测试中体现出的真实用法
现有测试提供了比模板文档更可靠的使用线索:
- 创建设备后先 `Initialize()`
- 查询 `GetCapabilities()``GetDeviceInfo()`
- 创建 `Buffer``Texture``Fence``CommandQueue``CommandList``Sampler` 等对象。
- 对每个对象做基本验证后 `Shutdown()``delete`
这说明 `RHIDevice` 当前不仅是概念入口,而且已经是单元测试和集成测试里的真实生产入口。
## 公共方法
- [Initialize](Initialize.md) - 初始化设备。
- [Shutdown](Shutdown.md) - 关闭设备并释放设备级状态。
- [Initialize](Initialize.md)
- [Shutdown](Shutdown.md)
- [CreateBuffer](CreateBuffer.md)
- [CreateTexture](CreateTexture.md)
- [CreateSwapChain](CreateSwapChain.md)
@@ -184,6 +111,7 @@ virtual RHIBuffer* CreateBuffer(
## 相关文档
- [当前模块](../RHI.md)
- [RHIBuffer](../RHIBuffer/RHIBuffer.md)
- [RHIFactory](../RHIFactory/RHIFactory.md)
- [RHICommandQueue](../RHICommandQueue/RHICommandQueue.md)
- [RHICommandList](../RHICommandList/RHICommandList.md)

View File

@@ -6,24 +6,20 @@
**头文件**: `XCEngine/RHI/RHIPipelineState.h`
**描述**: 抽象图形/计算管线状态对象负责聚合输入布局、光栅化、混合、深度模板、render target 格式以及可选计算 shader
**描述**: 图形/计算管线状态的跨后端抽象,负责收敛固定功能状态、着色器与目标格式,并向具体后端暴露统一的配置接口
## 角色概述
`RHIPipelineState` 对应的是当前抽象层里的“可绑定管线状态包”。它的接口明显带有“统一图形状态描述”的设计意图,而不是简单把某个后端的 native PSO 结构原样暴露出去
`RHIPipelineState` 对应的是“上层渲染系统描述管线状态”的统一入口。它不是简单照抄某个 native API 的 PSO 结构,而是把输入布局、光栅化、混合、深度模板、拓扑、目标格式、多重采样信息以及计算着色器统一收敛到一套接口上
头文件注释里直接写了
这是一种典型的商业引擎设计思路
- `Unity SRP style`
- `Shader independent of PSO`
这说明当前设计方向是希望用一套更统一的 pipeline 状态模型去支撑不同后端,而不是要求所有状态都只通过 shader 或 native API 自己的配置路径表达。
- 渲染器先在统一描述层组织状态。
- 具体后端再决定这些状态是立即创建 native PSO、延迟编译还是只作为运行时状态缓存。
## 当前状态模型
`RHIPipelineState` 当前主要包含两类信息:
### 图形状态
### 图形路径
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
@@ -38,71 +34,43 @@
- [SetComputeShader](SetComputeShader.md)
也就是说,当前同一个抽象接口既能表达 graphics pipeline,也能表达 compute pipeline
其中 `SetSampleCount``SetSampleQuality` 共同表达多重采样配置,目的是让 `GraphicsPipelineDesc` 的采样参数可以完整下传到后端,而不是让上层在描述层就为不同图形 API 分叉
## 类型与有效性
## 为什么要有 SetSampleQuality
这是这页最值得说明的地方。
`sampleQuality` 在显式图形 API 中并不是完全对等的概念:
### `GetType()`
- D3D12 会直接把它映射到 `DXGI_SAMPLE_DESC::Quality`
- Vulkan 当前实现只消费 sample count不消费 quality
- OpenGL 当前实现也没有把 quality 落到真实状态
从现有测试看
但抽象层仍然保留这个接口,原因很明确
- 默认创建出来的 pipeline state 类型是 `PipelineType::Graphics`
- 当设置了 compute shader 后,类型可以变成 `PipelineType::Compute`
- 上层 `GraphicsPipelineDesc` 可以保持完整
- 后端可以渐进式演进,不必修改渲染器对外契约
- 文档能够明确告诉使用者“这个字段在不同后端的成熟度不同”
### `IsValid()` / `EnsureValid()`
这比“为了照顾当前最弱后端,直接把接口删掉”更符合商业级引擎的长期演进方式。
现有测试明确揭示了一个很重要的后端差异:
## 后端语义差异
- D3D12 下,默认或信息不足的 pipeline state 往往不是立即有效的
- 在 OpenGL 路径下,默认 pipeline state 更容易被视为有效
- `D3D12PipelineState` 会真正存储 sample quality并让它进入 PSO 创建与 hash
- `VulkanPipelineState` 暂时把 quality 视为兼容字段,当前实现忽略其值
- `OpenGLPipelineState` 当前同样把它作为占位契约处理。
头文件注释甚至直接写出了当前统一语义:
因此 `RHIPipelineState` 的接口是稳定的,但“字段被消费的深度”仍然是后端相关事实,文档必须把这一点写清楚。
- `D3D12` 需要编译
- `OpenGL` 总是有效
## 生命周期与有效性
这意味着 `RHIPipelineState` 的“有效”不是完全抽象无差异的概念而是和后端实现成熟度、native API 要求直接相关。
`RHIPipelineState` 的“有效”并不保证在所有后端都等价于“native pipeline 已经创建完成”:
## 设计理解
- 有的后端在初始化时就固化 native 对象。
- 有的后端在 `EnsureValid()` 时才推进到真正可绑定状态。
- 还有的后端只是把状态缓存起来,绑定时再逐步写回。
从商用引擎经验看,这样的 pipeline state 抽象是合理的:
这也是为什么文档不能把所有实现都粗暴描述成“标准 PSO”。
- renderer 可以先在统一描述层拼好状态,再交给后端去编译或绑定
- 输入布局、混合、深度模板和 render target 格式等关键状态有明确归属
- 计算路径可以与图形路径复用一部分生命周期和绑定逻辑
但当前实现也有现实边界:
- 还保留了 `RHICommandList::SetShader()` 这种并行路径,所以整个系统并不是“只有 PSO”这一种绑定范式
- `GraphicsPipelineDesc``RHIPipelineState` 的关系更偏工程实现上的状态收敛,而不是已经完全封装成单一材质系统
- 有效性判定明显依赖后端
## 测试体现出的真实使用方式
`tests/RHI/unit/test_pipeline_state.cpp``test_compute.cpp` 给出了当前比较可信的使用路径:
- 可以先创建默认 `GraphicsPipelineDesc` 再逐步设置状态
- 可以读回 `RasterizerDesc``BlendDesc``DepthStencilStateDesc``InputLayoutDesc`
- 可以为 graphics pipeline 直接在 `GraphicsPipelineDesc` 里提供 vertex / fragment shader
- 可以用 `SetComputeShader()` 走 compute 路径
- 在部分后端下 `EnsureValid()` 会触发或推进有效性检查,但并不保证无条件成功
这说明当前 pipeline state 的职责更像“后端可消费的统一状态对象”,而不是文档层面随便说说的概念壳子。
## 生命周期
当前接口提供:
- [Bind](Bind.md)
- [Unbind](Unbind.md)
- [Shutdown](Shutdown.md)
- [GetNativeHandle](GetNativeHandle.md)
通常它由 [RHIDevice](../RHIDevice/RHIDevice.md) 创建,并以裸指针形式返回。使用完成后应显式 `Shutdown()``delete`
## 公共方法
## 公开方法
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
@@ -131,8 +99,10 @@
## 相关文档
- [当前模块](../RHI.md)
- [RHITypes](../RHITypes/RHITypes.md)
- [RHICommandList](../RHICommandList/RHICommandList.md)
- [RHIDevice](../RHIDevice/RHIDevice.md)
- [RHIPipelineLayout](../RHIPipelineLayout/RHIPipelineLayout.md)
- [RHIShader](../RHIShader/RHIShader.md)
- [D3D12PipelineState](../D3D12/D3D12PipelineState/D3D12PipelineState.md)
- [OpenGLPipelineState](../OpenGL/OpenGLPipelineState/OpenGLPipelineState.md)
- [VulkanPipelineState](../Vulkan/VulkanPipelineState/VulkanPipelineState.md)
- [API 总索引](../../../main.md)

View File

@@ -14,17 +14,36 @@ virtual void SetSampleQuality(uint32_t quality) = 0;
## 作用
设置多采样的 quality 维度;与 [SetSampleCount](SetSampleCount.md) 共同表达完整的 multisample 配置。
设置管线状态的多重采样质量级别。它与 [SetSampleCount](SetSampleCount.md) 一起描述最终的 MSAA 配置。
## 当前实现
## 设计意图
- 该声明是纯虚接口,基类不提供实现。
- 各后端当前语义并不完全一致:
- D3D12 会把它写入 `D3D12_SAMPLE_DESC.Quality` 并纳入 pipeline hash。
- Vulkan 当前显式忽略这个参数。
- OpenGL 当前也是空实现。
这个接口的存在不是偶然补丁,而是抽象层契约的一部分:
- `GraphicsPipelineDesc` 可以完整表达 `sampleCount``sampleQuality`
- 后端不需要为了当前实现成熟度不同而迫使上层分叉
- 文档可以明确说明“哪些后端真正消费了该字段,哪些暂时忽略”
这类“先保留稳定契约,再让后端逐步补齐实现”的做法,在商业引擎重构阶段很常见。
## 当前后端语义
- `D3D12PipelineState` 会存储该值,并把它写进 `DXGI_SAMPLE_DESC::Quality`
- `VulkanPipelineState` 当前实现忽略该值
- `OpenGLPipelineState` 当前实现同样忽略该值
因此它不是“所有后端都已有完整效果”的统一语义,而是“所有后端都接受同一描述接口”的统一语义。
## 使用建议
-`SetSampleCount` 配套使用。
- 如果你的上层逻辑需要严格依赖 quality 级别,当前应明确限制在 D3D12 后端验证。
- 不要假设所有后端对该字段都具备同等成熟度。
## 相关文档
- [RHIPipelineState](RHIPipelineState.md)
- [SetSampleCount](SetSampleCount.md)
- [D3D12PipelineState::SetSampleQuality](../D3D12/D3D12PipelineState/SetSampleQuality.md)
- [VulkanPipelineState::SetSampleQuality](../Vulkan/VulkanPipelineState/SetSampleQuality.md)
- [OpenGLPipelineState::SetSampleQuality](../OpenGL/OpenGLPipelineState/SetSampleQuality.md)

View File

@@ -1,19 +1,38 @@
# VulkanPipelineState::SetSampleQuality
**命名空间**: `XCEngine::RHI`
**类型**: `method`
**头文件**: `XCEngine/RHI/Vulkan/VulkanPipelineState.h`
## 签名
```cpp
void SetSampleQuality(uint32_t quality) override;
```
## 作用
保留与抽象层一致的 sample quality 入口
接受抽象层传入的 sample quality 参数
## 当前实现行为
## 当前实现
- 当前实现显式 `(void)quality`也就是直接忽略这个参数
- Vulkan 后端当前只把 [SetSampleCount](SetSampleCount.md) 写入 `VkSampleCountFlagBits` 路径,没有额外的 sample quality 映射。
当前实现不会保存或消费这个值,函数体只有 `(void)quality`也就是Vulkan 后端当前把它视为接口兼容字段,而不是创建 `VkPipeline` 的真实输入
## 为什么接口仍然保留
- `RHIPipelineState` 需要保持统一的跨后端契约
- 上层 `GraphicsPipelineDesc` 不必因为某个后端暂未使用该字段就裁掉描述能力
- 后续如果 Vulkan 路径需要引入更细的采样策略,不必再改外层 API
## 使用建议
- 需要真实控制 Vulkan 多重采样时,应优先关注 [SetSampleCount](SetSampleCount.md)
- 不要假设 quality 值当前会影响 Vulkan pipeline 创建结果
## 相关文档
- [VulkanPipelineState](VulkanPipelineState.md)
- [RHIPipelineState::SetSampleQuality](../../RHIPipelineState/SetSampleQuality.md)
- [SetSampleCount](SetSampleCount.md)

View File

@@ -6,159 +6,66 @@
**头文件**: `XCEngine/RHI/Vulkan/VulkanPipelineState.h`
**描述**: Vulkan 后端`RHIPipelineState` 实现,负责创建 graphics / compute pipeline并保存与之相关的 layout、render target 格式和固定功能状态
**描述**: Vulkan 后端`RHIPipelineState` 实现,负责收敛图形/计算管线描述,并在需要时创建 `VkPipeline`
## 概览
## 角色概述
`VulkanPipelineState` 是当前 Vulkan 后端里 draw / dispatch 能否真正执行的关键对象
`VulkanPipelineState` 是当前 Vulkan 路径里最核心的可绑定管线对象之一。它保存统一的管线状态描述,并在图形或计算路径上创建对应的 native pipeline
它承担两类职责
和显式 API 的常见商业引擎设计一样,它试图把高层的统一描述与底层 Vulkan 创建过程隔离开
- 保存抽象层的 pipeline 描述,例如输入布局、光栅化、混合、深度模板、拓扑和 render target 格式
- 在合适时机创建原生 `VkPipeline`
- 高层只关心输入布局、固定功能状态、shader 与目标格式
- 后端负责决定何时创建 `VkPipeline`、如何组织 pipeline layout以及哪些能力当前已经成熟
但它不是一个完全成熟、完全统一的 PSO 系统。当前 graphics 和 compute 路径的成熟度并不一致,这一点文档必须明确写出来。
## 当前实现要点
## 生命周期
### 图形与计算路径成熟度不同
常见用法有两种:
- `Initialize(...)` 面向图形管线配置
- 计算管线依赖 `SetComputeShader()``EnsureValid()` 延迟创建
- `Bind()` / `Unbind()` 当前仍然是轻量或空语义
### 图形管线
所以它虽然叫 `PipelineState`,但目前仍然是“图形优先、计算补齐”的工程状态。
1. 构造 `VulkanPipelineState`
2. `Initialize(VulkanDevice*, const GraphicsPipelineDesc&)`
3. 录制阶段把它传给 [VulkanCommandList](../VulkanCommandList/VulkanCommandList.md) 的 `SetPipelineState()`
4. 不再使用时 `Shutdown()`
### SetSampleQuality 的当前语义
### 计算管线
`SetSampleQuality` 已经进入接口,但 Vulkan 后端目前显式忽略该值:
1. 初始化 pipeline state让它至少拥有 pipeline layout
2. `SetComputeShader(RHIShader*)`
3. 在录制 `Dispatch()` 前由 `EnsureValid()` 延迟创建 compute pipeline
- `SetSampleCount` 会真正保存并影响后续创建
- `SetSampleQuality` 只是接受参数后 `(void)quality`
## 当前实现的真实行为
这说明抽象层契约已经对齐,但 Vulkan 实现当前只消费多重采样中的 `count`,还没有引入独立的 quality 概念。
### graphics 与 compute 的差异
### 有效性与 native 对象
按当前实现:
`IsValid()` 更接近“对象配置可用”,而不总是严格等价于“`VkPipeline` 一定已经存在”。尤其在计算路径上,真正的 pipeline 可能要等到 `EnsureValid()` 之后才创建。
- `Initialize()` 面向的是 `GraphicsPipelineDesc`
- 如果 `vertexShader``fragmentShader` 都没有有效 payload`Initialize()` 仍可能把对象标记为 `m_isConfigured = true`
- graphics 路径要求:
- `renderTargetCount == 1`
- 第一项 render target format 有效
- vertex shader 和 fragment shader 都存在
- compute 路径不是在 `Initialize()` 里直接创建,而是走 `SetComputeShader()` + `EnsureValid()` + `CreateComputePipeline()`
## 为什么文档必须写清这个差异
因此当前类名虽然叫 `PipelineState`,但本质上是“图形优先、计算补充”的实现。
在商业引擎里,统一接口和后端成熟度不同步是正常现象。真正危险的不是接口暂时不对等,而是文档把它们写成“都一样”。把这种差异明确写出来,调用者才能知道:
### Pipeline Layout
`EnsurePipelineLayout(desc)` 有两种行为:
- 如果 `desc.pipelineLayout` 非空,直接借用外部 `VulkanPipelineLayout`
- 否则创建一个空的 `VkPipelineLayout`
这解释了为什么 compute 路径能先有 pipeline layout再延迟绑定 compute shader。
### Graphics Pipeline 创建
`CreateGraphicsPipeline()` 当前会:
-`CompileVulkanShader(...)``desc.vertexShader` / `desc.fragmentShader` 编译成 SPIR-V
- 临时创建两个 `VkShaderModule`
- 根据 render target / depth format 构造一个内部 `VkRenderPass`
- 再据此创建 graphics pipeline
需要注意的是,当前这个 graphics pipeline 会自己生成一个内部 `VkRenderPass`。这和一些成熟引擎里把 render pass / framebuffer compatibility 单独抽出来统一管理的做法相比,还处于更简单直接的阶段。
### Compute Pipeline 创建
`CreateComputePipeline()` 当前要求:
- `m_device` 有效
- `m_pipelineLayout` 有效
- `m_computeShader` 非空
- `m_computeShader` 实际上是有效的 `VulkanShader`
创建时会从 `VulkanShader` 里拿 `VkShaderModule` 与 entry point然后调用 `vkCreateComputePipelines()`
### `IsValid()` 与真实 native pipeline
这是当前实现里最容易误解的一点:
- `IsValid()` 返回的是 `m_isConfigured`
- 它不等价于“`m_pipeline != VK_NULL_HANDLE` 一定成立”
尤其在 compute 路径里pipeline 可能要等 `EnsureValid()` 之后才真正创建。因此把 `IsValid()` 理解成“配置状态可用”更准确,而不是“原生对象已经存在”。
### 其他行为
- `Bind()` / `Unbind()` 当前都是 no-op
- `GetHash()` 目前只用了拓扑和第一个 render target format 做轻量 hash
- `SetSampleQuality()` 当前显式忽略 `quality` 参数,只保留接口对齐
- `HasDepthStencilAttachment()` 只按 `m_depthStencilFormat` 是否为 `Unknown` 判断
## 线程语义
当前实现没有内部锁。更安全的工程约束是:
- pipeline state 构建与修改在单线程完成
- 构建完成后再交给录制线程使用
- 不要在录制过程中并发修改同一个 pipeline state
## 所有权与资源管理
- `VulkanPipelineState` 持有 `VkPipeline`
- 可能持有自建的 `VkPipelineLayout`
- graphics 路径还会持有内部创建的 `VkRenderPass`
- 如果借用了外部 `VulkanPipelineLayout`,则不会在自身 `Shutdown()` 中销毁它
## 设计取向
当前实现非常典型地体现了“先让 pipeline 概念在跨后端 RHI 里工作起来,再逐步收敛成更严谨的 Vulkan 语义”:
- 优点是 graphics / compute 基本链路已经能跑
- 代价是状态有效性、render pass 归属和 hash 策略仍较简化
这类设计在商业引擎重构早期很常见。真正成熟后,通常会继续把 pipeline cache、render pass 兼容性、shader reflection 和 descriptor layout 规则再往前推一层。
## 当前限制
- graphics 路径当前只支持一个 render target
- compute pipeline 为延迟创建模型
- `IsValid()` 不严格等价于 native pipeline 已创建
- `Bind()` / `Unbind()` 无实际行为
- `GetHash()` 很简化,不能视为完整 pipeline cache key
- 哪些字段已经是可靠后端语义
- 哪些字段目前只是为了接口稳定性保留
- 何时需要在特定后端做额外验证
## 主要公开方法
- `bool Initialize(VulkanDevice* device, const GraphicsPipelineDesc& desc)`
- `void SetInputLayout(const InputLayoutDesc& layout)`
- `void SetRasterizerState(const RasterizerDesc& state)`
- `void SetBlendState(const BlendDesc& state)`
- `void SetDepthStencilState(const DepthStencilStateDesc& state)`
- `void SetTopology(uint32_t topologyType)`
- `void SetRenderTargetFormats(uint32_t count, const uint32_t* formats, uint32_t depthFormat)`
- `void SetSampleCount(uint32_t count)`
- `void SetSampleQuality(uint32_t quality)`
- `void SetComputeShader(RHIShader* shader)`
- `PipelineStateHash GetHash() const`
- `RHIShader* GetComputeShader() const`
- `bool HasComputeShader() const`
- `bool IsValid() const`
- `void EnsureValid()`
- `void Shutdown()`
- `void Bind()`
- `void Unbind()`
- `void* GetNativeHandle()`
- `PipelineType GetType() const`
## 相关测试与使用线索
- `tests/RHI/unit/test_vulkan_graphics.cpp` 覆盖了 Vulkan graphics pipeline 与 compute dispatch 的真实创建路径
- `tests/RHI/unit/test_compute.cpp` 会把 compute shader、pipeline layout 和 descriptor set 接起来验证计算路径
- `tests/RHI/unit/test_command_list.cpp` 会通过抽象命令列表接口驱动 pipeline 绑定与 dispatch
- [Initialize](Initialize.md)
- [SetInputLayout](SetInputLayout.md)
- [SetRasterizerState](SetRasterizerState.md)
- [SetBlendState](SetBlendState.md)
- [SetDepthStencilState](SetDepthStencilState.md)
- [SetTopology](SetTopology.md)
- [SetRenderTargetFormats](SetRenderTargetFormats.md)
- [SetSampleCount](SetSampleCount.md)
- [SetSampleQuality](SetSampleQuality.md)
- [SetComputeShader](SetComputeShader.md)
- [GetHash](GetHash.md)
- [HasComputeShader](HasComputeShader.md)
- [IsValid](IsValid.md)
- [EnsureValid](EnsureValid.md)
- [Shutdown](Shutdown.md)
- [GetNativeHandle](GetNativeHandle.md)
## 相关文档
@@ -166,5 +73,5 @@
- [VulkanShader](../VulkanShader/VulkanShader.md)
- [VulkanPipelineLayout](../VulkanPipelineLayout/VulkanPipelineLayout.md)
- [VulkanCommandList](../VulkanCommandList/VulkanCommandList.md)
- [VulkanRenderPass](../VulkanRenderPass/VulkanRenderPass.md)
- [RHIPipelineState](../../RHIPipelineState/RHIPipelineState.md)
- [API 总索引](../../../../main.md)

View File

@@ -1,13 +1,13 @@
# API 文档重构状态
**生成时间**: `2026-04-10 17:36:16`
**生成时间**: `2026-04-10 17:58:41`
**来源**: `docs/api/_tools/audit_api_docs.py`
## 摘要
- Markdown 页面数(全部): `3969`
- Markdown 页面数canonical: `3941`
- Markdown 页面数(全部): `3973`
- Markdown 页面数canonical: `3945`
- Public headers 数: `384`
- `XCEditor` public headers 数: `64`canonical 已覆盖 `64`
- `XCEngine` public headers 数: `320`canonical 已覆盖 `320`
@@ -83,8 +83,8 @@
| 字段 | 页面数 |
|------|--------|
| `命名空间` | `2563` |
| `类型` | `2563` |
| `命名空间` | `2567` |
| `类型` | `2567` |
| `描述` | `617` |
| `头文件` | `1968` |
| `头文件` | `1972` |
| `源文件` | `520` |