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) {
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
|
||||||
|
// Initialize logger
|
||||||
Logger::Get().Initialize();
|
Logger::Get().Initialize();
|
||||||
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
|
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
|
||||||
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
||||||
|
|
||||||
Log("[INFO] OpenGL Integration Test Starting");
|
Log("[INFO] OpenGL Integration Test Starting");
|
||||||
|
|
||||||
|
// Register window class
|
||||||
WNDCLASSEXW wc = {};
|
WNDCLASSEXW wc = {};
|
||||||
wc.cbSize = sizeof(WNDCLASSEXW);
|
wc.cbSize = sizeof(WNDCLASSEXW);
|
||||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||||
@@ -95,9 +97,11 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate full window size from client area size
|
||||||
RECT rect = { 0, 0, gWidth, gHeight };
|
RECT rect = { 0, 0, gWidth, gHeight };
|
||||||
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
|
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
|
||||||
|
|
||||||
|
// Create window
|
||||||
HWND hwnd = CreateWindowExW(
|
HWND hwnd = CreateWindowExW(
|
||||||
0,
|
0,
|
||||||
L"XCEngine_OpenGL_Test",
|
L"XCEngine_OpenGL_Test",
|
||||||
@@ -113,6 +117,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize OpenGL device with existing window
|
||||||
OpenGLDevice device;
|
OpenGLDevice device;
|
||||||
if (!device.InitializeWithExistingWindow(hwnd)) {
|
if (!device.InitializeWithExistingWindow(hwnd)) {
|
||||||
Log("[ERROR] Failed to initialize OpenGL device");
|
Log("[ERROR] Failed to initialize OpenGL device");
|
||||||
@@ -122,12 +127,15 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
ShowWindow(hwnd, nShowCmd);
|
ShowWindow(hwnd, nShowCmd);
|
||||||
UpdateWindow(hwnd);
|
UpdateWindow(hwnd);
|
||||||
|
|
||||||
|
// Log OpenGL device info
|
||||||
Log("[INFO] OpenGL Device: %S", device.GetDeviceInfo().renderer.c_str());
|
Log("[INFO] OpenGL Device: %S", device.GetDeviceInfo().renderer.c_str());
|
||||||
Log("[INFO] OpenGL Version: %S", device.GetDeviceInfo().version.c_str());
|
Log("[INFO] OpenGL Version: %S", device.GetDeviceInfo().version.c_str());
|
||||||
|
|
||||||
|
// Create swap chain for rendering
|
||||||
OpenGLSwapChain swapChain;
|
OpenGLSwapChain swapChain;
|
||||||
swapChain.Initialize(hwnd, gWidth, gHeight);
|
swapChain.Initialize(hwnd, gWidth, gHeight);
|
||||||
|
|
||||||
|
// Main render loop
|
||||||
MSG msg = {};
|
MSG msg = {};
|
||||||
int frameCount = 0;
|
int frameCount = 0;
|
||||||
const int targetFrameCount = 30;
|
const int targetFrameCount = 30;
|
||||||
@@ -140,17 +148,23 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
|||||||
TranslateMessage(&msg);
|
TranslateMessage(&msg);
|
||||||
DispatchMessageW(&msg);
|
DispatchMessageW(&msg);
|
||||||
} else {
|
} else {
|
||||||
|
// Set viewport and clear color for each frame
|
||||||
glViewport(0, 0, gWidth, gHeight);
|
glViewport(0, 0, gWidth, gHeight);
|
||||||
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Present the rendered frame
|
||||||
swapChain.Present(0, 0);
|
swapChain.Present(0, 0);
|
||||||
frameCount++;
|
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);
|
Log("[INFO] Reached target frame count %d - taking screenshot!", targetFrameCount);
|
||||||
SaveScreenshotPPM("minimal.ppm", gWidth, gHeight);
|
SaveScreenshotPPM("minimal.ppm", gWidth, gHeight);
|
||||||
|
|
||||||
|
// Shutdown in reverse order of initialization
|
||||||
swapChain.Shutdown();
|
swapChain.Shutdown();
|
||||||
device.Shutdown();
|
device.Shutdown();
|
||||||
Logger::Get().Shutdown();
|
Logger::Get().Shutdown();
|
||||||
|
|||||||
Reference in New Issue
Block a user