19 KiB
19 KiB
OpenGL 测试架构重构方案
本文档是 XCEngine 测试规范的 OpenGL 专项补充,旨在将 OpenGL 后端测试体系重构为与 D3D12 同样规范的标准架构。
前置阅读:
- tests/TEST_SPEC.md - 通用测试规范
- tests/RHI/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 设计缺陷
当前问题:
// 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 一致的目录分层
步骤:
- 创建
unit/和integration/子目录 - 移动现有测试文件到
unit/ - 创建顶层
CMakeLists.txt做add_subdirectory - 重构
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.cpp、test_fence.cpp、test_shader.cpp
步骤:
- 取消 CMakeLists.txt 中的注释
- 检查并修复可能的编译错误
- 运行测试验证
预期新增测试:
- Buffer: 6 tests
- Fence: 5 tests
- Shader: 4 tests
Phase 4: 完善单元测试
目标: 补充缺失的测试用例
新增测试计划:
| 组件 | 新增测试 | 说明 |
|---|---|---|
| VertexArray | 1 | VertexArray_Bind_MultipleAttributes |
Phase 5: 建立集成测试体系
目标: 建立与 D3D12 一致的集成测试框架
步骤:
- 创建
integration/目录结构 - 复制
run_integration_test.py和compare_ppm.py - 创建
minimal/集成测试 - 创建
render_model/集成测试 - 配置 CTest 注册
minimal/ 集成测试:
// integration/minimal/main.cpp
// 渲染一个简单三角形,输出 PPM 截图
int main() {
// 1. 初始化 GLFW + OpenGL
// 2. 创建窗口
// 3. 渲染简单场景
// 4. 截图保存为 minimal.ppm
// 5. 退出
}
Golden Image 生成流程:
- 在干净硬件环境运行集成测试
- 截图保存为
GT_<name>.ppm - 人工验证截图正确性
- 提交到版本控制
Phase 6: 编写测试规范文档
目标: 创建 TEST_SPEC.md 和 TEST_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
前置文档: