Files
XCEngine/docs/used/OpenGL后端测试设计.md
2026-03-29 01:36:53 +08:00

1187 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <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 错误检查辅助
```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<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 |
**测试代码示例**:
```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, &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` | 状态查询 |
**测试代码示例**:
```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<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
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日