Files
XCEngine/RHI_Design_Issues.md
ssdfasd dc970d215b RHI: Internalize OpenGL-specific methods in OpenGLDevice
- Remove GetTextureUnitAllocator/GetUniformBufferManager from public interface
- Remove SwapBuffers from public interface (friend OpenGLSwapChain can still access)
- Add MakeContextCurrent() and GetNativeContext() public methods
- Update integration tests to use MakeContextCurrent() instead of wglMakeCurrent
- Update RenderDoc calls to use GetNativeContext() instead of GetGLContext()
- Remove Device_SwapBuffers_NoErrors test (SwapBuffers no longer public)
- Mark Priority 7 as completed in RHI_Design_Issues.md
2026-03-25 01:20:38 +08:00

610 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RHI 模块设计问题分析报告(第二版)
## 1. 项目背景
本项目 RHI 模块参考 Unity 渲染架构设计,面向 **Direct3D 12****Vulkan** 等现代图形 API目标是为引擎上层SRP/RenderGraph提供统一的渲染硬件抽象层屏蔽 API 差异,实现跨后端移植。
当前已实现两个后端D3D12 和 OpenGL。基础框架已有雏形Reset/Close、TransitionBarrier、PipelineState但存在**被 OpenGL 风格带偏、不符合现代 D3D12/Vulkan 显式设计**的关键问题。
---
## 2. 做得好的地方
1. **有显式生命周期管理**`Reset()` / `Close()` 是 D3D12/Vulkan 风格,理解"录制-提交"的分离
2. **考虑了资源状态转换**`TransitionBarrier()` 是现代 API 必须的,预留了接口
3. **覆盖了图形+计算**`Draw` / `DrawIndexed` / `Dispatch` 都有,功能完整
4. **预留了原生句柄**`GetNativeHandle()` 方便调试和特殊场景扩展
---
## 3. 核心问题(按严重程度排序)
### 3.1 最大的坑:字符串查找 SetUniform 是 OpenGL 风格
**问题描述**:当前接口使用字符串名称设置 Uniform/Texture
```cpp
virtual void SetUniformInt(const char* name, int value) = 0;
virtual void SetGlobalTexture(const char* name, RHIResourceView* texture) = 0;
```
这是**典型的 OpenGL 立即模式风格**,问题极大:
| 问题 | 说明 |
|------|------|
| **性能差** | 运行时通过字符串查找 uniform 位置,驱动开销大 |
| **无法多线程** | 字符串查找不是线程安全的,不利于多线程录制 CommandList |
| **接 Vulkan 不可能** | D3D12/Vulkan 用 **DescriptorSet + PipelineLayout**,根本没有"按名字设 uniform"的概念 |
**根本原因**:这是 OpenGL 风格的设计,没有对齐 D3D12/Vulkan 的显式 Descriptor 绑定模型。
**D3D12/Vulkan 正确做法**
```cpp
// D3D12: 通过 Root Signature + Descriptor Table 绑定
commandList->SetGraphicsRootDescriptorTable(rootIndex, gpuDescriptorHandle);
// Vulkan: 通过 DescriptorSet 绑定
vkCmdBindDescriptorSets(commandBuffer, ..., descriptorSet, ...);
```
**修改建议**
```cpp
// 新增DescriptorSet 绑定接口(替代 SetUniform/SetGlobal
virtual void SetGraphicsDescriptorSets(
uint32_t firstSet,
uint32_t count,
RHIDescriptorSet** descriptorSets,
RHIPipelineLayout* pipelineLayout) = 0;
virtual void SetComputeDescriptorSets(
uint32_t firstSet,
uint32_t count,
RHIDescriptorSet** descriptorSets,
RHIPipelineLayout* pipelineLayout) = 0;
```
---
### 3.1.1 ✅ 已完成DescriptorSet 抽象
**实现状态**2026-03-25 已完成
**新增抽象**
| 类名 | 说明 |
|------|------|
| `RHIDescriptorSet` | DescriptorSet 基类,含 Update/UpdateSampler/GetNativeHandle/GetBindingCount/GetBindings |
| `RHIDescriptorPool` | DescriptorPool 基类,含 AllocateSet/FreeSet |
| `D3D12DescriptorSet` | D3D12 实现,使用 DescriptorHeap 分配 |
| `D3D12DescriptorHeap` | 扩展支持 AllocateSet/FreeSet |
| `OpenGLDescriptorSet` | OpenGL 实现,使用 TextureUnitAllocator |
| `OpenGLDescriptorPool` | OpenGL 实现 |
**RHICommandList 新增接口**
```cpp
virtual void SetGraphicsDescriptorSets(
uint32_t firstSet,
uint32_t count,
RHIDescriptorSet** descriptorSets,
RHIPipelineLayout* pipelineLayout) = 0;
virtual void SetComputeDescriptorSets(
uint32_t firstSet,
uint32_t count,
RHIDescriptorSet** descriptorSets,
RHIPipelineLayout* pipelineLayout) = 0;
```
**RHIDevice 新增工厂方法**
```cpp
virtual RHIDescriptorPool* CreateDescriptorPool(const DescriptorPoolDesc& desc) = 0;
virtual RHIDescriptorSet* CreateDescriptorSet(RHIDescriptorPool* pool, const DescriptorSetDesc& desc) = 0;
```
**实现说明**
- D3D12使用 `D3D12DescriptorHeap` 的 GPU/CPU descriptor 分配
- OpenGL使用 `TextureUnitAllocator` 分配 texture unit
- 旧的 `SetUniform*/SetGlobal*` 仍保留,向后兼容
---
### 3.2 缺少显式 RenderPass
**问题描述**:当前只有 `SetRenderTargets()`,没有 `BeginRenderPass()` / `EndRenderPass()`
```cpp
// 当前设计
virtual void SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, ...) = 0;
virtual void ClearRenderTarget(RHIResourceView* renderTarget, const float color[4]) = 0;
```
这是**不符合现代 API 设计**的:
| 问题 | 说明 |
|------|------|
| **不符合显式设计** | D3D12/Vulkan 要求显式定义渲染通道的开始和结束 |
| **清屏逻辑不规范** | 现代 API 推荐用 RenderPass 的 `LoadOp`Clear/Load/DontCare清屏 |
| **Tile GPU 性能差** | 对移动端 Tile GPU 至关重要,隐式行为无法优化 |
**根本原因**OpenGL 的 `glClear()` 是立即模式,没有 RenderPass 概念。
**D3D12/Vulkan 正确做法**
```cpp
// D3D12: Begin/End Render Pass
commandList->BeginRenderPass(...);
commandList->EndRenderPass();
// Vulkan: vkCmdBeginRenderPass / vkCmdEndRenderPass
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, ...);
vkCmdEndRenderPass(commandBuffer);
```
**修改建议**
```cpp
// 新增:显式渲染通道
virtual void BeginRenderPass(
RHIRenderPass* renderPass,
RHIFramebuffer* framebuffer,
const Rect& renderArea,
uint32_t clearValueCount,
const ClearValue* clearValues) = 0;
virtual void EndRenderPass() = 0;
// 移除 SetRenderTargets改由 Framebuffer 定义
// 移除或废弃 ClearRenderTarget改为 BeginRenderPass 的 LoadOp
```
### 3.2.1 ✅ 已完成:显式 RenderPass 实现
**实现状态**2026-03-24 已完成
**新增抽象**
| 类名 | 说明 |
|------|------|
| `RHIFramebuffer` | Framebuffer 基类,含 Initialize/Bind/GetHandle |
| `RHIRenderPass` | RenderPass 基类,含 AttachmentDesc |
| `D3D12Framebuffer` | D3D12 Framebuffer 实现 |
| `D3D12RenderPass` | D3D12 RenderPass 实现(使用 OMSetRenderTargets |
| `OpenGLFramebuffer` | OpenGL 实现(继承自 RHIFramebuffer |
| `OpenGLRenderPass` | OpenGL RenderPass 实现 |
**RHICommandList 新增接口**
```cpp
virtual void BeginRenderPass(RHIRenderPass* renderPass, RHIFramebuffer* framebuffer,
const Rect& renderArea, uint32_t clearValueCount,
const ClearValue* clearValues) = 0;
virtual void EndRenderPass() = 0;
```
**AttachmentDesc 结构**(定义在 RHIRenderPass.h
```cpp
struct AttachmentDesc {
Format format = Format::Unknown;
LoadAction loadOp = LoadAction::Undefined; // Undefined/Load/Clear
StoreAction storeOp = StoreAction::Store; // Store/Resolve/Discard
LoadAction stencilLoadOp = LoadAction::Undefined;
StoreAction stencilStoreOp = StoreAction::Undefined;
ClearValue clearValue;
};
```
**实现说明**
- D3D12使用 `OMSetRenderTargets` + `ClearRenderTargetView`/`ClearDepthStencilView`
- OpenGL使用 `glBindFramebuffer` + `glClearBufferfv` 处理 LoadOp
- 旧的 `SetRenderTargets`/`ClearRenderTarget` 仍保留,向后兼容
---
### 3.3 动态状态太多,应该收敛到 PipelineState
**问题描述**:当前把 DepthStencilState、BlendState、PrimitiveTopology 都放在 CommandList 里动态设置:
```cpp
virtual void SetDepthStencilState(const DepthStencilState& state) = 0;
virtual void SetBlendState(const BlendState& state) = 0;
virtual void SetPrimitiveTopology(PrimitiveTopology topology) = 0;
```
这是**OpenGL 风格的残留**
| 问题 | 说明 |
|------|------|
| **驱动开销大** | 现代 API 中这些状态大部分是 PipelineState 的一部分,是不可变的 |
| **不符合显式设计** | 动态状态应该只保留极少数Viewport/Scissor/StencilRef/BlendFactor |
| **状态不一致风险** | CommandList 动态设置的状态可能与 PSO 中定义的状态冲突 |
**根本原因**OpenGL 是状态机可以在任何时候设置任何状态。D3D12/Vulkan 把大部分状态打包到 PSO 中。
**D3D12/Vulkan 正确做法**
- `DepthStencilState``BlendState``PrimitiveTopology`**PSO 创建时就确定**
- CommandList 只设置**真正的动态状态**Viewport、Scissor、StencilRef、BlendFactor
**修改建议**
```cpp
// PipelineState 应该包含:
// - Shader
// - VertexLayout
// - RenderPass 兼容
// - DepthStencilState不可变
// - BlendState不可变
// - PrimitiveTopology不可变
// - 动态状态掩码
// CommandList 只保留必要的动态状态:
virtual void SetViewports(uint32_t count, const Viewport* viewports) = 0;
virtual void SetScissorRects(uint32_t count, const Rect* rects) = 0;
virtual void SetStencilRef(uint8_t ref) = 0;
virtual void SetBlendFactor(const float factor[4]) = 0;
// 移除:
virtual void SetDepthStencilState(const DepthStencilState& state) = 0; // 移到 PSO
virtual void SetBlendState(const BlendState& state) = 0; // 移到 PSO
virtual void SetPrimitiveTopology(PrimitiveTopology topology) = 0; // 移到 PSO
```
### 3.3.1 ✅ 已完成:动态状态移至 PSO
**实现状态**2026-03-25 已完成
**移除的无效方法**
| 方法 | 问题 |
|------|------|
| `SetDepthStencilState` | D3D12 中是空实现TODO调用无效OpenGL 中直接调用 GL 函数但状态应通过 PSO 配置 |
| `SetBlendState` | D3D12 中只设置 BlendFactor其他参数被忽略OpenGL 中直接调用 GL 函数但状态应通过 PSO 配置 |
**保留的真正动态状态**
| 方法 | 说明 |
|------|------|
| `SetViewport/SetViewports` | 真正的动态状态 |
| `SetScissorRect/SetScissorRects` | 真正的动态状态 |
| `SetStencilRef` | D3D12 动态状态 |
| `SetBlendFactor` | D3D12 动态状态 |
**修改的文件**
- `RHICommandList.h` - 移除 SetDepthStencilState/SetBlendState 纯虚方法
- `D3D12CommandList.h/cpp` - 移除 SetDepthStencilState/SetBlendState 实现
- `OpenGLCommandList.h/cpp` - 移除 SetDepthStencilState/SetBlendState 实现
- `tests/RHI/unit/test_command_list.cpp` - 移除相关测试
- `tests/RHI/OpenGL/unit/test_command_list.cpp` - 移除相关测试
**说明**
- D3D12 中 PSO 是不可变的SetDepthStencilState/SetBlendState 调用原本就是无效代码
- OpenGL 中状态通过 OpenGLPipelineState::Apply() 在绑定 PSO 时应用
- PrimitiveTopology 保留在 CommandList因为 D3D12 允许动态改变 topology type
---
### 3.4 ResourceView 类型不明确 ✅ 基本完成
**问题描述**:当前用 `RHIResourceView*` 代表所有视图:
```cpp
virtual void SetVertexBuffer(uint32_t slot, RHIResourceView* buffer, ...) = 0;
virtual void SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, ...) = 0;
```
这会导致:
| 问题 | 说明 |
|------|------|
| **类型不安全** | 容易把 SRV 当成 RTV 传,运行时才发现错误 |
| **后端处理麻烦** | D3D12/Vulkan 对不同视图有严格区分RTV/DSV/SRV/UAV/VBV/IBV |
| **语义模糊** | `RHIResourceView` 到底是哪种视图?调用者必须查文档 |
**根本原因**:没有在类型层面区分不同视图的语义。
**修改建议**:定义明确的视图类型层次结构
```cpp
// 基类
class RHIResourceView {};
// 具体视图类型
class RHIRenderTargetView : public RHIResourceView {};
class RHIDepthStencilView : public RHIResourceView {};
class RHIShaderResourceView : public RHIResourceView {};
class RHIUnorderedAccessView : public RHIResourceView {};
class RHIVertexBufferView : public RHIResourceView {};
class RHIIndexBufferView : public RHIResourceView {};
class RHIConstantBufferView : public RHIResourceView {};
```
**实现状态**:✅ 基本完成 - `RHIResourceView.h` 中定义了具体视图类型,但 `RHICommandList` 仍使用 `RHIResourceView*` 以保持兼容性
---
### 3.5 TransitionBarrier 针对 View 而非 Resource ✅ 已完成
**问题描述**:当前接口:
```cpp
virtual void TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) = 0;
```
资源状态转换是针对 **ResourceBuffer/Texture 本身)** 的,不是针对 View 的。一张 Texture 可能创建多个 ViewSRV、RTV、DSV但状态转换是对整个 Texture 做的。
**根本原因**:没有区分 Resource 和 ResourceView 的概念。
**修改建议**
```cpp
// 新增 Resource 基类
class RHIResource {};
class RHIBuffer : public RHIResource {};
class RHITexture : public RHIResource {};
// 修改 TransitionBarrier
struct ResourceBarrier {
RHIResource* resource;
ResourceStates stateBefore;
ResourceStates stateAfter;
uint32_t subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
};
virtual void ResourceBarrier(uint32_t count, const ResourceBarrier* barriers) = 0;
```
**实现状态**:✅ 已完成 - `RHIResource.h` 已创建,`RHIBuffer``RHITexture` 已继承自 `RHIResource`
**问题描述**:当前接口:
```cpp
virtual void TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) = 0;
```
资源状态转换是针对 **ResourceBuffer/Texture 本身)** 的,不是针对 View 的。一张 Texture 可能创建多个 ViewSRV、RTV、DSV但状态转换是对整个 Texture 做的。
**根本原因**:没有区分 Resource 和 ResourceView 的概念。
**修改建议**
```cpp
// 新增 Resource 基类
class RHIResource {};
class RHIBuffer : public RHIResource {};
class RHITexture : public RHIResource {};
// 修改 TransitionBarrier
struct ResourceBarrier {
RHIResource* resource;
ResourceStates stateBefore;
ResourceStates stateAfter;
uint32_t subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
};
virtual void ResourceBarrier(uint32_t count, const ResourceBarrier* barriers) = 0;
```
---
## 4. 其他需要修改的问题
### 4.1 SetGlobal* 是空操作 ✅ 已完成
**问题**SetGlobal* 方法SetGlobalInt/SetGlobalFloat/SetGlobalVec3/SetGlobalVec4/SetGlobalMat4/SetGlobalTexture在 D3D12 和 OpenGL 中只是缓存值,从不提交到 GPU。
**实现状态**2026-03-25 已完成 - 移除所有 SetGlobal* 方法
**移除的方法**共6个
| 方法 | 问题 |
|------|------|
| `SetGlobalInt` | 只缓存到 unordered_map从未提交到 GPU |
| `SetGlobalFloat` | 只缓存到 unordered_map从未提交到 GPU |
| `SetGlobalVec3` | 只缓存到 unordered_map从未提交到 GPU |
| `SetGlobalVec4` | 只缓存到 unordered_map从未提交到 GPU |
| `SetGlobalMat4` | 只缓存到 unordered_map从未提交到 GPU |
| `SetGlobalTexture` | 只缓存到 unordered_map从未提交到 GPU |
**移除的缓存成员变量**
- D3D12CommandList: `m_globalIntCache`, `m_globalFloatCache`, `m_globalVec3Cache`, `m_globalVec4Cache`, `m_globalMat4Cache`, `m_globalTextureCache`
- OpenGLCommandList: 同上
**说明**
- `SetGlobal*` 从未被代码库中任何地方调用(死代码)
- `SetUniform*` 方法已正常工作,使用 shader reflection + 实际 GPU 绑定
- 移除后无功能损失
### 4.2 PrimitiveType 和 PrimitiveTopology 冲突
详见第一版文档,此处不再赘述。
### 4.3 OpenGL 特有方法暴露 ✅ 已完成
**实现状态**2026-03-25 已完成
**问题**OpenGLDevice 和 OpenGLCommandList 暴露了 OpenGL 特有方法,违反"上层只调用抽象接口"原则。
**修复内容**
1. **OpenGLDevice 内部化方法**
- 移除 `GetTextureUnitAllocator()``GetUniformBufferManager()` 公开访问(移到 private
- 移除 `SwapBuffers()` 公开方法OpenGLSwapChain 作为 friend 仍可访问)
- `GetPresentationDC()``GetGLContext()` 保留在 private通过 friend 访问
2. **新增抽象接口**
- `MakeContextCurrent()` - 封装 `wglMakeCurrent` 操作
- `GetNativeContext()` - 返回 GL Context 供 RenderDoc 使用
3. **保留的逃生舱方法**
- OpenGLCommandList 中的 `PrimitiveType` 相关方法是显式的 OpenGL 逃生舱
- 这些方法使用 OpenGL 特有类型,文档化为"backend-specific escape hatch"
- 抽象层正确使用 `PrimitiveTopology` 枚举
**修改的文件**
- `engine/include/XCEngine/RHI/OpenGL/OpenGLDevice.h` - 内部化特有方法,新增 MakeContextCurrent/GetNativeContext
- `engine/src/RHI/OpenGL/OpenGLDevice.cpp` - 实现 MakeContextCurrent
- `tests/RHI/OpenGL/unit/test_device.cpp` - 移除 SwapBuffers 测试
- `tests/RHI/OpenGL/integration/minimal/main.cpp` - 使用 MakeContextCurrent
- `tests/RHI/OpenGL/integration/triangle/main.cpp` - 使用 MakeContextCurrent
- `tests/RHI/OpenGL/integration/quad/main.cpp` - 使用 MakeContextCurrent
- `tests/RHI/OpenGL/integration/sphere/main.cpp` - 使用 MakeContextCurrent
**说明**
- `PrimitiveType` 枚举在 OpenGL 专用逃生舱方法中使用是可接受的,因为这些是显式的后端特定接口
- 抽象层正确使用 `PrimitiveTopology`,不存在类型泄漏问题
### 4.4 缺少 Compute Pipeline 抽象
详见第一版文档,此处不再赘述。
### 4.5 RHIPipelineLayout 空壳
详见第一版文档,此处不再赘述。
---
## 5. 修改后的 CommandList 核心接口
```cpp
class RHICommandList {
public:
virtual ~RHICommandList() = default;
// ========== 生命周期 ==========
virtual void Reset(RHICommandPool* pool = nullptr) = 0;
virtual void Close() = 0;
// ========== 资源状态转换 ==========
virtual void ResourceBarrier(uint32_t count, const ResourceBarrier* barriers) = 0;
// ========== 渲染通道(新增)==========
virtual void BeginRenderPass(
RHIRenderPass* renderPass,
RHIFramebuffer* framebuffer,
const Rect& renderArea,
uint32_t clearValueCount,
const ClearValue* clearValues) = 0;
virtual void EndRenderPass() = 0;
// ========== 管线与描述符 ==========
virtual void SetPipelineState(RHIPipelineState* pso) = 0;
virtual void SetGraphicsDescriptorSets(
uint32_t firstSet,
uint32_t count,
RHIDescriptorSet** descriptorSets,
RHIPipelineLayout* pipelineLayout) = 0;
// ========== 动态状态(精简后)==========
virtual void SetViewports(uint32_t count, const Viewport* viewports) = 0;
virtual void SetScissorRects(uint32_t count, const Rect* rects) = 0;
virtual void SetStencilRef(uint8_t ref) = 0;
virtual void SetBlendFactor(const float factor[4]) = 0;
// ========== 顶点/索引 ==========
virtual void SetVertexBuffers(
uint32_t startSlot,
uint32_t count,
RHIVertexBufferView** buffers,
const uint64_t* offsets,
const uint32_t* strides) = 0;
virtual void SetIndexBuffer(
RHIIndexBufferView* buffer,
uint64_t offset,
IndexFormat format) = 0;
// ========== 绘制 ==========
virtual void Draw(uint32_t vertexCount, uint32_t instanceCount = 1,
uint32_t startVertex = 0, uint32_t startInstance = 0) = 0;
virtual void DrawIndexed(uint32_t indexCount, uint32_t instanceCount = 1,
uint32_t startIndex = 0, int32_t baseVertex = 0,
uint32_t startInstance = 0) = 0;
// ========== 计算 ==========
virtual void SetComputePipelineState(RHIPipelineState* pso) = 0;
virtual void SetComputeDescriptorSets(
uint32_t firstSet,
uint32_t count,
RHIDescriptorSet** descriptorSets,
RHIPipelineLayout* pipelineLayout) = 0;
virtual void Dispatch(uint32_t x, uint32_t y, uint32_t z) = 0;
// ========== 复制 ==========
virtual void CopyResource(RHIResource* dst, RHIResource* src) = 0;
// ========== 原生句柄 ==========
virtual void* GetNativeHandle() = 0;
};
```
---
## 6. 需要同时新增的抽象
### 6.1 RHIDescriptorSet
```cpp
class RHIDescriptorSet {
public:
virtual ~RHIDescriptorSet() = default;
virtual void Update(uint32_t offset, RHIResourceView* view) = 0;
virtual void UpdateSampler(uint32_t offset, RHISampler* sampler) = 0;
virtual void* GetNativeHandle() = 0;
};
```
### 6.2 RHIRenderPass
```cpp
struct AttachmentDesc {
Format format;
LoadAction loadOp = LoadAction::DontCare;
StoreAction storeOp = StoreAction::Store;
LoadAction stencilLoadOp = LoadAction::DontCare;
StoreAction stencilStoreOp = StoreAction::DontCare;
};
class RHIRenderPass {
public:
virtual ~RHIRenderPass() = default;
virtual bool Initialize(uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments,
const AttachmentDesc* depthStencilAttachment) = 0;
virtual void Shutdown() = 0;
virtual void* GetNativeHandle() = 0;
};
```
### 6.3 RHIFramebuffer
```cpp
class RHIFramebuffer {
public:
virtual ~RHIFramebuffer() = default;
virtual bool Initialize(RHIRenderPass* renderPass, uint32_t attachmentCount,
RHIResourceView** attachments, uint32_t width, uint32_t height) = 0;
virtual void Shutdown() = 0;
virtual void* GetNativeHandle() = 0;
};
```
### 6.4 RHIResource 基类
```cpp
class RHIResource {
public:
virtual ~RHIResource() = default;
virtual void* GetNativeHandle() = 0;
virtual ResourceStates GetState() const = 0;
virtual void SetState(ResourceStates state) = 0;
};
class RHIBuffer : public RHIResource { ... };
class RHITexture : public RHIResource { ... };
```
---
## 7. 问题优先级总结
| 优先级 | 问题 | 严重性 | 修复难度 | 状态 |
|--------|------|--------|----------|------|
| 1 | 字符串查找 SetUniform 不符合 D3D12/Vulkan | 🔴 致命 | 高 | ✅ 已完成 |
| 2 | 缺少显式 RenderPass | 🔴 致命 | 高 | ✅ 已完成 |
| 3 | 动态状态太多 | 🔴 高 | 高 | ✅ 已完成 |
| 4 | ResourceView 类型不明确 | 🟡 中 | 中 | ✅ 基本完成 |
| 5 | TransitionBarrier 针对 View 而非 Resource | 🟡 中 | 中 | ✅ 已完成 |
| 6 | SetGlobal* 空操作 | 🟡 中 | 低 | ✅ 已完成 |
| 7 | OpenGL 特有方法暴露 | 🟡 中 | 高 | ✅ 已完成 |
| 8 | 缺少 Compute Pipeline 抽象 | 🟡 中 | 中 | ✅ 已完成 |
---
## 8. 总结
当前 RHI 模块的基础框架有 D3D12/Vulkan 的影子,但**被 OpenGL 风格带偏**了。最核心的问题是:
1. **用字符串查找设 uniform** —— 这是 OpenGL 风格,必须改成 DescriptorSet 绑定
2. **缺少显式 RenderPass** —— 现代 API 的基础,必须添加
3. **动态状态太多** —— 应该收敛到 PSO只保留必要的动态状态
修复这些问题后RHI 抽象层将完全对齐 D3D12/Vulkan 的显式模型,未来接入 Vulkan 将会非常顺利。