# OpenGL 测试架构重构方案 本文档是 XCEngine 测试规范的 OpenGL 专项补充,旨在将 OpenGL 后端测试体系重构为与 D3D12 同样规范的标准架构。 **前置阅读**: - [tests/TEST_SPEC.md](../tests/TEST_SPEC.md) - 通用测试规范 - [tests/RHI/D3D12/TEST_SPEC.md](./D3D12/TEST_SPEC.md) - D3D12 专项规范(参考模板) **规范版本**: 1.0 **最后更新**: 2026-03-20 --- ## 1. 现状分析 ### 1.1 当前目录结构 ``` tests/RHI/OpenGL/ ├── CMakeLists.txt # 混乱的一级配置 ├── fixtures/ │ ├── OpenGLTestFixture.h │ └── OpenGLTestFixture.cpp ├── test_device.cpp ├── test_buffer.cpp # 被注释,未启用 ├── test_fence.cpp # 被注释,未启用 ├── test_texture.cpp ├── test_shader.cpp # 被注释,未启用 ├── test_pipeline_state.cpp ├── test_vertex_array.cpp ├── test_command_list.cpp ├── test_render_target_view.cpp ├── test_depth_stencil_view.cpp ├── test_swap_chain.cpp ├── test_sampler.cpp └── Res/ # 空文件夹,无实际资源 ├── Data/ ├── Shader/ └── Texture/ ``` ### 1.2 当前测试统计 | 组件 | 文件 | 测试数 | 状态 | |------|------|--------|------| | Device | `test_device.cpp` | 6 | ✅ 启用 | | Buffer | `test_buffer.cpp` | 6 | ❌ 被注释 | | Fence | `test_fence.cpp` | 5 | ❌ 被注释 | | Texture | `test_texture.cpp` | 4 | ✅ 启用 | | Shader | `test_shader.cpp` | 4 | ❌ 被注释 | | PipelineState | `test_pipeline_state.cpp` | 3 | ✅ 启用 | | VertexArray | `test_vertex_array.cpp` | 1 | ✅ 启用 | | CommandList | `test_command_list.cpp` | 14 | ✅ 启用 | | Sampler | `test_sampler.cpp` | 2 | ✅ 启用 | | SwapChain | `test_swap_chain.cpp` | 3 | ✅ 启用 | | RTV | `test_render_target_view.cpp` | 2 | ✅ 启用 | | DSV | `test_depth_stencil_view.cpp` | 2 | ✅ 启用 | | **总计** | | **52** | **37 启用, 15 被注释** | ### 1.3 与 D3D12 对比 | 方面 | D3D12 (规范) | OpenGL (现状) | |------|-------------|--------------| | **目录分层** | `unit/` + `integration/` 分离 | 全部扁平 | | **CMake 结构** | 顶层 + unit + integration 三级 | 仅一级 | | **Fixture 设计** | 每测试独立设备 | 静态共享上下文 | | **被注释测试** | 无 | 3 个文件被注释 | | **集成测试** | 有 (Python + Golden Image) | **缺失** | | **测试规范文档** | `TEST_SPEC.md` | **缺失** | | **Res 资源** | 按测试隔离 | 空文件夹 | | **资源复制** | POST_BUILD 自动复制 | 未配置 | --- ## 2. 问题详解 ### 2.1 Fixture 设计缺陷 **当前问题**: ```cpp // OpenGL - 静态成员所有测试共享,存在状态污染 static GLFWwindow* m_window; // 共享窗口 static bool m_contextInitialized; // 共享状态 static OpenGLDevice* m_device; // 共享设备 ``` **D3D12 规范做法**: ```cpp // 每个测试独立创建设备 void D3D12TestFixture::SetUp() { D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, ...); } ``` **OpenGL 重构方向**: ```cpp // 方案A: 每测试创建独立上下文 (推荐) class OpenGLTestFixture : public ::testing::Test { protected: void SetUp() override { // 创建独立 OpenGL 上下文 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); m_window = glfwCreateWindow(640, 480, "Test", nullptr, nullptr); glfwMakeContextCurrent(m_window); gladLoadGLLoader(...); } }; ``` ### 2.2 CMake 配置混乱 **当前问题**: ```cmake # 硬编码绝对路径 include_directories(${CMAKE_SOURCE_DIR}/tests/OpenPackage/include/) include_directories(${CMAKE_SOURCE_DIR}/tests/OpenPackage/lib/) link_directories(${CMAKE_SOURCE_DIR}/tests/OpenPackage/lib/) # 被注释的测试源文件 # test_buffer.cpp # test_fence.cpp # test_shader.cpp ``` **D3D12 规范做法**: ```cmake # 顶层 CMakeLists.txt 仅做 add_subdirectory add_subdirectory(unit) add_subdirectory(integration) # unit/CMakeLists.txt 独立配置 # integration/CMakeLists.txt 独立配置 ``` ### 2.3 资源目录为空 当前 `Res/` 下的 `Data/`、`Shader/`、`Texture/` 均为空目录,没有实际测试资源。 --- ## 3. 重构目标 ### 3.1 目标目录结构 ``` tests/RHI/OpenGL/ ├── CMakeLists.txt # 顶层配置 ├── TEST_SPEC.md # 本文档 (OpenGL 专项) ├── TEST_IMPROVEMENT_PLAN.md # 改进计划 ├── unit/ │ ├── CMakeLists.txt # 单元测试构建 │ ├── fixtures/ │ │ ├── OpenGLTestFixture.h │ │ └── OpenGLTestFixture.cpp │ ├── test_device.cpp │ ├── test_buffer.cpp │ ├── test_fence.cpp │ ├── test_texture.cpp │ ├── test_shader.cpp │ ├── test_pipeline_state.cpp │ ├── test_vertex_array.cpp │ ├── test_command_list.cpp │ ├── test_render_target_view.cpp │ ├── test_depth_stencil_view.cpp │ ├── test_swap_chain.cpp │ └── test_sampler.cpp └── integration/ ├── CMakeLists.txt ├── run_integration_test.py # 公共脚本 ├── compare_ppm.py # PPM 图像比对 ├── run.bat # Windows 启动脚本 ├── minimal/ # 最小化测试 │ ├── main.cpp │ ├── GT_minimal.ppm │ └── Res/ │ └── Shader/ │ ├── simple.vert │ └── simple.frag └── render_model/ # 模型渲染测试 ├── main.cpp ├── GT.ppm └── Res/ ├── Image/ ├── Model/ └── Shader/ ``` ### 3.2 测试数量目标 | 组件 | 当前 | 重构后 | 变化 | |------|------|--------|------| | Device | 6 | 6 | - | | Buffer | 0 (被注释) | 6 | +6 | | Fence | 0 (被注释) | 5 | +5 | | Texture | 4 | 4 | - | | Shader | 0 (被注释) | 4 | +4 | | PipelineState | 3 | 3 | - | | VertexArray | 1 | 2 | +1 | | CommandList | 14 | 14 | - | | Sampler | 2 | 2 | - | | SwapChain | 3 | 3 | - | | RTV | 2 | 2 | - | | DSV | 2 | 2 | - | | **单元测试总计** | **37** | **53** | **+16** | | **集成测试** | **0** | **2** | **+2** | --- ## 4. 分阶段实施计划 ### Phase 1: 目录结构重构 **目标**: 建立与 D3D12 一致的目录分层 **步骤**: 1. 创建 `unit/` 和 `integration/` 子目录 2. 移动现有测试文件到 `unit/` 3. 创建顶层 `CMakeLists.txt` 做 `add_subdirectory` 4. 重构 `unit/CMakeLists.txt` 独立配置 **目录变更**: ``` # 重构前 tests/RHI/OpenGL/CMakeLists.txt tests/RHI/OpenGL/test_*.cpp tests/RHI/OpenGL/fixtures/ # 重构后 tests/RHI/OpenGL/CMakeLists.txt # 顶层,仅 add_subdirectory tests/RHI/OpenGL/unit/CMakeLists.txt # 单元测试构建 tests/RHI/OpenGL/unit/test_*.cpp # 测试文件 tests/RHI/OpenGL/unit/fixtures/ # Fixture tests/RHI/OpenGL/integration/ # 新增 tests/RHI/OpenGL/integration/... ``` ### Phase 2: Fixture 重构 **目标**: 消除静态共享状态,避免测试间污染 **重构内容**: ```cpp // OpenGLTestFixture.h 重构 class OpenGLTestFixture : public ::testing::Test { protected: void SetUp() override; void TearDown() override; GLFWwindow* GetWindow() { return m_window; } void MakeContextCurrent(); void DoneContextCurrent(); void ClearGLErrors(); bool CheckGLError(const char* file, int line); void ResetGLState(); private: GLFWwindow* m_window = nullptr; OpenGLDevice* m_device = nullptr; }; // OpenGLTestFixture.cpp 重构 void OpenGLTestFixture::SetUp() { glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); m_window = glfwCreateWindow(640, 480, "OpenGL Tests", nullptr, nullptr); if (!m_window) { GTEST_SKIP() << "Failed to create GLFW window"; return; } glfwMakeContextCurrent(m_window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { GTEST_SKIP() << "Failed to initialize GLAD"; glfwDestroyWindow(m_window); m_window = nullptr; return; } m_device = new OpenGLDevice(); m_device->CreateRenderWindow(640, 480, "Test Window", false); ClearGLErrors(); } void OpenGLTestFixture::TearDown() { ResetGLState(); if (m_device) { delete m_device; m_device = nullptr; } if (m_window) { glfwDestroyWindow(m_window); m_window = nullptr; } } ``` ### Phase 3: 启用被注释的测试 **目标**: 激活 `test_buffer.cpp`、`test_fence.cpp`、`test_shader.cpp` **步骤**: 1. 取消 CMakeLists.txt 中的注释 2. 检查并修复可能的编译错误 3. 运行测试验证 **预期新增测试**: - Buffer: 6 tests - Fence: 5 tests - Shader: 4 tests ### Phase 4: 完善单元测试 **目标**: 补充缺失的测试用例 **新增测试计划**: | 组件 | 新增测试 | 说明 | |------|---------|------| | VertexArray | 1 | `VertexArray_Bind_MultipleAttributes` | ### Phase 5: 建立集成测试体系 **目标**: 建立与 D3D12 一致的集成测试框架 **步骤**: 1. 创建 `integration/` 目录结构 2. 复制 `run_integration_test.py` 和 `compare_ppm.py` 3. 创建 `minimal/` 集成测试 4. 创建 `render_model/` 集成测试 5. 配置 CTest 注册 **minimal/ 集成测试**: ```cpp // integration/minimal/main.cpp // 渲染一个简单三角形,输出 PPM 截图 int main() { // 1. 初始化 GLFW + OpenGL // 2. 创建窗口 // 3. 渲染简单场景 // 4. 截图保存为 minimal.ppm // 5. 退出 } ``` **Golden Image 生成流程**: 1. 在干净硬件环境运行集成测试 2. 截图保存为 `GT_.ppm` 3. 人工验证截图正确性 4. 提交到版本控制 ### Phase 6: 编写测试规范文档 **目标**: 创建 `TEST_SPEC.md` 和 `TEST_IMPROVEMENT_PLAN.md` --- ## 5. CMake 重构详细方案 ### 5.1 顶层 CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.15) project(OpenGLEngineTests) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(unit) add_subdirectory(integration) ``` ### 5.2 unit/CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.15) get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE) find_package(OpenGL REQUIRED) include_directories(${PROJECT_ROOT_DIR}/engine/include) include_directories(${PROJECT_ROOT_DIR}/engine/src) link_directories(${CMAKE_SOURCE_DIR}/tests/OpenGL/package/lib/) find_package(GTest REQUIRED) set(TEST_SOURCES ${CMAKE_SOURCE_DIR}/tests/OpenGL/package/src/glad.c fixtures/OpenGLTestFixture.cpp test_device.cpp test_buffer.cpp test_fence.cpp test_texture.cpp test_shader.cpp test_pipeline_state.cpp test_vertex_array.cpp test_command_list.cpp test_render_target_view.cpp test_depth_stencil_view.cpp test_swap_chain.cpp test_sampler.cpp ) add_executable(opengl_engine_tests ${TEST_SOURCES}) target_link_libraries(opengl_engine_tests PRIVATE opengl32 glfw3 XCEngine GTest::gtest GTest::gtest_main ) target_include_directories(opengl_engine_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/fixtures ${PROJECT_ROOT_DIR}/engine/include ${PROJECT_ROOT_DIR}/engine/src ) target_compile_definitions(opengl_engine_tests PRIVATE TEST_RESOURCES_DIR="${PROJECT_ROOT_DIR}/tests/RHI/OpenGL/integration/minimal/Res" ) add_custom_command(TARGET opengl_engine_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_ROOT_DIR}/tests/RHI/OpenGL/integration/minimal/Res $/Res ) enable_testing() add_test(NAME OpenGLEngineTests COMMAND opengl_engine_tests) ``` ### 5.3 integration/CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.15) project(OpenGL_Integration) set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine) find_package(Python3 REQUIRED) enable_testing() # Minimal test add_executable(OpenGL_Minimal WIN32 minimal/main.cpp ) target_include_directories(OpenGL_Minimal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/minimal ${ENGINE_ROOT_DIR}/include ) target_link_libraries(OpenGL_Minimal PRIVATE opengl32 glfw3 d3d12 dxgi d3dcompiler XCEngine ) # Copy Res folder add_custom_command(TARGET OpenGL_Minimal POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/minimal/Res $/Res ) # Copy test scripts add_custom_command(TARGET OpenGL_Minimal POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/run.bat $/ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/compare_ppm.py $/ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/run_integration_test.py $/ ) # Register integration test with CTest add_test(NAME OpenGL_Minimal_Integration COMMAND ${Python3_EXECUTABLE} $/run_integration_test.py $ minimal.ppm ${CMAKE_CURRENT_SOURCE_DIR}/minimal/GT_minimal.ppm 5 WORKING_DIRECTORY $ ) ``` --- ## 6. 测试前缀对应表 | 类名 | 测试前缀 | |------|---------| | OpenGLDevice | Device | | OpenGLBuffer | Buffer | | OpenGLFence | Fence | | OpenGLTexture | Texture | | OpenGLShader | Shader | | OpenGLPipelineState | PipelineState | | OpenGLVertexArray | VertexArray | | OpenGLCommandList | CommandList | | OpenGLSampler | Sampler | | OpenGLSwapChain | SwapChain | | OpenGLRenderTargetView | RTV | | OpenGLDepthStencilView | DSV | --- ## 7. 测试执行 ### 7.1 单元测试 ```bash # 方式 1: 使用统一脚本 python scripts/run_tests.py --unit-only # 方式 2: 直接使用 CTest cd build/tests/RHI/OpenGL/unit ctest -C Debug --output-on-failure ``` ### 7.2 集成测试 ```bash # 方式 1: 使用统一脚本 python scripts/run_tests.py --integration # 方式 2: 直接使用 CTest cd build/tests/RHI/OpenGL/integration ctest -C Debug --output-on-failure ``` ### 7.3 构建和测试 ```bash # 构建 cmake --build . --target OpenGL_Minimal --config Debug # 运行测试 python scripts/run_tests.py --build ``` --- ## 8. CI 集成 ### 8.1 GitHub Actions 配置 ```yaml name: OpenGL Tests on: [push, pull_request] jobs: test: runs-on: windows-latest steps: - uses: actions/checkout@v4 - name: Configure CMake run: cmake -B build -DCMAKE_BUILD_TYPE=Debug - name: Build OpenGL Tests run: cmake --build build --target opengl_engine_tests OpenGL_Minimal --config Debug - name: Run Unit Tests run: cd build/tests/RHI/OpenGL/unit && ctest -C Debug --output-on-failure - name: Run Integration Tests run: python scripts/run_tests.py --integration ``` ### 8.2 CI 模式 `--ci` 模式会跳过需要 GUI 的集成测试: ```bash python scripts/run_tests.py --ci # 仅运行单元测试 ``` --- ## 9. 文件清单 ### 9.1 需创建的文件 | 文件路径 | 说明 | |---------|------| | `tests/RHI/OpenGL/CMakeLists.txt` | 顶层 CMake 配置 | | `tests/RHI/OpenGL/TEST_SPEC.md` | OpenGL 专项规范 | | `tests/RHI/OpenGL/TEST_IMPROVEMENT_PLAN.md` | 改进计划 | | `tests/RHI/OpenGL/unit/CMakeLists.txt` | 单元测试构建配置 | | `tests/RHI/OpenGL/integration/CMakeLists.txt` | 集成测试构建配置 | | `tests/RHI/OpenGL/integration/run_integration_test.py` | 集成测试运行脚本 | | `tests/RHI/OpenGL/integration/compare_ppm.py` | PPM 图像比对脚本 | | `tests/RHI/OpenGL/integration/run.bat` | Windows 启动脚本 | | `tests/RHI/OpenGL/integration/minimal/main.cpp` | 最小化集成测试 | | `tests/RHI/OpenGL/integration/minimal/GT_minimal.ppm` | Golden Image | | `tests/RHI/OpenGL/integration/minimal/Res/Shader/*.vert` | Vertex Shader | | `tests/RHI/OpenGL/integration/minimal/Res/Shader/*.frag` | Fragment Shader | ### 9.2 需修改的文件 | 文件路径 | 修改内容 | |---------|---------| | `tests/RHI/OpenGL/fixtures/OpenGLTestFixture.h` | 重构 Fixture 接口 | | `tests/RHI/OpenGL/fixtures/OpenGLTestFixture.cpp` | 重构 Fixture 实现 | | `tests/RHI/OpenGL/CMakeLists.txt` | 简化为 add_subdirectory | ### 9.3 需移动的文件 | 原路径 | 新路径 | |-------|-------| | `tests/RHI/OpenGL/test_*.cpp` | `tests/RHI/OpenGL/unit/test_*.cpp` | | `tests/RHI/OpenGL/fixtures/*` | `tests/RHI/OpenGL/unit/fixtures/*` | --- ## 10. OpenGL 与 D3D12 测试差异说明 ### 10.1 平台特性差异 | 方面 | D3D12 | OpenGL | |------|-------|--------| | **设备创建** | `D3D12CreateDevice()` 每测试独立 | 共享 GLFWcontext + Glad | | **上下文管理** | 无 | GLFWwindow 生命周期 | | **错误检查** | `HRESULT` 返回值 | `glGetError()` 状态码 | | **渲染目标** | RTV/DSV descriptor | OpenGL Framebuffer Object | | **同步原语** | `ID3D12Fence` | `glFenceSync` + `glClientWaitSync` | | **管线状态** | PSO 对象 | OpenGL State Machine | | **资源绑定** | CommandList + DescriptorHeap | `glBindBuffer`, `glBindTexture` | ### 10.2 Fixture 设计差异 ```cpp // D3D12 - 每测试独立 COM 对象 class D3D12TestFixture : public ::testing::Test { ComPtr mDevice; ComPtr mCommandQueue; }; // OpenGL - 需要共享 context,但每测试独立 window class OpenGLTestFixture : public ::testing::Test { GLFWwindow* m_window; // 每测试独立 OpenGLDevice* m_device; // 每测试独立 }; ``` ### 10.3 测试资源差异 | 方面 | D3D12 | OpenGL | |------|-------|--------| | **Shader 格式** | HLSL (.hlsl) | GLSL (.vert, .frag, .geom) | | **纹理格式** | DDS | PNG/BMP/TGA | | **模型格式** | 自定义 .lhsm | 自定义 .lhsm | --- ## 11. 已知问题与待办 ### 11.1 Phase 1 待办 - [ ] 创建 `unit/` 和 `integration/` 目录 - [ ] 移动测试文件到 `unit/` - [ ] 创建顶层 `CMakeLists.txt` - [ ] 重构 `unit/CMakeLists.txt` ### 11.2 Phase 2 待办 - [ ] 重构 `OpenGLTestFixture` 消除静态成员 - [ ] 验证测试隔离效果 ### 11.3 Phase 3 待办 - [ ] 启用 `test_buffer.cpp` - [ ] 启用 `test_fence.cpp` - [ ] 启用 `test_shader.cpp` - [ ] 修复编译错误 ### 11.4 Phase 4 待办 - [ ] 补充 `VertexArray_Bind_MultipleAttributes` 测试 ### 11.5 Phase 5 待办 - [ ] 创建 `integration/` 目录结构 - [ ] 复制并适配 `run_integration_test.py` - [ ] 复制并适配 `compare_ppm.py` - [ ] 创建 `minimal/` 集成测试 - [ ] 创建 `render_model/` 集成测试 - [ ] 生成 Golden Image ### 11.6 Phase 6 待办 - [ ] 编写 `TEST_SPEC.md` - [ ] 编写 `TEST_IMPROVEMENT_PLAN.md` --- ## 12. 规范更新记录 | 版本 | 日期 | 变更 | |------|------|------| | 1.0 | 2026-03-20 | 初始版本,参考 D3D12 TEST_SPEC.md 制定重构方案 | --- **规范版本**: 1.0 **最后更新**: 2026-03-20 **前置文档**: - [tests/TEST_SPEC.md](../tests/TEST_SPEC.md) - [tests/RHI/D3D12/TEST_SPEC.md](./D3D12/TEST_SPEC.md)