Add OpenGL test infrastructure - Phase 1: Device, Buffer, Fence tests (17 tests)

- Create test directory structure at tests/RHI/OpenGL/
- Implement OpenGLTestFixture with GLFW/GLAD initialization
- Add Device tests: CreateRenderWindow, InitializeWithExistingWindow, GetDeviceInfo, SwapBuffers, PollEvents
- Add Buffer tests: VertexBuffer, IndexBuffer, UniformBuffer, Dynamic, Bind/Unbind, Map/Unmap
- Add Fence tests: Initialize (signaled/unsignaled), Signal, Wait, IsSignaled, GetStatus
- Add CMakeLists.txt with proper GLFW/GLAD/GTest linking
- Create implementation plan document at docs/OpenGL测试实施计划.md
This commit is contained in:
2026-03-17 12:26:21 +08:00
parent f42a0795fb
commit 745f3ab225
8 changed files with 864 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
cmake_minimum_required(VERSION 3.15)
project(OpenGLEngineTests)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../.. ABSOLUTE)
find_package(OpenGL REQUIRED)
include_directories(${CMAKE_SOURCE_DIR}/tests/OpenGL/package/include/)
include_directories(${PROJECT_ROOT_DIR}/engine/include)
include_directories(${PROJECT_ROOT_DIR}/engine/src)
link_directories(${CMAKE_SOURCE_DIR}/tests/OpenGL/package/lib/)
find_package(GTest REQUIRED)
set(TEST_SOURCES
${CMAKE_SOURCE_DIR}/tests/OpenGL/package/src/glad.c
fixtures/OpenGLTestFixture.cpp
test_device.cpp
test_buffer.cpp
test_fence.cpp
)
add_executable(opengl_engine_tests ${TEST_SOURCES})
target_link_libraries(opengl_engine_tests PRIVATE
opengl32
glfw3
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(opengl_engine_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/fixtures
${PROJECT_ROOT_DIR}/engine/include
${PROJECT_ROOT_DIR}/engine/src
)
target_compile_definitions(opengl_engine_tests PRIVATE
TEST_RESOURCES_DIR="${PROJECT_ROOT_DIR}/tests/RHI/OpenGL/Res"
)
enable_testing()
add_test(NAME OpenGLEngineTests COMMAND opengl_engine_tests)

View File

@@ -0,0 +1,138 @@
#include "OpenGLTestFixture.h"
using namespace XCEngine::RHI;
GLFWwindow* OpenGLTestFixture::m_window = nullptr;
bool OpenGLTestFixture::m_contextInitialized = false;
OpenGLDevice* OpenGLTestFixture::m_device = nullptr;
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);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
GTEST_SKIP() << "Failed to initialize GLAD";
glfwDestroyWindow(m_window);
m_window = nullptr;
glfwTerminate();
return;
}
m_contextInitialized = true;
m_device = new OpenGLDevice();
m_device->CreateRenderWindow(640, 480, "Test Window", false);
}
void OpenGLTestFixture::TearDownTestSuite() {
if (m_device) {
delete m_device;
m_device = nullptr;
}
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() {
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
}
ResetGLState();
}
void OpenGLTestFixture::MakeContextCurrent() {
if (m_window) {
glfwMakeContextCurrent(m_window);
}
}
void OpenGLTestFixture::DoneContextCurrent() {
glfwMakeContextCurrent(nullptr);
}
void OpenGLTestFixture::ClearGLErrors() {
while (glGetError() != GL_NO_ERROR);
}
bool OpenGLTestFixture::CheckGLError(const char* file, int line) {
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
ADD_FAILURE() << "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";
}
}
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);
glBlendFunc(GL_ONE, GL_ZERO);
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <gtest/gtest.h>
#include "XCEngine/RHI/OpenGL/OpenGLDevice.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);
const char* GetGLErrorString(GLenum error);
void ResetGLState();
private:
static GLFWwindow* m_window;
static bool m_contextInitialized;
static OpenGLDevice* m_device;
};
} // namespace RHI
} // namespace XCEngine
#define GL_CLEAR_ERRORS() \
do { while (glGetError() != GL_NO_ERROR); } while(0)
#define GL_CHECK(call) \
do { \
call; \
ASSERT_TRUE(XCEngine::RHI::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)

View File

@@ -0,0 +1,89 @@
#include "fixtures/OpenGLTestFixture.h"
#include "XCEngine/RHI/OpenGL/OpenGLBuffer.h"
using namespace XCEngine::RHI;
TEST_F(OpenGLTestFixture, Buffer_Initialize_VertexBuffer) {
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);
buffer.Shutdown();
}
TEST_F(OpenGLTestFixture, Buffer_Initialize_IndexBuffer) {
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));
buffer.Shutdown();
}
TEST_F(OpenGLTestFixture, Buffer_Initialize_UniformBuffer) {
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);
buffer.Shutdown();
}
TEST_F(OpenGLTestFixture, Buffer_Initialize_Dynamic) {
OpenGLBuffer buffer;
bool result = buffer.Initialize(OpenGLBufferType::CopyRead, 256, nullptr, true);
ASSERT_TRUE(result);
EXPECT_TRUE(buffer.IsDynamic());
EXPECT_EQ(buffer.GetSize(), 256u);
buffer.Shutdown();
}
TEST_F(OpenGLTestFixture, Buffer_Bind_Unbind) {
OpenGLBuffer buffer;
buffer.InitializeVertexBuffer(nullptr, 16);
buffer.Bind();
GLint 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);
buffer.Shutdown();
}
TEST_F(OpenGLTestFixture, Buffer_Map_Unmap) {
OpenGLBuffer buffer;
buffer.Initialize(OpenGLBufferType::CopyRead, 256, nullptr, true);
void* mappedData = buffer.Map();
ASSERT_NE(mappedData, nullptr);
memset(mappedData, 0xAB, 256);
buffer.Unmap();
GLenum error = glGetError();
EXPECT_EQ(error, GL_NO_ERROR);
buffer.Shutdown();
}

