From 394bec9db614f842f6d460d34f6583f09e0f90d9 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 20 Mar 2026 19:56:48 +0800 Subject: [PATCH] OpenGL: Add test documentation for minimal integration test - Create TEST_SPEC.md with OpenGL test hierarchy and specifications - Add section and inline comments to main.cpp explaining: - Window size calculation with AdjustWindowRect - glFinish before screenshot for GPU sync - Target frame count warm-up period - Shutdown order (reverse of initialization) --- tests/RHI/OpenGL/TEST_SPEC.md | 251 ++++++++++++++++++ tests/RHI/OpenGL/integration/minimal/main.cpp | 16 +- 2 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 tests/RHI/OpenGL/TEST_SPEC.md diff --git a/tests/RHI/OpenGL/TEST_SPEC.md b/tests/RHI/OpenGL/TEST_SPEC.md new file mode 100644 index 00000000..bc28f877 --- /dev/null +++ b/tests/RHI/OpenGL/TEST_SPEC.md @@ -0,0 +1,251 @@ +# OpenGL 测试专项规范 + +本文档是 XCEngine 测试规范的 OpenGL 专项补充。 + +**前置阅读**: [tests/TEST_SPEC.md](../TEST_SPEC.md) - 通用测试规范 + +--- + +## 1. 概述 + +### 1.1 OpenGL 测试特点 + +| 特点 | 说明 | +|------|------| +| 平台依赖 | Windows WGL 实现,Win32 原生 API | +| 窗口依赖 | 集成测试需要 GUI 窗口 | +| GPU 状态 | 测试间可能有 GPU 状态污染 | +| 无 GLFW | 已移除 GLFW 依赖,使用 Win32 WGL API | + +### 1.2 测试层级 + +| 层级 | 位置 | 执行方式 | 框架 | +|------|------|----------|------| +| 单元测试 | `tests/RHI/OpenGL/unit/` | CTest | Google Test | +| 集成测试 | `tests/RHI/OpenGL/integration/` | CTest + Python | Python wrapper | + +--- + +## 2. 单元测试规范 + +### 2.1 Fixture 设计 + +每个测试独立创建 OpenGL 上下文,避免 GPU 状态污染: + +```cpp +class OpenGLTestFixture : public ::testing::Test { +protected: + void SetUp() override; // 创建窗口、初始化 OpenGL 上下文 + void TearDown() override; // 清理资源、销毁上下文 + + HWND GetHWND(); + HDC GetHDC(); + HGLRC GetGLRC(); + void MakeCurrent(); +}; +``` + +### 2.2 测试前缀对应 + +| 类名 | 测试前缀 | +|------|---------| +| OpenGLDevice | Device | +| OpenGLSwapChain | SwapChain | +| OpenGLBuffer | Buffer | +| OpenGLTexture | Texture | +| OpenGLShader | Shader | +| OpenGLCommandList | CommandList | +| OpenGLPipelineState | PipelineState | +| OpenGLRenderTargetView | RTV | +| OpenGLDepthStencilView | DSV | +| OpenGLFence | Fence | +| OpenGLSampler | Sampler | +| OpenGLVertexArray | VertexArray | + +--- + +## 3. 集成测试规范 + +### 3.1 目录结构 + +每个集成测试独占一个子文件夹,资源相互隔离: + +``` +integration/ +├── CMakeLists.txt # 构建配置 +├── run_integration_test.py # 公共测试运行脚本 +├── compare_ppm.py # PPM 图像比对脚本 +├── minimal/ # 最小化测试 +│ ├── main.cpp +│ ├── GT.ppm +│ ├── CMakeLists.txt +│ └── run.bat +└── ... # 其他测试 +``` + +### 3.2 Python Wrapper + +**位置**: `tests/RHI/OpenGL/integration/run_integration_test.py` + +**职责**: +1. 启动 OpenGL exe +2. 等待进程完成 +3. 检查输出文件 (PPM 截图) +4. 调用 `compare_ppm.py` 比对 Golden Image +5. 返回 0(成功)/1(失败) + +### 3.3 CTest 注册格式 + +```cmake +add_test(NAME OpenGL_Minimal_Integration + COMMAND ${Python3_EXECUTABLE} $/run_integration_test.py + $ + minimal.ppm + ${CMAKE_CURRENT_SOURCE_DIR}/minimal/GT.ppm + 5 + WORKING_DIRECTORY $ +) +``` + +### 3.4 Golden Image 规范 + +| 属性 | 值 | +|------|-----| +| 格式 | PPM (P6) | +| 命名 | `GT.ppm` 或 `GT_.ppm` | +| 阈值 | 默认 5% | +| 存储位置 | `tests/RHI/OpenGL/integration//` | + +### 3.5 Golden Image 生成流程 + +1. 在干净硬件环境运行集成测试 +2. 截图保存为 `minimal.ppm` +3. 人工验证截图正确性 +4. 提交到版本控制作为 GT + +### 3.6 当前集成测试 + +| 测试名 | Golden Image | 状态 | +|--------|-------------|------| +| OpenGL_Minimal_Integration | `minimal/GT.ppm` | ✅ 通过 | + +--- + +## 4. 测试执行 + +### 4.1 单元测试 + +```bash +# 方式 1: 使用统一脚本 +python scripts/run_tests.py --unit-only + +# 方式 2: 直接使用 CTest +cd build/tests/RHI/OpenGL/unit +ctest -C Debug --output-on-failure +``` + +### 4.2 集成测试 + +```bash +# 方式 1: 使用统一脚本 +python scripts/run_tests.py --integration + +# 方式 2: 直接使用 CTest +cd build/tests/RHI/OpenGL/integration +ctest -C Debug --output-on-failure +``` + +### 4.3 构建和测试 + +```bash +# 构建 +cmake --build . --target OpenGL_Minimal --config Debug + +# 运行测试 +python scripts/run_tests.py --build +``` + +--- + +## 5. CI 集成 + +### 5.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_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 +``` + +### 5.2 CI 模式 + +`--ci` 模式会跳过需要 GUI 的集成测试: +```bash +python scripts/run_tests.py --ci # 仅运行单元测试 +``` + +--- + +## 6. 文件结构 + +``` +tests/RHI/OpenGL/ +├── CMakeLists.txt +├── TEST_SPEC.md # 本文档 (OpenGL 专项) +├── unit/ +│ ├── CMakeLists.txt +│ ├── fixtures/ +│ │ ├── OpenGLTestFixture.h +│ │ └── OpenGLTestFixture.cpp +│ └── test_device.cpp +└── integration/ + ├── CMakeLists.txt + ├── run_integration_test.py # 公共脚本 + ├── compare_ppm.py # 公共脚本 + └── minimal/ # 最小化测试 + ├── main.cpp + ├── GT.ppm + ├── CMakeLists.txt + └── run.bat +``` + +--- + +## 7. 已知问题 + +### 7.1 窗口尺寸问题 + +**问题**: `SetWindowPos` 的尺寸参数在 `WS_OVERLAPPEDWINDOW` 风格下是完整窗口尺寸(包含 chrome、边框),而不是客户区尺寸。 + +**修复**: 使用 `AdjustWindowRect` 计算完整尺寸后再传给 `SetWindowPos`。 + +--- + +## 8. 规范更新记录 + +| 版本 | 日期 | 变更 | +|------|------|------| +| 1.0 | 2026-03-20 | 初始版本,移除 GLFW 依赖后的重构版本 | + +--- + +**规范版本**: 1.0 +**最后更新**: 2026-03-20 +**前置文档**: [tests/TEST_SPEC.md](../TEST_SPEC.md) \ No newline at end of file diff --git a/tests/RHI/OpenGL/integration/minimal/main.cpp b/tests/RHI/OpenGL/integration/minimal/main.cpp index 3f3e3b78..571c89e0 100644 --- a/tests/RHI/OpenGL/integration/minimal/main.cpp +++ b/tests/RHI/OpenGL/integration/minimal/main.cpp @@ -77,12 +77,14 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { + // Initialize logger Logger::Get().Initialize(); Logger::Get().AddSink(std::make_unique()); Logger::Get().SetMinimumLevel(LogLevel::Debug); Log("[INFO] OpenGL Integration Test Starting"); + // Register window class WNDCLASSEXW wc = {}; wc.cbSize = sizeof(WNDCLASSEXW); wc.style = CS_HREDRAW | CS_VREDRAW; @@ -95,9 +97,11 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine return -1; } + // Calculate full window size from client area size RECT rect = { 0, 0, gWidth, gHeight }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); + // Create window HWND hwnd = CreateWindowExW( 0, L"XCEngine_OpenGL_Test", @@ -113,6 +117,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine return -1; } + // Initialize OpenGL device with existing window OpenGLDevice device; if (!device.InitializeWithExistingWindow(hwnd)) { Log("[ERROR] Failed to initialize OpenGL device"); @@ -122,12 +127,15 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine ShowWindow(hwnd, nShowCmd); UpdateWindow(hwnd); + // Log OpenGL device info Log("[INFO] OpenGL Device: %S", device.GetDeviceInfo().renderer.c_str()); Log("[INFO] OpenGL Version: %S", device.GetDeviceInfo().version.c_str()); + // Create swap chain for rendering OpenGLSwapChain swapChain; swapChain.Initialize(hwnd, gWidth, gHeight); + // Main render loop MSG msg = {}; int frameCount = 0; const int targetFrameCount = 30; @@ -140,21 +148,27 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine TranslateMessage(&msg); DispatchMessageW(&msg); } else { + // Set viewport and clear color for each frame glViewport(0, 0, gWidth, gHeight); glClearColor(1.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Present the rendered frame swapChain.Present(0, 0); frameCount++; } } + // Take screenshot after target frame count is reached + // glFinish ensures all OpenGL commands are completed before reading pixels Log("[INFO] Reached target frame count %d - taking screenshot!", targetFrameCount); SaveScreenshotPPM("minimal.ppm", gWidth, gHeight); + // Shutdown in reverse order of initialization swapChain.Shutdown(); device.Shutdown(); Logger::Get().Shutdown(); Log("[INFO] OpenGL Integration Test Finished"); return 0; -} +} \ No newline at end of file