diff --git a/docs/issues/RHI_OpenGL_State_Not_Applied.md b/docs/issues/RHI_OpenGL_State_Not_Applied.md new file mode 100644 index 00000000..d97390b3 --- /dev/null +++ b/docs/issues/RHI_OpenGL_State_Not_Applied.md @@ -0,0 +1,144 @@ +# RHI 模块严重问题:OpenGL 后端 RHI 状态不生效 + +## 问题严重程度 + +**严重级别**: 🔴 Critical Bug + +## 问题定位 + +`OpenGLPipelineState` 维护了两套状态系统,但它们之间没有关联。 + +### 两套状态系统 + +**RHI 抽象层状态** (`OpenGLPipelineState.h:131-133`): + +```cpp +RasterizerDesc m_rasterizerDesc; +BlendDesc m_blendDesc; +DepthStencilStateDesc m_depthStencilDesc; +``` + +**OpenGL 特定状态** (`OpenGLPipelineState.h:141-143`): + +```cpp +OpenGLDepthStencilState m_glDepthStencilState; +OpenGLBlendState m_glBlendState; +OpenGLRasterizerState m_glRasterizerState; +``` + +## 问题详述 + +### RHI 接口设置的状态只存储不生效 + +```cpp +// OpenGLPipelineState.cpp:19-21 +void OpenGLPipelineState::SetRasterizerState(const RasterizerDesc& state) { + m_rasterizerDesc = state; // ❌ 只存储到 m_rasterizerDesc +} + +void OpenGLPipelineState::SetBlendState(const BlendDesc& state) { + m_blendDesc = state; // ❌ 只存储到 m_blendDesc +} + +void OpenGLPipelineState::SetDepthStencilState(const DepthStencilStateDesc& state) { + m_depthStencilDesc = state; // ❌ 只存储到 m_depthStencilDesc +} +``` + +### Apply() 使用的是另一套状态 + +```cpp +// OpenGLPipelineState.cpp:133-149 +void OpenGLPipelineState::ApplyRasterizer() { + if (m_glRasterizerState.cullFaceEnable) { // ✅ 使用的是 m_glRasterizerState + glEnable(GL_CULL_FACE); + glCullFace(static_cast(m_glRasterizerState.cullFace)); + // ... + } +} + +void OpenGLPipelineState::ApplyBlend() { + if (m_glBlendState.blendEnable) { // ✅ 使用的是 m_glBlendState + glEnable(GL_BLEND); + // ... + } +} + +void OpenGLPipelineState::ApplyDepthStencil() { + if (m_glDepthStencilState.depthTestEnable) { // ✅ 使用的是 m_glDepthStencilState + glEnable(GL_DEPTH_TEST); + // ... + } +} +``` + +## 导致的后果 + +**通过 RHI 抽象层设置的状态完全不会生效!** + +```cpp +// 上层代码 +RasterizerDesc raster; +raster.cullMode = 2; // 设置剔除模式 +pso->SetRasterizerState(raster); // 通过 RHI 接口设置 + +// 实际渲染时 +pso->Bind(); // 调用 ApplyRasterizer() +// ❌ m_glRasterizerState.cullMode 仍然是默认值,不是设置的值! +``` + +## 对比 D3D12 后端 + +D3D12 后端正确地在 `CreateD3D12PSO()` 中使用了 RHI 状态: + +```cpp +// D3D12PipelineState.cpp:157-161 +desc.RasterizerState.FillMode = static_cast(m_rasterizerDesc.fillMode); +desc.RasterizerState.CullMode = static_cast(m_rasterizerDesc.cullMode); +// ✅ 直接使用 m_rasterizerDesc +``` + +## 根本原因 + +OpenGL 后端有两个 `SetRasterizerState` 重载: + +```cpp +// RHI 接口 - 只存储 +void SetRasterizerState(const RasterizerDesc& state); + +// OpenGL 特定接口 - 会生效 +void SetRasterizerState(const OpenGLRasterizerState& state); +``` + +RHI 接口的实现缺少状态转换逻辑。 + +## 修复方案 + +在 `SetRasterizerState(const RasterizerDesc& state)` 中添加转换逻辑: + +```cpp +void OpenGLPipelineState::SetRasterizerState(const RasterizerDesc& state) { + m_rasterizerDesc = state; + + // 转换到 OpenGL 状态 + m_glRasterizerState.cullFaceEnable = (state.cullMode != 0); // CullMode::None + m_glRasterizerState.cullFace = static_cast(state.cullMode); + m_glRasterizerState.frontFace = static_cast(state.frontFace); + m_glRasterizerState.polygonMode = static_cast(state.fillMode); + m_glRasterizerState.depthClipEnable = state.depthClipEnable; + // ... 其他字段 +} +``` + +同样需要修复 `SetBlendState` 和 `SetDepthStencilState`。 + +## 相关文件 + +- `engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineState.h` +- `engine/src/RHI/OpenGL/OpenGLPipelineState.cpp` + +## 影响范围 + +1. **所有通过 RHI 抽象层设置的状态都不会生效** +2. **OpenGL 集成测试能工作是因为使用了 OpenGL 特定 API** +3. **上层代码如果使用 RHI 接口,行为会与预期不符** \ No newline at end of file diff --git a/docs/issues/RHI_PipelineState_Shader_Missing.md b/docs/issues/RHI_PipelineState_Shader_Missing.md deleted file mode 100644 index d54dd17d..00000000 --- a/docs/issues/RHI_PipelineState_Shader_Missing.md +++ /dev/null @@ -1,174 +0,0 @@ -# RHI 模块严重问题:PipelineState 与 Shader 关系设计混乱 - -## 问题严重程度 - -**严重级别**: 🔴 架构级缺陷(Critical) - -## 问题定位 - -根据核心设计理念: -> - **求同存异**:优先提取 API 共性作为核心抽象 -> - **核心约束原则**:面向 D3D12/Vulkan 设计,参考 Unity RHI - -当前设计存在一个**根本性的架构缺陷**。 - -## 问题详述 - -### `RHIDevice::CreatePipelineState()` 接口设计错误 - -```cpp -// engine/include/XCEngine/RHI/RHIDevice.h:41 -virtual RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) = 0; -``` - -### `GraphicsPipelineDesc` 缺少 Shader 字段 - -```cpp -// engine/include/XCEngine/RHI/RHITypes.h:309-320 -struct GraphicsPipelineDesc { - InputLayoutDesc inputLayout; - RasterizerDesc rasterizerState; - BlendDesc blendState; - DepthStencilStateDesc depthStencilState; - - uint32_t topologyType = 0; - uint32_t renderTargetCount = 1; - uint32_t renderTargetFormats[8] = { 0 }; - uint32_t depthStencilFormat = 0; - uint32_t sampleCount = 1; - // ❌ 缺少 Shader 字段! -}; -``` - -## 导致的严重后果 - -### 1. D3D12 PSO 创建后立即无效 - -```cpp -// engine/src/RHI/D3D12/D3D12Device.cpp:434-444 -RHIPipelineState* D3D12Device::CreatePipelineState(const GraphicsPipelineDesc& desc) { - auto* pso = new D3D12PipelineState(m_device.Get()); - pso->SetInputLayout(desc.inputLayout); - // ... 设置各种状态 - return pso; // ❌ 此时 m_vsBytecode 和 m_psBytecode 都是空的! -} -``` - -当调用 `EnsureValid()` 时: - -```cpp -// engine/src/RHI/D3D12/D3D12PipelineState.cpp:143-146 -bool D3D12PipelineState::CreateD3D12PSO() { - if (!m_vsBytecode.pShaderBytecode || !m_psBytecode.pShaderBytecode) { - return false; // ❌ 直接失败! - } - // ... -} -``` - -### 2. 后端行为不一致,违反"求同存异"原则 - -| 后端 | `CreatePipelineState()` 返回后 `IsValid()` | -|------|------------------------------------------| -| D3D12 | `false` (需要额外设置 shader) | -| OpenGL | `true` (OpenGL 不需要预编译 PSO) | - -这完全违反了核心设计原则。 - -### 3. 运行时状态污染 - -Shader 通过 `SetShaderBytecodes()` 在运行时注入: - -```cpp -// 需要外部代码手动调用 -pso->SetShaderBytecodes(vsBytecode, psBytecode); // ❌ 语义混乱 -pso->EnsureValid(); // ❌ 现在才能创建真正的 D3D12 PSO -``` - -## 设计根源分析 - -### 参考 Unity RHI 的正确做法 - -Unity 的 `PipelineState` 是 **Shader + RenderState** 的不可变组合: -- 创建时必须提供完整的 shader 和 state -- 创建后不可修改 -- `IsValid()` 的语义是确定的 - -### 当前设计的错误假设 - -把 Shader 和 PipelineState 分离,试图让 PSO 成为"可配置的 render state 容器",但这与 D3D12/Vulkan 的 PSO 概念完全冲突——在这些 API 中,**PSO 是 shader + state 的编译后不可变对象**。 - -## 影响范围 - -1. **无法正确实现 SRP**:上层渲染管线无法可靠地预编译 PSO -2. **资源管理混乱**:PSO 何时真正创建?何时可以释放?语义不明确 -3. **测试困难**:后端行为不一致,单元测试无法统一编写 -4. **扩展性差**:未来添加 Vulkan 后端会遇到同样的问题 - -## 建议修复方案 - -### 方案 A:在 `GraphicsPipelineDesc` 中添加 Shader 字段 - -```cpp -struct GraphicsPipelineDesc { - // ... 现有字段 - - RHIShader* vertexShader = nullptr; - RHIShader* pixelShader = nullptr; - RHIShader* geometryShader = nullptr; - RHIShader* hullShader = nullptr; - RHIShader* domainShader = nullptr; -}; -``` - -### 方案 B:创建专用的 ShaderProgram 类型 - -```cpp -struct ShaderProgramDesc { - RHIShader* vertexShader = nullptr; - RHIShader* pixelShader = nullptr; - // ... -}; - -struct GraphicsPipelineDesc { - ShaderProgramDesc shaders; // 包含所有 shader - InputLayoutDesc inputLayout; - RasterizerDesc rasterizerState; - BlendDesc blendState; - DepthStencilStateDesc depthStencilState; - // ... -}; -``` - -### 方案 C:分离 PSO 创建接口 - -```cpp -// 独立的 Compute PSO 创建 -RHIPipelineState* CreateComputePipelineState(RHIShader* computeShader); - -// Graphics PSO 创建时必须提供 shader -RHIPipelineState* CreateGraphicsPipelineState( - const GraphicsPipelineDesc& desc, - RHIShader* vertexShader, - RHIShader* pixelShader -); -``` - -## 相关文件 - -- `engine/include/XCEngine/RHI/RHIDevice.h` -- `engine/include/XCEngine/RHI/RHITypes.h` -- `engine/include/XCEngine/RHI/RHIPipelineState.h` -- `engine/src/RHI/D3D12/D3D12Device.cpp` -- `engine/src/RHI/D3D12/D3D12PipelineState.cpp` -- `engine/src/RHI/OpenGL/OpenGLDevice.cpp` - -## 总结 - -**`GraphicsPipelineDesc` 缺少 Shader 字段** 导致: - -1. 抽象层无法正确表达 D3D12/Vulkan 的 PSO 概念 -2. 后端行为不一致,违反"求同存异" -3. PSO 生命周期管理语义混乱 - -这是整个 RHI 模块**最严重的架构级缺陷**,应该优先修复。 \ No newline at end of file diff --git a/editor/src/Core/EditorContext.h b/editor/src/Core/EditorContext.h index 5cd3b906..86281265 100644 --- a/editor/src/Core/EditorContext.h +++ b/editor/src/Core/EditorContext.h @@ -4,6 +4,7 @@ #include "EventBus.h" #include "SelectionManager.h" #include "IProjectManager.h" +#include "ISceneManager.h" #include "Managers/SceneManager.h" #include "Managers/ProjectManager.h" #include @@ -30,11 +31,7 @@ public: return *m_selectionManager; } - void* GetSceneManager() override { - return m_sceneManager.get(); - } - - SceneManager& GetSceneManagerConcrete() { + ISceneManager& GetSceneManager() override { return *m_sceneManager; } diff --git a/editor/src/Core/IEditorContext.h b/editor/src/Core/IEditorContext.h index c2ddf61b..37a69649 100644 --- a/editor/src/Core/IEditorContext.h +++ b/editor/src/Core/IEditorContext.h @@ -9,6 +9,7 @@ namespace Editor { class EventBus; class ISelectionManager; class IProjectManager; +class ISceneManager; class IEditorContext { public: @@ -16,7 +17,7 @@ public: virtual EventBus& GetEventBus() = 0; virtual ISelectionManager& GetSelectionManager() = 0; - virtual void* GetSceneManager() = 0; + virtual ISceneManager& GetSceneManager() = 0; virtual IProjectManager& GetProjectManager() = 0; virtual void SetProjectPath(const std::string& path) = 0; diff --git a/editor/src/Core/ISceneManager.h b/editor/src/Core/ISceneManager.h new file mode 100644 index 00000000..94a12b51 --- /dev/null +++ b/editor/src/Core/ISceneManager.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +namespace XCEngine { +namespace Editor { + +class ISceneManager { +public: + virtual ~ISceneManager() = default; + + virtual ::XCEngine::Components::GameObject* CreateEntity(const std::string& name, ::XCEngine::Components::GameObject* parent = nullptr) = 0; + virtual void DeleteEntity(::XCEngine::Components::GameObject::ID id) = 0; + virtual void RenameEntity(::XCEngine::Components::GameObject::ID id, const std::string& newName) = 0; + virtual ::XCEngine::Components::GameObject* GetEntity(::XCEngine::Components::GameObject::ID id) = 0; + virtual const std::vector<::XCEngine::Components::GameObject*>& GetRootEntities() const = 0; + virtual void CopyEntity(::XCEngine::Components::GameObject::ID id) = 0; + virtual ::XCEngine::Components::GameObject::ID PasteEntity(::XCEngine::Components::GameObject::ID parent = 0) = 0; + virtual ::XCEngine::Components::GameObject::ID DuplicateEntity(::XCEngine::Components::GameObject::ID id) = 0; + virtual void MoveEntity(::XCEngine::Components::GameObject::ID id, ::XCEngine::Components::GameObject::ID newParent) = 0; + virtual bool HasClipboardData() const = 0; + virtual void CreateDemoScene() = 0; +}; + +} +} \ No newline at end of file diff --git a/editor/src/Managers/SceneManager.h b/editor/src/Managers/SceneManager.h index d6f68fcb..5f96c254 100644 --- a/editor/src/Managers/SceneManager.h +++ b/editor/src/Managers/SceneManager.h @@ -14,13 +14,14 @@ #include #include "Core/ISelectionManager.h" +#include "Core/ISceneManager.h" namespace XCEngine { namespace Editor { class ISelectionManager; -class SceneManager { +class SceneManager : public ISceneManager { public: SceneManager(); diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index 19b50f42..20a53fee 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -1,5 +1,6 @@ #include "HierarchyPanel.h" #include "Core/IEditorContext.h" +#include "Core/ISceneManager.h" #include #include #include @@ -15,8 +16,8 @@ HierarchyPanel::~HierarchyPanel() { } void HierarchyPanel::OnAttach() { - auto* sceneManager = static_cast(m_context->GetSceneManager()); - sceneManager->CreateDemoScene(); + auto& sceneManager = m_context->GetSceneManager(); + sceneManager.CreateDemoScene(); } void HierarchyPanel::Render() { @@ -32,7 +33,7 @@ void HierarchyPanel::Render() { ImGui::BeginChild("EntityList"); - auto& sceneManager = *static_cast(m_context->GetSceneManager()); + auto& sceneManager = m_context->GetSceneManager(); auto rootEntities = sceneManager.GetRootEntities(); SortEntities(const_cast&>(rootEntities)); @@ -56,7 +57,7 @@ void HierarchyPanel::Render() { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) { ::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data; if (sourceGameObject && sourceGameObject->GetParent() != nullptr) { - auto& sceneManager = *static_cast(m_context->GetSceneManager()); + auto& sceneManager = m_context->GetSceneManager(); sceneManager.MoveEntity(sourceGameObject->GetID(), 0); } } @@ -108,7 +109,7 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject ImGui::SetNextItemWidth(-1); if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { if (strlen(m_renameBuffer) > 0) { - static_cast(m_context->GetSceneManager())->RenameEntity(gameObject->GetID(), m_renameBuffer); + m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer); } m_renaming = false; m_renamingEntity = nullptr; @@ -116,7 +117,7 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) { if (strlen(m_renameBuffer) > 0) { - static_cast(m_context->GetSceneManager())->RenameEntity(gameObject->GetID(), m_renameBuffer); + m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer); } m_renaming = false; m_renamingEntity = nullptr; @@ -161,7 +162,7 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject } void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameObject) { - auto& sceneManager = *static_cast(m_context->GetSceneManager()); + auto& sceneManager = m_context->GetSceneManager(); auto& selectionManager = m_context->GetSelectionManager(); if (ImGui::BeginMenu("Create")) { @@ -213,7 +214,7 @@ void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameO } void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent) { - auto& sceneManager = *static_cast(m_context->GetSceneManager()); + auto& sceneManager = m_context->GetSceneManager(); auto& selectionManager = m_context->GetSelectionManager(); if (ImGui::MenuItem("Empty Object")) { @@ -256,7 +257,7 @@ void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent } void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObject) { - auto& sceneManager = *static_cast(m_context->GetSceneManager()); + auto& sceneManager = m_context->GetSceneManager(); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { ImGui::SetDragDropPayload("ENTITY_PTR", &gameObject, sizeof(::XCEngine::Components::GameObject*)); @@ -297,7 +298,7 @@ void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObje } void HierarchyPanel::HandleKeyboardShortcuts() { - auto& sceneManager = *static_cast(m_context->GetSceneManager()); + auto& sceneManager = m_context->GetSceneManager(); auto& selectionManager = m_context->GetSelectionManager(); ::XCEngine::Components::GameObject* selectedGameObject = sceneManager.GetEntity(selectionManager.GetSelectedEntity()); diff --git a/editor/src/panels/InspectorPanel.cpp b/editor/src/panels/InspectorPanel.cpp index e73dc801..3af5c999 100644 --- a/editor/src/panels/InspectorPanel.cpp +++ b/editor/src/panels/InspectorPanel.cpp @@ -1,6 +1,6 @@ #include "InspectorPanel.h" #include "Core/EditorContext.h" -#include "Managers/SceneManager.h" +#include "Core/ISceneManager.h" #include "UI/UI.h" #include #include @@ -38,8 +38,8 @@ void InspectorPanel::Render() { m_selectedEntityId = m_context->GetSelectionManager().GetSelectedEntity(); if (m_selectedEntityId) { - auto* sceneManager = static_cast(m_context->GetSceneManager()); - auto* gameObject = sceneManager->GetEntity(m_selectedEntityId); + auto& sceneManager = m_context->GetSceneManager(); + auto* gameObject = sceneManager.GetEntity(m_selectedEntityId); if (gameObject) { RenderGameObject(gameObject); } else {