OpenGL: Refactor integration test and enable CTest framework

- Simplify OpenGL integration test structure
- Enable CTest registration for OpenGL tests
- Refactor test fixtures and device enumeration
- Minor code cleanup and improvements
This commit is contained in:
2026-03-20 19:05:50 +08:00
parent 45fd25dce3
commit 572e0e9bd5
29 changed files with 446 additions and 295 deletions

View File

@@ -24,7 +24,6 @@ target_include_directories(OpenGL_Minimal PRIVATE
target_link_libraries(OpenGL_Minimal PRIVATE
opengl32
${PACKAGE_DIR}/lib/glfw3.lib
XCEngine
)

View File

@@ -1,23 +1,23 @@
#include <windows.h>
#include <glad/glad.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <glad/glad.h>
#include <windows.h>
#include "XCEngine/RHI/OpenGL/OpenGLDevice.h"
#include "XCEngine/RHI/OpenGL/OpenGLSwapChain.h"
#include "XCEngine/Debug/Logger.h"
#include "XCEngine/Debug/ConsoleLogSink.h"
#include "XCEngine/Containers/String.h"
#pragma comment(lib, "opengl32.lib")
using namespace XCEngine::RHI;
using namespace XCEngine::Debug;
using namespace XCEngine::Containers;
static const int gWidth = 1280;
static const int gHeight = 720;
static HWND gHWND = nullptr;
static HDC gHDC = nullptr;
static HGLRC gHGLRC = nullptr;
void Log(const char* format, ...) {
char buffer[1024];
@@ -28,6 +28,38 @@ void Log(const char* format, ...) {
Logger::Get().Debug(LogCategory::Rendering, String(buffer));
}
void SaveScreenshotPPM(const char* filename, int width, int height) {
unsigned char* pixels = (unsigned char*)malloc(width * height * 3);
if (!pixels) {
Log("[ERROR] Failed to allocate pixel buffer");
return;
}
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
FILE* f = fopen(filename, "wb");
if (!f) {
Log("[ERROR] Failed to open file for screenshot: %s", filename);
free(pixels);
return;
}
fprintf(f, "P6\n%d %d\n255\n", width, height);
unsigned char* row = (unsigned char*)malloc(width * 3);
for (int y = height - 1; y >= 0; y--) {
memcpy(row, pixels + y * width * 3, width * 3);
fwrite(row, 1, width * 3, f);
}
free(row);
free(pixels);
fclose(f);
Log("[INFO] Screenshot saved to %s", filename);
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CLOSE:
@@ -37,109 +69,6 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
bool InitOpenGL() {
gHDC = GetDC(gHWND);
if (!gHDC) {
Log("[ERROR] Failed to get DC");
return false;
}
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
int pixelFormat = ChoosePixelFormat(gHDC, &pfd);
if (!pixelFormat) {
Log("[ERROR] Failed to choose pixel format");
return false;
}
if (!SetPixelFormat(gHDC, pixelFormat, &pfd)) {
Log("[ERROR] Failed to set pixel format");
return false;
}
gHGLRC = wglCreateContext(gHDC);
if (!gHGLRC) {
Log("[ERROR] Failed to create GL context");
return false;
}
if (!wglMakeCurrent(gHDC, gHGLRC)) {
Log("[ERROR] Failed to make GL context current");
return false;
}
if (!gladLoadGL()) {
Log("[ERROR] Failed to load OpenGL functions");
return false;
}
Log("[INFO] OpenGL initialized: %s", glGetString(GL_RENDERER));
Log("[INFO] OpenGL Version: %s", glGetString(GL_VERSION));
glViewport(0, 0, gWidth, gHeight);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
return true;
}
void ShutdownOpenGL() {
if (gHGLRC) {
wglDeleteContext(gHGLRC);
gHGLRC = nullptr;
}
if (gHDC) {
ReleaseDC(gHWND, gHDC);
gHDC = nullptr;
}
}
void SaveScreenshotPPM(const char* filename) {
unsigned char* pixels = (unsigned char*)malloc(gWidth * gHeight * 3);
if (!pixels) {
Log("[ERROR] Failed to allocate pixel buffer");
return;
}
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, gWidth, gHeight, GL_RGB, GL_UNSIGNED_BYTE, pixels);
FILE* f = fopen(filename, "wb");
if (!f) {
Log("[ERROR] Failed to open file for screenshot: %s", filename);
free(pixels);
return;
}
fprintf(f, "P6\n%d %d\n255\n", gWidth, gHeight);
unsigned char* row = (unsigned char*)malloc(gWidth * 3);
for (int y = gHeight - 1; y >= 0; y--) {
memcpy(row, pixels + y * gWidth * 3, gWidth * 3);
fwrite(row, 1, gWidth * 3, f);
}
free(row);
free(pixels);
fclose(f);
Log("[INFO] Screenshot saved to %s", filename);
}
void RenderFrame() {
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SwapBuffers(gHDC);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
Logger::Get().Initialize();
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
@@ -147,63 +76,86 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
Log("[INFO] OpenGL Integration Test Starting");
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"OpenGLTest";
wc.lpszClassName = L"XCEngine_OpenGL_Test";
if (!RegisterClassEx(&wc)) {
MessageBox(NULL, L"Failed to register window class", L"Error", MB_OK);
if (!RegisterClassExW(&wc)) {
Log("[ERROR] Failed to register window class");
return -1;
}
RECT rect = { 0, 0, gWidth, gHeight };
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
gHWND = CreateWindowEx(0, L"OpenGLTest", L"OpenGL Integration Test",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
HWND hwnd = CreateWindowExW(
0,
L"XCEngine_OpenGL_Test",
L"OpenGL Integration Test",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left, rect.bottom - rect.top,
NULL, NULL, hInstance, NULL);
NULL, NULL, hInstance, NULL
);
if (!gHWND) {
MessageBox(NULL, L"Failed to create window", L"Error", MB_OK);
RECT clientRect;
GetClientRect(hwnd, &clientRect);
fprintf(stderr, "[minimal] Window client area: %ldx%ld\n", clientRect.right, clientRect.bottom);
if (!hwnd) {
Log("[ERROR] Failed to create window");
return -1;
}
if (!InitOpenGL()) {
MessageBox(NULL, L"Failed to initialize OpenGL", L"Error", MB_OK);
OpenGLDevice device;
fprintf(stderr, "[minimal] About to call InitializeWithExistingWindow\n");
if (!device.InitializeWithExistingWindow(hwnd)) {
fprintf(stderr, "[minimal] InitializeWithExistingWindow returned false\n");
Log("[ERROR] Failed to initialize OpenGL device");
return -1;
}
fprintf(stderr, "[minimal] InitializeWithExistingWindow succeeded\n");
ShowWindow(gHWND, nShowCmd);
UpdateWindow(gHWND);
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
Log("[INFO] OpenGL Device: %S", device.GetDeviceInfo().renderer.c_str());
Log("[INFO] OpenGL Version: %S", device.GetDeviceInfo().version.c_str());
OpenGLSwapChain swapChain;
swapChain.Initialize(hwnd, gWidth, gHeight);
MSG msg = {};
int frameCount = 0;
const int targetFrameCount = 30;
while (true) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
DispatchMessageW(&msg);
} else {
RenderFrame();
glViewport(0, 0, gWidth, gHeight);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
swapChain.Present(0, 0);
frameCount++;
if (frameCount >= targetFrameCount) {
Log("[INFO] Reached target frame count %d - taking screenshot!", targetFrameCount);
SaveScreenshotPPM("minimal.ppm");
SaveScreenshotPPM("minimal.ppm", gWidth, gHeight);
break;
}
}
}
ShutdownOpenGL();
swapChain.Shutdown();
device.Shutdown();
Logger::Get().Shutdown();
Log("[INFO] OpenGL Integration Test Finished");

View File

@@ -8,8 +8,6 @@ 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
@@ -33,7 +31,6 @@ add_executable(opengl_engine_tests ${TEST_SOURCES})
target_link_libraries(opengl_engine_tests PRIVATE
opengl32
glfw3
XCEngine
GTest::gtest
GTest::gtest_main

View File

@@ -1,45 +1,107 @@
#include <glad/glad.h>
#include "OpenGLTestFixture.h"
using namespace XCEngine::RHI;
static bool s_glfwInitialized = false;
static bool s_gladInitialized = false;
static bool s_windowClassRegistered = false;
static const wchar_t kWindowClassName[] = L"XCEngine_Test_WindowClass";
void OpenGLTestFixture::SetUp() {
if (!s_glfwInitialized) {
if (!glfwInit()) {
GTEST_SKIP() << "Failed to initialize GLFW";
if (!s_windowClassRegistered) {
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = DefWindowProcW;
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpszClassName = kWindowClassName;
if (!RegisterClassExW(&wc)) {
GTEST_SKIP() << "Failed to register window class";
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);
s_glfwInitialized = true;
s_windowClassRegistered = true;
}
m_window = glfwCreateWindow(640, 480, "OpenGL Tests", nullptr, nullptr);
if (!m_window) {
GTEST_SKIP() << "Failed to create GLFW window";
m_hwnd = CreateWindowExW(
0,
kWindowClassName,
L"OpenGL Tests",
WS_POPUP,
0, 0,
640, 480,
nullptr,
nullptr,
GetModuleHandleW(nullptr),
nullptr
);
if (!m_hwnd) {
GTEST_SKIP() << "Failed to create window";
return;
}
glfwMakeContextCurrent(m_window);
ShowWindow(m_hwnd, SW_HIDE);
m_hdc = GetDC(m_hwnd);
if (!s_gladInitialized) {
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
GTEST_SKIP() << "Failed to initialize GLAD";
glfwDestroyWindow(m_window);
m_window = nullptr;
return;
}
s_gladInitialized = true;
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
int pixelFormat = ChoosePixelFormat(m_hdc, &pfd);
if (!pixelFormat) {
ReleaseDC(m_hwnd, m_hdc);
DestroyWindow(m_hwnd);
m_hwnd = nullptr;
GTEST_SKIP() << "Failed to choose pixel format";
return;
}
if (!SetPixelFormat(m_hdc, pixelFormat, &pfd)) {
ReleaseDC(m_hwnd, m_hdc);
DestroyWindow(m_hwnd);
m_hwnd = nullptr;
GTEST_SKIP() << "Failed to set pixel format";
return;
}
m_hglrc = wglCreateContext(m_hdc);
if (!m_hglrc) {
ReleaseDC(m_hwnd, m_hdc);
DestroyWindow(m_hwnd);
m_hwnd = nullptr;
GTEST_SKIP() << "Failed to create GL context";
return;
}
if (!wglMakeCurrent(m_hdc, m_hglrc)) {
wglDeleteContext(m_hglrc);
ReleaseDC(m_hwnd, m_hdc);
DestroyWindow(m_hwnd);
m_hglrc = nullptr;
m_hwnd = nullptr;
GTEST_SKIP() << "Failed to make GL context current";
return;
}
if (!gladLoadGLLoader((GLADloadproc)wglGetProcAddress)) {
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(m_hglrc);
ReleaseDC(m_hwnd, m_hdc);
DestroyWindow(m_hwnd);
m_hglrc = nullptr;
m_hwnd = nullptr;
GTEST_SKIP() << "Failed to load OpenGL functions";
return;
}
m_device = new OpenGLDevice();
m_device->CreateRenderWindow(640, 480, "Test Window", false);
m_device->InitializeWithExistingWindow(m_hwnd);
m_ownsWindow = true;
ClearGLErrors();
}
@@ -52,20 +114,31 @@ void OpenGLTestFixture::TearDown() {
m_device = nullptr;
}
if (m_window) {
glfwDestroyWindow(m_window);
m_window = nullptr;
if (m_hglrc) {
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(m_hglrc);
m_hglrc = nullptr;
}
if (m_hdc && m_hwnd) {
ReleaseDC(m_hwnd, m_hdc);
m_hdc = nullptr;
}
if (m_ownsWindow && m_hwnd) {
DestroyWindow(m_hwnd);
m_hwnd = nullptr;
}
}
void OpenGLTestFixture::MakeContextCurrent() {
if (m_window) {
glfwMakeContextCurrent(m_window);
if (m_hdc && m_hglrc) {
wglMakeCurrent(m_hdc, m_hglrc);
}
}
void OpenGLTestFixture::DoneContextCurrent() {
glfwMakeContextCurrent(nullptr);
wglMakeCurrent(nullptr, nullptr);
}
void OpenGLTestFixture::ClearGLErrors() {

View File

@@ -1,7 +1,7 @@
#pragma once
#include <windows.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <gtest/gtest.h>
#include "XCEngine/RHI/OpenGL/OpenGLDevice.h"
@@ -14,7 +14,9 @@ protected:
void SetUp() override;
void TearDown() override;
GLFWwindow* GetWindow() { return m_window; }
HWND GetWindow() { return m_hwnd; }
HDC GetDC() { return m_hdc; }
HGLRC GetContext() { return m_hglrc; }
void MakeContextCurrent();
void DoneContextCurrent();
@@ -24,8 +26,11 @@ protected:
void ResetGLState();
private:
GLFWwindow* m_window = nullptr;
HWND m_hwnd = nullptr;
HDC m_hdc = nullptr;
HGLRC m_hglrc = nullptr;
OpenGLDevice* m_device = nullptr;
bool m_ownsWindow = false;
};
} // namespace RHI

View File

@@ -23,7 +23,7 @@ TEST_F(OpenGLTestFixture, Device_CreateRenderWindow_DebugMode) {
TEST_F(OpenGLTestFixture, Device_InitializeWithExistingWindow) {
OpenGLDevice device;
GLFWwindow* existingWindow = GetWindow();
HWND existingWindow = GetWindow();
bool result = device.InitializeWithExistingWindow(existingWindow);