Files
XCEngine/OpenGL_Test_Restructuring_Plan.md
ssdfasd 0a19fdfb0f OpenGL: Restructure tests similar to D3D12 layout
- Move old test files to new unit/integration structure
- Add OpenGL Test Fixture
- Update CMakeLists.txt for new layout
- Add OpenGL_Test_Restructuring_Plan.md
2026-03-20 17:37:09 +08:00

19 KiB
Raw Blame History

OpenGL 测试架构重构方案

本文档是 XCEngine 测试规范的 OpenGL 专项补充,旨在将 OpenGL 后端测试体系重构为与 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 设计缺陷

当前问题:

// OpenGL - 静态成员所有测试共享,存在状态污染
static GLFWwindow* m_window;        // 共享窗口
static bool m_contextInitialized;    // 共享状态
static OpenGLDevice* m_device;       // 共享设备

D3D12 规范做法:

// 每个测试独立创建设备
void D3D12TestFixture::SetUp() {
    D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, ...);
}

OpenGL 重构方向:

// 方案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 配置混乱

当前问题:

# 硬编码绝对路径
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 规范做法:

# 顶层 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.txtadd_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 重构

目标: 消除静态共享状态,避免测试间污染

重构内容:

// 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.cpptest_fence.cpptest_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.pycompare_ppm.py
  3. 创建 minimal/ 集成测试
  4. 创建 render_model/ 集成测试
  5. 配置 CTest 注册

minimal/ 集成测试:

// integration/minimal/main.cpp
// 渲染一个简单三角形,输出 PPM 截图
int main() {
    // 1. 初始化 GLFW + OpenGL
    // 2. 创建窗口
    // 3. 渲染简单场景
    // 4. 截图保存为 minimal.ppm
    // 5. 退出
}

Golden Image 生成流程:

  1. 在干净硬件环境运行集成测试
  2. 截图保存为 GT_<name>.ppm
  3. 人工验证截图正确性
  4. 提交到版本控制

Phase 6: 编写测试规范文档

目标: 创建 TEST_SPEC.mdTEST_IMPROVEMENT_PLAN.md


5. CMake 重构详细方案

5.1 顶层 CMakeLists.txt

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_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
        $<TARGET_FILE_DIR:opengl_engine_tests>/Res
)

enable_testing()
add_test(NAME OpenGLEngineTests COMMAND opengl_engine_tests)

5.3 integration/CMakeLists.txt

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
        $<TARGET_FILE_DIR:OpenGL_Minimal>/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
        $<TARGET_FILE_DIR:OpenGL_Minimal>/
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        ${CMAKE_CURRENT_SOURCE_DIR}/compare_ppm.py
        $<TARGET_FILE_DIR:OpenGL_Minimal>/
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        ${CMAKE_CURRENT_SOURCE_DIR}/run_integration_test.py
        $<TARGET_FILE_DIR:OpenGL_Minimal>/
)

# Register integration test with CTest
add_test(NAME OpenGL_Minimal_Integration
    COMMAND ${Python3_EXECUTABLE} $<TARGET_FILE_DIR:OpenGL_Minimal>/run_integration_test.py
        $<TARGET_FILE:OpenGL_Minimal>
        minimal.ppm
        ${CMAKE_CURRENT_SOURCE_DIR}/minimal/GT_minimal.ppm
        5
    WORKING_DIRECTORY $<TARGET_FILE_DIR:OpenGL_Minimal>
)

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 单元测试

# 方式 1: 使用统一脚本
python scripts/run_tests.py --unit-only

# 方式 2: 直接使用 CTest
cd build/tests/RHI/OpenGL/unit
ctest -C Debug --output-on-failure

7.2 集成测试

# 方式 1: 使用统一脚本
python scripts/run_tests.py --integration

# 方式 2: 直接使用 CTest
cd build/tests/RHI/OpenGL/integration
ctest -C Debug --output-on-failure

7.3 构建和测试

# 构建
cmake --build . --target OpenGL_Minimal --config Debug

# 运行测试
python scripts/run_tests.py --build

8. CI 集成

8.1 GitHub Actions 配置

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 的集成测试:

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 设计差异

// D3D12 - 每测试独立 COM 对象
class D3D12TestFixture : public ::testing::Test {
    ComPtr<ID3D12Device> mDevice;
    ComPtr<ID3D12CommandQueue> 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
前置文档: