diff --git a/docs/OpenGL后端测试设计.md b/docs/OpenGL后端测试设计.md new file mode 100644 index 00000000..664b2fd1 --- /dev/null +++ b/docs/OpenGL后端测试设计.md @@ -0,0 +1,1186 @@ +# OpenGL 后端测试设计 + +## 1. 概述 + +本文档描述 XCEngine OpenGL 渲染后端的测试框架设计。OpenGL 后端与 D3D12 后端有显著差异,主要体现在: + +- **窗口依赖**:OpenGL 需要 GLFW 窗口上下文才能执行任何图形操作 +- **对象模型**:使用 GL 句柄(unsigned int)而非 COM 接口 +- **状态管理**:OpenGL 是立即模式渲染,状态通过 GL 函数设置 +- **同步机制**:使用 GLsync 对象进行 GPU/CPU 同步 + +### 1.1 测试目标 + +- 验证 OpenGL 各组件 API 的正确性 +- 确保组件间的协作正常工作 +- 捕获 GL 错误和状态问题 +- 支持持续集成(CI)自动化测试 + +### 1.2 特殊挑战 + +| 挑战 | 说明 | 解决方案 | +|------|------|----------| +| 窗口依赖 | OpenGL 需要 GL 上下文 | 使用无头渲染(headless)或 offscreen 渲染 | +| CI 环境 | 无显示器无法创建窗口 | 使用 OSMesa 或 EGL offscreen 上下文 | +| GL 状态污染 | 全局 GL 状态影响测试 | 每个测试独立设置状态 + 状态重置 | + +### 1.3 与 D3D12 测试对比 + +| 特性 | D3D12 测试 | OpenGL 测试 | +|------|-------------|-------------| +| 设备创建 | D3D12CreateDevice | GLFW + glfwMakeContextCurrent | +| 错误检查 | HRESULT 返回值 | glGetError() | +| 对象类型 | COM 接口 (IUnknown) | GL 句柄 (unsigned int) | +| 资源清理 | Release() | glDelete*() | +| 同步原语 | ID3D12Fence | GLsync | +| 状态管理 | 显式状态对象 | 全局 GL 状态 | + +## 2. 测试目录结构 + +``` +tests/RHI/OpenGL/ +├── CMakeLists.txt # 测试构建配置 +├── fixtures/ +│ ├── OpenGLTestFixture.h # 基础测试夹具 +│ └── OpenGLTestFixture.cpp # 夹具实现 +├── test_device.cpp # OpenGLDevice 测试 +├── test_buffer.cpp # OpenGLBuffer 测试 +├── test_texture.cpp # OpenGLTexture 测试 +├── test_shader.cpp # OpenGLShader 测试 +├── test_pipeline_state.cpp # OpenGLPipelineState 测试 +├── test_vertex_array.cpp # OpenGLVertexArray 测试 +├── test_command_list.cpp # OpenGLCommandList 测试 +├── test_sampler.cpp # OpenGLSampler 测试 +├── test_fence.cpp # OpenGLFence 测试 +├── test_render_target_view.cpp # OpenGLRenderTargetView 测试 +├── test_depth_stencil_view.cpp # OpenGLDepthStencilView 测试 +└── test_swap_chain.cpp # OpenGLSwapChain 测试 +``` + +## 3. 测试夹具设计 + +### 3.1 基础夹具 + +```cpp +// fixtures/OpenGLTestFixture.h +#pragma once + +#include +#include + +namespace XCEngine { +namespace RHI { + +class OpenGLTestFixture : public ::testing::Test { +protected: + static void SetUpTestSuite(); + static void TearDownTestSuite(); + + void SetUp() override; + void TearDown() override; + + GLFWwindow* GetWindow() { return m_window; } + void MakeContextCurrent(); + void DoneContextCurrent(); + + void ClearGLErrors(); + bool CheckGLError(const char* file, int line); + +private: + static GLFWwindow* m_window; + static bool m_contextInitialized; +}; + +} // namespace RHI +} // namespace XCEngine +``` + +### 3.2 设计决策 + +**为什么使用 GLFW 窗口?** + +OpenGL 必须有有效的 GL 上下文才能执行。测试框架使用 GLFW 创建窗口并获取 GL 上下文。在 CI 环境中可能需要使用 OSMesa 或 offscreen 渲染。 + +### 3.3 GL 错误检查辅助 + +```cpp +// fixtures/OpenGLTestFixture.cpp +#include "OpenGLTestFixture.h" + +GLFWwindow* OpenGLTestFixture::m_window = nullptr; +bool OpenGLTestFixture::m_contextInitialized = false; + +void OpenGLTestFixture::SetUpTestSuite() { + if (!glfwInit()) { + GTEST_SKIP() << "Failed to initialize GLFW"; + return; + } + + // 创建隐藏窗口用于测试 + 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"; + glfwTerminate(); + return; + } + + glfwMakeContextCurrent(m_window); + m_contextInitialized = true; +} + +void OpenGLTestFixture::TearDownTestSuite() { + if (m_window) { + glfwDestroyWindow(m_window); + m_window = nullptr; + } + glfwTerminate(); + m_contextInitialized = false; +} + +void OpenGLTestFixture::SetUp() { + if (!m_window || !m_contextInitialized) { + GTEST_SKIP() << "OpenGL context not available"; + return; + } + + glfwMakeContextCurrent(m_window); + ClearGLErrors(); +} + +void OpenGLTestFixture::TearDown() { + // 确保没有未检查的 GL 错误 + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + // 记录但不一定失败,因为某些 GL 状态改变会产生预期内的错误 + } + + // 重置 GL 状态 + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glUseProgram(0); + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_SCISSOR_TEST); +} + +void OpenGLTestFixture::ClearGLErrors() { + while (glGetError() != GL_NO_ERROR); +} + +bool OpenGLTestFixture::CheckGLError(const char* file, int line) { + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + GTEST_MESSAGE_ << "OpenGL Error: " << error + << " (" << GetGLErrorString(error) << ")" + << " at " << file << ":" << line; + return false; + } + return true; +} + +const char* OpenGLTestFixture::GetGLErrorString(GLenum error) { + switch (error) { + case GL_NO_ERROR: return "GL_NO_ERROR"; + case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; + case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION"; + case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; + default: return "Unknown error"; + } +} +``` + +### 3.4 GL 检查宏 + +```cpp +// 在头文件中定义检查宏 +#define GL_CLEAR_ERRORS() \ + do { while (glGetError() != GL_NO_ERROR); } while(0) + +#define GL_CHECK(call) \ + do { \ + call; \ + ASSERT_TRUE(OpenGLTestFixture::CheckGLError(__FILE__, __LINE__)) \ + << "GL error after " << #call; \ + } while(0) + +#define GL_EXPECT_SUCCESS(call) \ + do { \ + call; \ + EXPECT_EQ(glGetError(), GL_NO_ERROR) \ + << "GL error after " << #call; \ + } while(0) +``` + +bool OpenGLTestFixture::CheckGLError(const char* file, int line) { + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + GTEST_MESSAGE_ << "OpenGL Error: " << error + << " at " << file << ":" << line; + return false; + } + return true; +} + +#define GL_CHECK() ASSERT_TRUE(CheckGLError(__FILE__, __LINE__)) +``` + +## 4. 组件测试详情 + +### 4.1 OpenGLDevice + +**文件**: `test_device.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `CreateRenderWindow_ValidParams` | 窗口创建成功 | +| 初始化 | `InitializeWithExistingWindow` | 现有窗口初始化 | +| 设备信息 | `GetDeviceInfo_ReturnsValid` | 供应商/渲染器信息 | +| 窗口操作 | `SwapBuffers_ClearsScreen` | 交换缓冲区 | +| 窗口操作 | `PollEvents_ProcessesInput` | 事件轮询 | +| 窗口操作 | `ShouldClose_ReturnsBool` | 关闭状态查询 | + +**测试代码示例**: + +```cpp +#include "fixtures/OpenGLTestFixture.h" + +TEST_F(OpenGLTestFixture, Device_CreateWindow_Success) { + OpenGLDevice device; + + bool result = device.CreateRenderWindow(800, 600, "Test Window", false); + + ASSERT_TRUE(result); + ASSERT_NE(device.GetWindow(), nullptr); +} + +TEST_F(OpenGLTestFixture, Device_GetDeviceInfo) { + OpenGLDevice device; + device.CreateRenderWindow(800, 600, "Test", false); + + const auto& info = device.GetDeviceInfo(); + + EXPECT_FALSE(info.vendor.empty()); + EXPECT_FALSE(info.renderer.empty()); + EXPECT_GE(info.majorVersion, 4); + EXPECT_GE(info.minorVersion, 0); +} + +TEST_F(OpenGLTestFixture, Device_PollEvents) { + OpenGLDevice device; + device.CreateRenderWindow(800, 600, "Test", false); + + bool result = device.PollEvents(); + EXPECT_TRUE(result); +} + +TEST_F(OpenGLTestFixture, Device_SwapBuffers) { + OpenGLDevice device; + device.CreateRenderWindow(800, 600, "Test", false); + + device.SwapBuffers(); + + GL_CHECK(); +} +``` + +### 4.2 OpenGLBuffer + +**文件**: `test_buffer.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_VertexBuffer` | 顶点缓冲创建 | +| 初始化 | `Initialize_IndexBuffer` | 索引缓冲创建 | +| 初始化 | `Initialize_UniformBuffer` | Uniform 缓冲创建 | +| 初始化 | `Initialize_Dynamic` | 动态缓冲创建 | +| 绑定 | `Bind_Unbind` | 缓冲绑定/解绑 | +| 绑定 | `BindBase_TargetIndex` | 缓冲绑定到目标索引 | +| 数据操作 | `Map_Unmap` | 数据映射操作 | +| 数据操作 | `SetData_UpdatesContent` | 数据更新 | + +**测试代码示例**: + +```cpp +#include "fixtures/OpenGLTestFixture.h" +#include "OpenGLBuffer.h" + +TEST_F(OpenGLTestFixture, Buffer_InitializeVertexBuffer) { + OpenGLBuffer buffer; + float vertices[] = { 0.0f, 0.0f, 0.5f, 0.5f }; + + bool result = buffer.InitializeVertexBuffer(vertices, sizeof(vertices)); + + ASSERT_TRUE(result); + EXPECT_NE(buffer.GetID(), 0u); + EXPECT_EQ(buffer.GetSize(), sizeof(vertices)); + EXPECT_EQ(buffer.GetType(), OpenGLBufferType::Vertex); +} + +TEST_F(OpenGLTestFixture, Buffer_InitializeIndexBuffer) { + OpenGLBuffer buffer; + unsigned int indices[] = { 0, 1, 2 }; + + bool result = buffer.InitializeIndexBuffer(indices, sizeof(indices)); + + ASSERT_TRUE(result); + EXPECT_NE(buffer.GetID(), 0u); + EXPECT_EQ(buffer.GetSize(), sizeof(indices)); +} + +TEST_F(OpenGLTestFixture, Buffer_InitializeUniformBuffer) { + OpenGLBuffer buffer; + float data[16] = {}; + + bool result = buffer.Initialize( + OpenGLBufferType::Uniform, + sizeof(data), + data, + true + ); + + ASSERT_TRUE(result); + EXPECT_TRUE(buffer.IsDynamic()); + EXPECT_EQ(buffer.GetType(), OpenGLBufferType::Uniform); +} + +TEST_F(OpenGLTestFixture, Buffer_BindUnbind) { + OpenGLBuffer buffer; + buffer.InitializeVertexBuffer(nullptr, 16); + + buffer.Bind(); + GLenum boundBuffer = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer); + EXPECT_EQ(boundBuffer, static_cast(buffer.GetID())); + + buffer.Unbind(); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer); + EXPECT_EQ(boundBuffer, 0); +} + +TEST_F(OpenGLTestFixture, Buffer_MapUnmap) { + OpenGLBuffer buffer; + buffer.Initialize(OpenGLBufferType::CopyRead, 256, nullptr, true); + + void* mappedData = buffer.Map(); + ASSERT_NE(mappedData, nullptr); + + memset(mappedData, 0xAB, 256); + + buffer.Unmap(); + + GL_CHECK(); +} +``` + +### 4.3 OpenGLTexture + +**文件**: `test_texture.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_2DTexture` | 2D 纹理创建 | +| 初始化 | `Initialize_3DTexture` | 3D 纹理创建 | +| 初始化 | `Initialize_CubeMap` | 立方体纹理创建 | +| 初始化 | `Initialize_FromFile` | 从文件加载纹理 | +| 绑定 | `Bind_Unbind` | 纹理绑定/解绑 | +| 绑定 | `BindImage_ReadWrite` | 图像绑定 | +| 生成 | `GenerateMipmap` | Mipmap 生成 | +| 参数 | `SetFiltering` | 过滤参数设置 | +| 参数 | `SetWrapping` | 环绕参数设置 | + +### 4.4 OpenGLShader + +**文件**: `test_shader.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 编译 | `Compile_VertexFragment` | 顶点和片段着色器编译 | +| 编译 | `Compile_WithGeometry` | 几何着色器编译 | +| 编译 | `Compile_Compute` | 计算着色器编译 | +| 编译 | `Compile_InvalidSource` | 无效源码编译失败 | +| 链接 | `Link_ValidProgram` | 程序链接成功 | +| 链接 | `Link_MissingStage` | 缺失阶段链接失败 | +| Uniform | `SetInt_SetFloat` | Uniform 设置 | +| Uniform | `SetVec3_SetMat4` | 向量/矩阵 Uniform | + +**测试代码示例**: + +```cpp +#include "fixtures/OpenGLTestFixture.h" +#include "OpenGLShader.h" + +TEST_F(OpenGLTestFixture, Shader_Compile_VertexFragment) { + const char* vertexSource = R"( + #version 460 core + layout(location = 0) in vec3 aPosition; + void main() { + gl_Position = vec4(aPosition, 1.0); + } + )"; + + const char* fragmentSource = R"( + #version 460 core + out vec4 FragColor; + void main() { + FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + )"; + + OpenGLShader shader; + bool result = shader.Compile(vertexSource, fragmentSource); + + ASSERT_TRUE(result); + EXPECT_TRUE(shader.IsValid()); + EXPECT_NE(shader.GetID(), 0u); +} + +TEST_F(OpenGLTestFixture, Shader_SetUniforms) { + OpenGLShader shader; + const char* vs = R"( + #version 460 core + void main() { gl_Position = vec4(0.0); } + )"; + const char* fs = R"( + #version 460 core + uniform int uIntValue; + uniform float uFloatValue; + uniform vec3 uVec3Value; + uniform mat4 uMat4Value; + out vec4 FragColor; + void main() { FragColor = vec4(1.0); } + )"; + shader.Compile(vs, fs); + + shader.Use(); + + shader.SetInt("uIntValue", 42); + shader.SetFloat("uFloatValue", 3.14f); + shader.SetVec3("uVec3Value", 1.0f, 2.0f, 3.0f); + + float mat[16] = {}; + mat[0] = 1.0f; mat[5] = 1.0f; mat[10] = 1.0f; mat[15] = 1.0f; + shader.SetMat4("uMat4Value", mat); + + GL_CHECK(); +} + +TEST_F(OpenGLTestFixture, Shader_Use_Unbind) { + OpenGLShader shader; + shader.Compile( + "void main() { gl_Position = vec4(0.0); }", + "void main() { }" + ); + + shader.Use(); + + GLint currentProgram = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); + EXPECT_EQ(currentProgram, static_cast(shader.GetID())); + + shader.Unbind(); + + glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); + EXPECT_EQ(currentProgram, 0); +} +``` + +### 4.5 OpenGLPipelineState + +**文件**: `test_pipeline_state.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 深度状态 | `SetDepthStencilState` | 深度模板状态 | +| 深度状态 | `ApplyDepthStencil` | 应用深度状态 | +| 混合状态 | `SetBlendState` | 混合状态设置 | +| 混合状态 | `ApplyBlend` | 应用混合状态 | +| 光栅化 | `SetRasterizerState` | 光栅化状态 | +| 视口 | `SetViewport` | 视口设置 | +| 裁剪 | `SetScissor` | 裁剪矩形 | +| 清除 | `SetClearColor` | 清除颜色 | +| 清除 | `Clear_ColorDepthStencil` | 清除颜色/深度/模板 | + +### 4.6 OpenGLVertexArray + +**文件**: `test_vertex_array.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_CreatesVAO` | VAO 创建 | +| 属性 | `AddVertexBuffer` | 顶点缓冲添加 | +| 属性 | `SetIndexBuffer` | 索引缓冲设置 | +| 绑定 | `Bind_Unbind` | VAO 绑定/解绑 | +| 属性 | `GetIndexCount` | 索引数量 | + +### 4.7 OpenGLCommandList + +**文件**: `test_command_list.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 清除 | `Clear_ColorBuffer` | 清除颜色缓冲 | +| 清除 | `Clear_DepthStencil` | 清除深度/模板 | +| 管线 | `SetPipelineState` | 设置管线状态 | +| 顶点 | `SetVertexBuffer` | 设置顶点缓冲 | +| 索引 | `SetIndexBuffer` | 设置索引缓冲 | +| 绘制 | `Draw_Triangles` | 绘制三角形 | +| 绘制 | `DrawIndexed_Indices` | 索引绘制 | +| 绘制 | `DrawInstanced` | 实例化绘制 | +| 计算 | `Dispatch_ComputeShader` | 计算着色器分发 | +| 同步 | `MemoryBarrier` | 内存屏障 | +| 纹理 | `BindTexture` | 纹理绑定 | + +### 4.8 OpenGLSampler + +**文件**: `test_sampler.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_DefaultDesc` | 默认采样器创建 | +| 初始化 | `Initialize_CustomDesc` | 自定义采样器创建 | +| 绑定 | `Bind_Unbind` | 采样器绑定/解绑 | +| 参数 | `GetID_ReturnsValid` | 采样器 ID 有效 | + +### 4.9 OpenGLFence + +**文件**: `test_fence.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_Unsignaled` | 未 signaled 状态创建 | +| 初始化 | `Initialize_Signaled` | signaled 状态创建 | +| 同步 | `Signal_SetsValue` | Signal 设置值 | +| 同步 | `Wait_Blocks` | Wait 阻塞等待 | +| 状态 | `IsSignaled_ReturnsState` | signaled 状态查询 | +| 状态 | `GetStatus_ReturnsCorrect` | 状态查询 | + +**测试代码示例**: + +```cpp +#include "fixtures/OpenGLTestFixture.h" +#include "OpenGLFence.h" + +TEST_F(OpenGLTestFixture, Fence_Initialize_Unsignaled) { + OpenGLFence fence; + + bool result = fence.Initialize(false); + + ASSERT_TRUE(result); + EXPECT_EQ(fence.GetStatus(), FenceStatus::Unsignaled); +} + +TEST_F(OpenGLTestFixture, Fence_Initialize_Signaled) { + OpenGLFence fence; + + bool result = fence.Initialize(true); + + ASSERT_TRUE(result); + // 初始 signaled 状态可能立即完成 +} + +TEST_F(OpenGLTestFixture, Fence_Signal_Wait) { + OpenGLFence fence; + fence.Initialize(false); + + fence.Signal(1); + + // 等待完成 + fence.Wait(1); + + EXPECT_TRUE(fence.IsSignaled()); + EXPECT_EQ(fence.GetCompletedValue(), 1u); +} + +TEST_F(OpenGLTestFixture, Fence_GetStatus) { + OpenGLFence fence; + fence.Initialize(false); + + FenceStatus status = fence.GetStatus(); + EXPECT_TRUE(status == FenceStatus::Signaled || + status == FenceStatus::Unsignaled); +} + +TEST_F(OpenGLFence, Fence_MultipleSignals) { + OpenGLFence fence; + fence.Initialize(false); + + fence.Signal(1); + fence.Wait(1); + + fence.Signal(2); + fence.Wait(2); + + EXPECT_EQ(fence.GetCompletedValue(), 2u); +} +``` + +### 4.10 OpenGLRenderTargetView + +**文件**: `test_render_target_view.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_Texture2D` | 2D 纹理 RTV 创建 | +| 初始化 | `Initialize_Cubemap` | 立方体 RTV 创建 | +| 绑定 | `Bind_Unbind` | RTV 绑定/解绑 | +| 清除 | `Clear_Color` | 清除颜色 | + +### 4.11 OpenGLDepthStencilView + +**文件**: `test_depth_stencil_view.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_Texture2D` | 2D 纹理 DSV 创建 | +| 初始化 | `Initialize_Cubemap` | 立方体 DSV 创建 | +| 绑定 | `Bind_Unbind` | DSV 绑定/解绑 | +| 清除 | `ClearDepthStencil` | 清除深度/模板 | + +### 4.12 OpenGLSwapChain + +**文件**: `test_swap_chain.cpp` + +**可测试 API 点**: + +| 测试类别 | 测试用例 | 验证内容 | +|----------|----------|----------| +| 初始化 | `Initialize_Window` | 交换链初始化 | +| 初始化 | `Initialize_WithSize` | 指定尺寸初始化 | +| 显示 | `Present_VSync` | 垂直同步显示 | +| 显示 | `Present_Immediate` | 立即显示 | +| 调整 | `Resize_ChangesSize` | 调整大小 | +| 属性 | `GetWidth_GetHeight` | 宽高查询 | + +## 5. 测试分类 + +### 5.1 单元测试 + +测试单一 API 的基本功能: + +```cpp +TEST(OpenGL_Buffer, Initialize_VertexBuffer_ReturnsValidID) { + OpenGLBuffer buffer; + float data[] = { 1.0f, 2.0f, 3.0f }; + + bool result = buffer.InitializeVertexBuffer(data, sizeof(data)); + + ASSERT_TRUE(result); + ASSERT_NE(buffer.GetID(), 0u); +} +``` + +### 5.2 集成测试 + +测试多个组件的协作: + +```cpp +TEST(OpenGL_Buffer, UploadToTexture_DataIntegrity) { + // 创建纹理 + OpenGLTexture texture; + texture.Initialize2D(64, 64, 4, nullptr, false); + + // 创建缓冲并上传数据 + OpenGLBuffer buffer; + buffer.Initialize(OpenGLBufferType::CopyRead, 64 * 64 * 4, nullptr, false); + + // 使用命令列表复制 + OpenGLCommandList cmdList; + cmdList.BindTexture(GL_TEXTURE_2D, 0, texture.GetID()); + + GL_CHECK(); +} +``` + +### 5.3 渲染结果测试 + +渲染到帧缓冲并验证像素: + +```cpp +TEST(OpenGL_RenderTarget, ClearColor_VerifyPixelValue) { + // 创建帧缓冲 + OpenGLRenderTargetView rtv; + rtv.Initialize(0); // 默认帧缓冲 + + // 清除为特定颜色 + rtv.Clear(0.25f, 0.5f, 0.75f, 1.0f); + + // 读取像素 + std::vector pixels(4); + glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + + // 验证 + EXPECT_NEAR(pixels[0], 64, 2); // R + EXPECT_NEAR(pixels[1], 128, 2); // G + EXPECT_NEAR(pixels[2], 192, 2); // B +} +``` + +## 6. 构建配置 + +### 6.1 CMakeLists.txt + +```cmake +cmake_minimum_required(VERSION 3.15) + +project(OpenGLTests) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# 查找 OpenGL +find_package(OpenGL REQUIRED) + +# 查找 GLFW +find_package(GLFW3 REQUIRED) + +# Google Test +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://gitee.com/mirrors/googletest.git + GIT_TAG v1.14.0 +) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +enable_testing() + +# 测试源文件 +set(TEST_SOURCES + test_device.cpp + test_buffer.cpp + test_texture.cpp + test_shader.cpp + test_pipeline_state.cpp + test_vertex_array.cpp + test_command_list.cpp + test_sampler.cpp + test_fence.cpp + test_render_target_view.cpp + test_depth_stencil_view.cpp + test_swap_chain.cpp +) + +# 可执行文件 +add_executable(opengl_tests ${TEST_SOURCES}) + +# 链接库 +target_link_libraries(opengl_tests PRIVATE + OpenGL::GL + GLFW3::GLFW + XCEngine + GTest::gtest + GTest::gtest_main +) + +# 包含目录 +target_include_directories(opengl_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine/include + ${CMAKE_CURRENT_SOURCE_DIR}/fixtures +) + +# 编译定义 +target_compile_definitions(opengl_tests PRIVATE + GL_GLEXT_PROTOTYPES +) + +# 测试资源路径 +target_compile_definitions(opengl_tests PRIVATE + TEST_RESOURCES_DIR="${CMAKE_SOURCE_DIR}/tests/RHI/OpenGL/Res" +) + +add_test(NAME OpenGLTests COMMAND opengl_tests) +``` + +### 6.2 tests/CMakeLists.txt 更新 + +在 `tests/CMakeLists.txt` 中添加: + +```cmake +# OpenGL Tests +add_subdirectory(RHI/OpenGL) +``` + +## 7. CI 集成 + +### 7.1 GitHub Actions (Linux) + +```yaml +# .github/workflows/opengl-tests.yml +name: OpenGL Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libgl1-mesa-dev \ + libglfw3-dev \ + libglm-dev + + - name: Configure + run: cmake -B build -S . -G "Unix Makefiles" + + - name: Build + run: cmake --build build --config Debug + + - name: Run Tests + run: ctest --test-dir build -C Debug --output-on-failure +``` + +### 7.2 GitHub Actions (Windows) + +```yaml +# .github/workflows/opengl-tests-windows.yml +name: OpenGL Tests (Windows) + +on: [push, pull_request] + +jobs: + test: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build -S . -G "Visual Studio 17 2022" + + - name: Build + run: cmake --build build --config Debug + + - name: Run Tests + run: ctest --test-dir build -C Debug --output-on-failure +``` + +### 7.3 测试执行流程 + +```bash +# 1. 配置项目 +cmake -B build -S . -G "Unix Makefiles" + +# 2. 编译测试 +cmake --build build --config Debug + +# 3. 运行测试 +./build/tests/RHI/OpenGL/Debug/opengl_tests.exe + +# 或者使用 CTest +ctest --test-dir build -C Debug --output-on-failure + +# 4. 运行特定测试 +./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_filter=OpenGL_Buffer.* + +# 5. 详细输出 +./build/tests/RHI/OpenGL/Debug/opengl_tests.exe --gtest_also_run_disabled_tests --gtest_print_time=1 +``` + +### 7.4 测试输出示例 + +``` +[==========] Running 54 tests from 1 test suite. +[----------] Global test environment set-up. +[----------] 54 tests from OpenGLTestFixture +[ RUN ] OpenGLTestFixture.Buffer_InitializeVertexBuffer +[ OK ] OpenGLTestFixture.Buffer_InitializeVertexBuffer (12 ms) +[ RUN ] OpenGLTestFixture.Buffer_InitializeIndexBuffer +[ OK ] OpenGLTestFixture.Buffer_InitializeIndexBuffer (8 ms) +... +[----------] Global test environment tear-down +[==========] 54 tests from 1 test suite ran. +[ PASSED ] 54 tests. +``` + +## 8. 测试覆盖矩阵 + +| 组件 | 单元测试 | 集成测试 | 渲染测试 | 性能测试 | +|------|:--------:|:--------:|:--------:|:--------:| +| OpenGLDevice | ✓ | - | - | ✓ | +| OpenGLBuffer | ✓ | ✓ | - | ✓ | +| OpenGLTexture | ✓ | ✓ | ✓ | ✓ | +| OpenGLShader | ✓ | - | - | - | +| OpenGLPipelineState | ✓ | - | - | - | +| OpenGLVertexArray | ✓ | ✓ | - | - | +| OpenGLCommandList | ✓ | ✓ | - | - | +| OpenGLSampler | ✓ | - | - | - | +| OpenGLFence | ✓ | ✓ | - | - | +| OpenGLRenderTargetView | ✓ | - | ✓ | - | +| OpenGLDepthStencilView | ✓ | - | ✓ | - | +| OpenGLSwapChain | - | - | - | - | + +## 9. 测试资源文件 + +### 9.1 资源目录结构 + +``` +tests/RHI/OpenGL/ +├── Res/ +│ ├── Shader/ +│ │ ├── simple_vs.glsl # 简单顶点着色器 +│ │ ├── simple_fs.glsl # 简单片段着色器 +│ │ ├── compute_cs.glsl # 计算着色器 +│ │ └── quad_vs.glsl # Quad 顶点着色器 +│ ├── Texture/ +│ │ ├── test_256x256.png # 测试纹理 +│ │ └── cube_pos_x.jpg # 立方体纹理面 +│ └── Data/ +│ ├── triangle_verts.bin # 三角形顶点数据 +│ └── quad_verts.bin # Quad 顶点数据 +└── ... +``` + +### 9.2 测试着色器示例 + +```glsl +// Res/Shader/simple_vs.glsl +#version 460 core +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoord; + +layout(std140, binding = 0) uniform Camera { + mat4 uModelViewProjection; + mat4 uModel; + mat3 uNormalMatrix; +}; + +out vec3 vPosition; +out vec3 vNormal; +out vec2 vTexCoord; + +void main() { + vPosition = (uModel * vec4(aPosition, 1.0)).xyz; + vNormal = normalize(uNormalMatrix * aNormal); + vTexCoord = aTexCoord; + gl_Position = uModelViewProjection * vec4(aPosition, 1.0); +} +``` + +```glsl +// Res/Shader/simple_fs.glsl +#version 460 core +in vec3 vPosition; +in vec3 vNormal; +in vec2 vTexCoord; + +uniform vec3 uLightDirection; +uniform vec3 uLightColor; +uniform vec3 uAmbientColor; +uniform sampler2D uTexture; + +out vec4 FragColor; + +void main() { + vec3 normal = normalize(vNormal); + float diff = max(dot(normal, uLightDirection), 0.0); + vec3 diffuse = diff * uLightColor; + vec3 ambient = uAmbientColor; + + vec4 texColor = texture(uTexture, vTexCoord); + FragColor = vec4(ambient + diffuse, 1.0) * texColor; +} +``` + +### 9.3 在测试中加载资源 + +```cpp +TEST_F(OpenGLTestFixture, Shader_CompileFromFile) { + OpenGLShader shader; + + std::string vertexPath = std::string(TEST_RESOURCES_DIR) + "/Shader/simple_vs.glsl"; + std::string fragmentPath = std::string(TEST_RESOURCES_DIR) + "/Shader/simple_fs.glsl"; + + // 加载文件内容并编译 + // ... +} + +TEST_F(OpenGLTestFixture, Texture_LoadFromFile) { + OpenGLTexture texture; + + std::string texturePath = std::string(TEST_RESOURCES_DIR) + "/Texture/test_256x256.png"; + + bool result = texture.LoadFromFile(texturePath.c_str()); + + ASSERT_TRUE(result); + EXPECT_EQ(texture.GetWidth(), 256); + EXPECT_EQ(texture.GetHeight(), 256); +} +``` + +## 9. 实现优先级 + +### Phase 1: 核心基础设施 + +1. **OpenGLDevice** - 窗口和上下文管理 +2. **OpenGLBuffer** - 基础数据缓冲 +3. **OpenGLFence** - 同步基础 + +### Phase 2: 资源管理 + +4. **OpenGLTexture** - 纹理资源 +5. **OpenGLSampler** - 采样器 + +### Phase 3: 渲染管线 + +6. **OpenGLShader** - 着色器编译 +7. **OpenGLPipelineState** - 管线状态 +8. **OpenGLVertexArray** - 顶点数组 + +### Phase 4: 命令与视图 + +9. **OpenGLCommandList** - 命令录制 +10. **OpenGLRenderTargetView** - 渲染目标 +11. **OpenGLDepthStencilView** - 深度模板 + +### Phase 5: 窗口管理 + +12. **OpenGLSwapChain** - 交换链 + +## 10. 特殊考虑 + +### 10.1 无头渲染 + +在 CI 环境中没有显示器,需要使用 offscreen 渲染: + +```cpp +// 使用 OSMesa 创建 offscreen 上下文 +#ifdef __linux__ + glfwWindowHint(GLFW_PLATFORM, GLFW_PLATFORM_OSMESA); +#endif + +// 或使用 Mesa 的软件渲染llvmpipe +// 运行测试时设置环境变量: +// MESA_GL_VERSION_OVERRIDE=4.6 MESA_GALLIUM_DRIVER=llvmpipe +``` + +### 10.2 GL 状态隔离 + +OpenGL 状态是全局的,测试需要注意: + +- 每个测试开始时重置 GL 状态 +- 使用独立的 VAO 隔离顶点数组状态 +- 使用独立的 Program 隔离着色器状态 +- 测试结束后清理所有绑定的对象 + +```cpp +void OpenGLTestFixture::ResetGLState() { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glUseProgram(0); + + glBindVertexArray(0); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + glActiveTexture(GL_TEXTURE0); + for (int i = 0; i < 16; ++i) { + glBindTexture(GL_TEXTURE_2D, 0); + } + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_SCISSOR_TEST); + + glDepthFunc(GL_LESS); + glStencilFunc(GL_ALWAYS, 0, 0xFF, 0xFF); + glBlendFunc(GL_ONE, GL_ZERO); +} +``` + +### 10.3 错误处理 + +```cpp +// 检查所有 GL 调用后的错误 +#define GL_CALL(call) \ + call; \ + CheckGLError(__FILE__, __LINE__, #call) + +// 期望成功的调用 +#define GL_EXPECT_SUCCESS(call) \ + do { \ + call; \ + EXPECT_EQ(glGetError(), GL_NO_ERROR) \ + << "GL error after " << #call; \ + } while(0) + +// 检查特定错误 +#define GL_EXPECT_ERROR(expectedError, call) \ + do { \ + glGetError(); /* clear existing */ \ + call; \ + EXPECT_EQ(glGetError(), expectedError) \ + << "Expected " << #expectedError << " after " << #call; \ + } while(0) +``` + +### 10.4 跨平台考虑 + +| 平台 | 特殊处理 | +|------|----------| +| Windows | 使用 WGL 创建 GL 上下文 | +| Linux | 使用 GLX 或 Mesa 软件渲染 | +| macOS | 使用 Cocoa/NSOpenGL(需要特殊处理) | +| CI/无头 | 使用 OSMesa 或 EGL offscreen | + +## 11. 后续改进 + +- [ ] 实现 Phase 1 核心基础设施测试 +- [ ] 实现 Phase 2 资源管理测试 +- [ ] 实现 Phase 3 渲染管线测试 +- [ ] 实现 Phase 4 命令与视图测试 +- [ ] 实现 Phase 5 窗口管理测试 +- [ ] 添加资源泄漏检测工具 +- [ ] 添加性能基准测试 +- [ ] 配置 CI 自动测试 +- [ ] 支持 macOS 测试 + +--- + +**文档版本**:1.1 +**创建日期**:2026年3月17日 +**最后更新**:2026年3月17日