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)
This commit is contained in:
251
tests/RHI/OpenGL/TEST_SPEC.md
Normal file
251
tests/RHI/OpenGL/TEST_SPEC.md
Normal file
@@ -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} $<TARGET_FILE_DIR:OpenGL_Minimal>/run_integration_test.py
|
||||
$<TARGET_FILE:OpenGL_Minimal>
|
||||
minimal.ppm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/minimal/GT.ppm
|
||||
5
|
||||
WORKING_DIRECTORY $<TARGET_FILE_DIR:OpenGL_Minimal>
|
||||
)
|
||||
```
|
||||
|
||||
### 3.4 Golden Image 规范
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 格式 | PPM (P6) |
|
||||
| 命名 | `GT.ppm` 或 `GT_<test_name>.ppm` |
|
||||
| 阈值 | 默认 5% |
|
||||
| 存储位置 | `tests/RHI/OpenGL/integration/<test_name>/` |
|
||||
|
||||
### 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)
|
||||
@@ -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<ConsoleLogSink>());
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user