# 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日