Files
XCSDD/docs/plan/OpenGL后端测试设计.md
ssdfasd 58a83f445a fix: improve doc link navigation and tree display
- Fix link resolution with proper relative/absolute path handling
- Improve link styling with underline decoration
- Hide leaf nodes from tree, only show directories
- Fix log file path for packaged app
2026-03-19 12:44:08 +08:00

31 KiB
Raw Permalink Blame History

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 基础夹具

// fixtures/OpenGLTestFixture.h
#pragma once

#include <gtest/gtest.h>
#include <GLFW/glfw3.h>

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 错误检查辅助

// 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 检查宏

// 在头文件中定义检查宏
#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 数据更新

测试代码示例:

#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<GLint>(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

测试代码示例:

#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, &currentProgram);
    EXPECT_EQ(currentProgram, static_cast<GLint>(shader.GetID()));
    
    shader.Unbind();
    
    glGetIntegerv(GL_CURRENT_PROGRAM, &currentProgram);
    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 状态查询

测试代码示例:

#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 的基本功能:

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 集成测试

测试多个组件的协作:

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 渲染结果测试

渲染到帧缓冲并验证像素:

TEST(OpenGL_RenderTarget, ClearColor_VerifyPixelValue) {
    // 创建帧缓冲
    OpenGLRenderTargetView rtv;
    rtv.Initialize(0); // 默认帧缓冲
    
    // 清除为特定颜色
    rtv.Clear(0.25f, 0.5f, 0.75f, 1.0f);
    
    // 读取像素
    std::vector<unsigned char> 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_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 中添加:

# OpenGL Tests
add_subdirectory(RHI/OpenGL)

7. CI 集成

7.1 GitHub Actions (Linux)

# .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)

# .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 测试执行流程

# 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 测试着色器示例

// 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);
}
// 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 在测试中加载资源

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: 资源管理

  1. OpenGLTexture - 纹理资源
  2. OpenGLSampler - 采样器

Phase 3: 渲染管线

  1. OpenGLShader - 着色器编译
  2. OpenGLPipelineState - 管线状态
  3. OpenGLVertexArray - 顶点数组

Phase 4: 命令与视图

  1. OpenGLCommandList - 命令录制
  2. OpenGLRenderTargetView - 渲染目标
  3. OpenGLDepthStencilView - 深度模板

Phase 5: 窗口管理

  1. OpenGLSwapChain - 交换链

10. 特殊考虑

10.1 无头渲染

在 CI 环境中没有显示器,需要使用 offscreen 渲染:

// 使用 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 隔离着色器状态
  • 测试结束后清理所有绑定的对象
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 错误处理

// 检查所有 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日