View File

@@ -0,0 +1,63 @@
#include "fixtures/OpenGLTestFixture.h"
#include "XCEngine/RHI/OpenGL/OpenGLDevice.h"
using namespace XCEngine::RHI;
TEST_F(OpenGLTestFixture, Device_CreateRenderWindow_ValidParams) {
OpenGLDevice device;
bool result = device.CreateRenderWindow(800, 600, "Test Window", false);
ASSERT_TRUE(result);
ASSERT_NE(device.GetWindow(), nullptr);
}
TEST_F(OpenGLTestFixture, Device_CreateRenderWindow_DebugMode) {
OpenGLDevice device;
bool result = device.CreateRenderWindow(640, 480, "Debug Window", true);
ASSERT_TRUE(result);
ASSERT_NE(device.GetWindow(), nullptr);
}
TEST_F(OpenGLTestFixture, Device_InitializeWithExistingWindow) {
OpenGLDevice device;
GLFWwindow* existingWindow = GetWindow();
bool result = device.InitializeWithExistingWindow(existingWindow);
ASSERT_TRUE(result);
ASSERT_EQ(device.GetWindow(), existingWindow);
}
TEST_F(OpenGLTestFixture, Device_GetDeviceInfo_ReturnsValid) {
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, 3);
EXPECT_GE(info.minorVersion, 0);
}
TEST_F(OpenGLTestFixture, Device_SwapBuffers_NoErrors) {
OpenGLDevice device;
device.CreateRenderWindow(800, 600, "Test", false);
device.SwapBuffers();
GLenum error = glGetError();
EXPECT_EQ(error, GL_NO_ERROR);
}
TEST_F(OpenGLTestFixture, Device_PollEvents_ReturnsTrue) {
OpenGLDevice device;
device.CreateRenderWindow(800, 600, "Test", false);
bool result = device.PollEvents();
EXPECT_TRUE(result);
}

View File

@@ -0,0 +1,61 @@
#include "fixtures/OpenGLTestFixture.h"
#include "XCEngine/RHI/OpenGL/OpenGLFence.h"
using namespace XCEngine::RHI;
TEST_F(OpenGLTestFixture, Fence_Initialize_Unsignaled) {
OpenGLFence fence;
bool result = fence.Initialize(false);
ASSERT_TRUE(result);
EXPECT_EQ(fence.GetStatus(), FenceStatus::Unsignaled);
fence.Shutdown();
}
TEST_F(OpenGLTestFixture, Fence_Initialize_Signaled) {
OpenGLFence fence;
bool result = fence.Initialize(true);
ASSERT_TRUE(result);
fence.Shutdown();
}
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);
fence.Shutdown();
}
TEST_F(OpenGLTestFixture, Fence_IsSignaled_ReturnsState) {
OpenGLFence fence;
fence.Initialize(false);
fence.Signal(1);
fence.Wait(1);
EXPECT_TRUE(fence.IsSignaled());
fence.Shutdown();
}
TEST_F(OpenGLTestFixture, Fence_GetStatus_ReturnsCorrect) {
OpenGLFence fence;
fence.Initialize(false);
FenceStatus status = fence.GetStatus();
EXPECT_TRUE(status == FenceStatus::Signaled || status == FenceStatus::Unsignaled);
fence.Shutdown();
}