- 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
31 KiB
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, ¤tProgram);
EXPECT_EQ(currentProgram, static_cast<GLint>(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 |
状态查询 |
测试代码示例:
#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: 核心基础设施
- OpenGLDevice - 窗口和上下文管理
- OpenGLBuffer - 基础数据缓冲
- OpenGLFence - 同步基础
Phase 2: 资源管理
- OpenGLTexture - 纹理资源
- OpenGLSampler - 采样器
Phase 3: 渲染管线
- OpenGLShader - 着色器编译
- OpenGLPipelineState - 管线状态
- OpenGLVertexArray - 顶点数组
Phase 4: 命令与视图
- OpenGLCommandList - 命令录制
- OpenGLRenderTargetView - 渲染目标
- OpenGLDepthStencilView - 深度模板
Phase 5: 窗口管理
- 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日