From 85b8b3e58301222cddee0b2ea55a8dde08e5f87f Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 10 Apr 2026 17:59:59 +0800 Subject: [PATCH] docs(rhi): sync buffer init and sample quality docs --- .../RHI/D3D12/D3D12Device/CreateBuffer.md | 59 +++++- .../RHI/D3D12/D3D12Device/D3D12Device.md | 95 +++++----- .../D3D12PipelineState/D3D12PipelineState.md | 105 ++++++----- .../D3D12PipelineState/SetSampleQuality.md | 16 +- .../OpenGLPipelineState.md | 66 +++---- .../OpenGLPipelineState/SetSampleQuality.md | 25 ++- .../XCEngine/RHI/RHIDevice/CreateBuffer.md | 46 +++-- docs/api/XCEngine/RHI/RHIDevice/RHIDevice.md | 146 ++++----------- .../RHI/RHIPipelineState/RHIPipelineState.md | 96 ++++------ .../RHI/RHIPipelineState/SetSampleQuality.md | 33 +++- .../VulkanPipelineState/SetSampleQuality.md | 27 ++- .../VulkanPipelineState.md | 175 ++++-------------- docs/api/_meta/rebuild-status.md | 12 +- 13 files changed, 418 insertions(+), 483 deletions(-) diff --git a/docs/api/XCEngine/RHI/D3D12/D3D12Device/CreateBuffer.md b/docs/api/XCEngine/RHI/D3D12/D3D12Device/CreateBuffer.md index a619c03a..a7756e27 100644 --- a/docs/api/XCEngine/RHI/D3D12/D3D12Device/CreateBuffer.md +++ b/docs/api/XCEngine/RHI/D3D12/D3D12Device/CreateBuffer.md @@ -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) diff --git a/docs/api/XCEngine/RHI/D3D12/D3D12Device/D3D12Device.md b/docs/api/XCEngine/RHI/D3D12/D3D12Device/D3D12Device.md index 8fa846a4..6afa6462 100644 --- a/docs/api/XCEngine/RHI/D3D12/D3D12Device/D3D12Device.md +++ b/docs/api/XCEngine/RHI/D3D12/D3D12Device/D3D12Device.md @@ -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) diff --git a/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/D3D12PipelineState.md b/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/D3D12PipelineState.md index 763f9844..5ed59aff 100644 --- a/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/D3D12PipelineState.md +++ b/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/D3D12PipelineState.md @@ -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) diff --git a/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/SetSampleQuality.md b/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/SetSampleQuality.md index 7369078c..7367fa4e 100644 --- a/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/SetSampleQuality.md +++ b/docs/api/XCEngine/RHI/D3D12/D3D12PipelineState/SetSampleQuality.md @@ -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) diff --git a/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/OpenGLPipelineState.md b/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/OpenGLPipelineState.md index 7e3cb53f..f833e665 100644 --- a/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/OpenGLPipelineState.md +++ b/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/OpenGLPipelineState.md @@ -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) diff --git a/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/SetSampleQuality.md b/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/SetSampleQuality.md index 68ee1993..156b6f52 100644 --- a/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/SetSampleQuality.md +++ b/docs/api/XCEngine/RHI/OpenGL/OpenGLPipelineState/SetSampleQuality.md @@ -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) diff --git a/docs/api/XCEngine/RHI/RHIDevice/CreateBuffer.md b/docs/api/XCEngine/RHI/RHIDevice/CreateBuffer.md index c254b323..2628bbe1 100644 --- a/docs/api/XCEngine/RHI/RHIDevice/CreateBuffer.md +++ b/docs/api/XCEngine/RHI/RHIDevice/CreateBuffer.md @@ -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) diff --git a/docs/api/XCEngine/RHI/RHIDevice/RHIDevice.md b/docs/api/XCEngine/RHI/RHIDevice/RHIDevice.md index e92496a9..fa12800f 100644 --- a/docs/api/XCEngine/RHI/RHIDevice/RHIDevice.md +++ b/docs/api/XCEngine/RHI/RHIDevice/RHIDevice.md @@ -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) diff --git a/docs/api/XCEngine/RHI/RHIPipelineState/RHIPipelineState.md b/docs/api/XCEngine/RHI/RHIPipelineState/RHIPipelineState.md index b9f972f4..a8db1332 100644 --- a/docs/api/XCEngine/RHI/RHIPipelineState/RHIPipelineState.md +++ b/docs/api/XCEngine/RHI/RHIPipelineState/RHIPipelineState.md @@ -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) diff --git a/docs/api/XCEngine/RHI/RHIPipelineState/SetSampleQuality.md b/docs/api/XCEngine/RHI/RHIPipelineState/SetSampleQuality.md index ff65932f..a9c5dd9d 100644 --- a/docs/api/XCEngine/RHI/RHIPipelineState/SetSampleQuality.md +++ b/docs/api/XCEngine/RHI/RHIPipelineState/SetSampleQuality.md @@ -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) diff --git a/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/SetSampleQuality.md b/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/SetSampleQuality.md index 6ec664a2..9b4a8065 100644 --- a/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/SetSampleQuality.md +++ b/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/SetSampleQuality.md @@ -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) diff --git a/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/VulkanPipelineState.md b/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/VulkanPipelineState.md index 29621645..febad737 100644 --- a/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/VulkanPipelineState.md +++ b/docs/api/XCEngine/RHI/Vulkan/VulkanPipelineState/VulkanPipelineState.md @@ -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) diff --git a/docs/api/_meta/rebuild-status.md b/docs/api/_meta/rebuild-status.md index d3508da1..2b94ce40 100644 --- a/docs/api/_meta/rebuild-status.md +++ b/docs/api/_meta/rebuild-status.md @@ -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` |