Fix OpenGL device initialization and file shaders

This commit is contained in:
2026-03-26 02:07:21 +08:00
parent 10ee1fa3fa
commit c47e871c5a
5 changed files with 310 additions and 42 deletions

View File

@@ -76,12 +76,18 @@ public:
private:
friend class OpenGLSwapChain;
bool AttachWindow(HWND hwnd);
bool ConfigureWindowPixelFormat(HDC hdc);
bool CreateContextForCurrentWindow();
void DestroyOwnedWindow();
HDC GetPresentationDC() const { return m_hdc; }
OpenGLTextureUnitAllocator* GetTextureUnitAllocator() { return m_textureUnitAllocator.get(); }
OpenGLUniformBufferManager* GetUniformBufferManager() { return m_uniformBufferManager.get(); }
void SwapBuffers();
HWND m_hwnd = nullptr;
HWND m_ownedWindow = nullptr;
HDC m_hdc = nullptr;
HGLRC m_hglrc = nullptr;
RHIDeviceInfo m_deviceInfo;

View File

@@ -38,6 +38,7 @@ static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = nullptr;
#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x0001
#define XCE_OPENGL_HIDDEN_WINDOW_CLASS L"XCEngineOpenGLHiddenWindow"
namespace XCEngine {
namespace RHI {
@@ -120,6 +121,31 @@ FramebufferAttachmentType ResolveDepthAttachmentType(Format format) {
: FramebufferAttachmentType::Depth;
}
HWND CreateHiddenOpenGLWindow() {
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_OWNDC;
wc.lpfnWndProc = DefWindowProcW;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = XCE_OPENGL_HIDDEN_WINDOW_CLASS;
const ATOM atom = RegisterClassExW(&wc);
if (atom == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) {
return nullptr;
}
return CreateWindowExW(
0,
XCE_OPENGL_HIDDEN_WINDOW_CLASS,
L"XCEngineOpenGLHiddenWindow",
WS_OVERLAPPED,
0, 0, 1, 1,
nullptr,
nullptr,
wc.hInstance,
nullptr);
}
} // namespace
OpenGLDevice::OpenGLDevice()
@@ -136,28 +162,92 @@ OpenGLDevice::~OpenGLDevice() {
}
bool OpenGLDevice::Initialize(const RHIDeviceDesc& desc) {
(void)desc;
if (m_initialized) {
return true;
}
return false;
m_ownedWindow = CreateHiddenOpenGLWindow();
if (!m_ownedWindow) {
return false;
}
if (!AttachWindow(m_ownedWindow)) {
DestroyOwnedWindow();
return false;
}
return true;
}
bool OpenGLDevice::InitializeWithExistingWindow(HWND hwnd) {
if (m_initialized) {
return true;
return hwnd == m_hwnd ? true : AttachWindow(hwnd);
}
if (!hwnd) {
return false;
}
m_hwnd = hwnd;
m_hdc = ::GetDC(m_hwnd);
if (!m_hdc) {
return AttachWindow(hwnd);
}
bool OpenGLDevice::AttachWindow(HWND hwnd) {
if (!hwnd) {
return false;
}
HDC newDC = ::GetDC(hwnd);
if (!newDC) {
return false;
}
if (!ConfigureWindowPixelFormat(newDC)) {
ReleaseDC(hwnd, newDC);
return false;
}
if (m_hglrc != nullptr) {
if (!wglMakeCurrent(newDC, m_hglrc)) {
ReleaseDC(hwnd, newDC);
return false;
}
if (m_hdc && m_hwnd) {
ReleaseDC(m_hwnd, m_hdc);
}
m_hwnd = hwnd;
m_hdc = newDC;
if (m_ownedWindow != nullptr && m_ownedWindow != hwnd) {
DestroyOwnedWindow();
}
return true;
}
m_hwnd = hwnd;
m_hdc = newDC;
if (!CreateContextForCurrentWindow()) {
ReleaseDC(hwnd, newDC);
m_hdc = nullptr;
m_hwnd = nullptr;
return false;
}
return true;
}
bool OpenGLDevice::ConfigureWindowPixelFormat(HDC hdc) {
if (!hdc) {
return false;
}
if (GetPixelFormat(hdc) != 0) {
return true;
}
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
@@ -168,23 +258,25 @@ bool OpenGLDevice::InitializeWithExistingWindow(HWND hwnd) {
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
int pixelFormat = ChoosePixelFormat(m_hdc, &pfd);
int pixelFormat = ChoosePixelFormat(hdc, &pfd);
if (!pixelFormat) {
ReleaseDC(m_hwnd, m_hdc);
m_hdc = nullptr;
return false;
}
if (!SetPixelFormat(m_hdc, pixelFormat, &pfd)) {
ReleaseDC(m_hwnd, m_hdc);
m_hdc = nullptr;
if (!SetPixelFormat(hdc, pixelFormat, &pfd)) {
return false;
}
return true;
}
bool OpenGLDevice::CreateContextForCurrentWindow() {
if (m_hdc == nullptr || m_hwnd == nullptr) {
return false;
}
HGLRC tempRC = wglCreateContext(m_hdc);
if (!tempRC) {
ReleaseDC(m_hwnd, m_hdc);
m_hdc = nullptr;
return false;
}
@@ -229,26 +321,20 @@ bool OpenGLDevice::InitializeWithExistingWindow(HWND hwnd) {
if (m_hglrc) {
XCEngine::Debug::Logger::Get().Warning(XCEngine::Debug::LogCategory::General, "Created OpenGL context without debug bit (wglCreateContextAttribsARB failed)");
} else {
ReleaseDC(m_hwnd, m_hdc);
m_hdc = nullptr;
return false;
}
}
if (!wglMakeCurrent(m_hdc, m_hglrc)) {
wglDeleteContext(m_hglrc);
ReleaseDC(m_hwnd, m_hdc);
m_hglrc = nullptr;
m_hdc = nullptr;
return false;
}
if (!gladLoadGL()) {
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(m_hglrc);
ReleaseDC(m_hwnd, m_hdc);
m_hglrc = nullptr;
m_hdc = nullptr;
return false;
}
@@ -328,9 +414,11 @@ void OpenGLDevice::Shutdown() {
if (m_hdc && m_hwnd) {
ReleaseDC(m_hwnd, m_hdc);
m_hdc = nullptr;
m_hwnd = nullptr;
}
m_hwnd = nullptr;
DestroyOwnedWindow();
if (m_uniformBufferManager) {
m_uniformBufferManager->Shutdown();
}
@@ -348,6 +436,13 @@ bool OpenGLDevice::MakeContextCurrent() {
return false;
}
void OpenGLDevice::DestroyOwnedWindow() {
if (m_ownedWindow != nullptr) {
DestroyWindow(m_ownedWindow);
m_ownedWindow = nullptr;
}
}
void OpenGLDevice::SwapBuffers() {
if (m_hdc) {
::SwapBuffers(m_hdc);
@@ -451,7 +546,7 @@ RHISwapChain* OpenGLDevice::CreateSwapChain(const SwapChainDesc& desc, RHIComman
auto* swapChain = new OpenGLSwapChain();
HWND hwnd = static_cast<HWND>(desc.windowHandle);
if (hwnd && swapChain->Initialize(this, hwnd, desc.width, desc.height)) {
if (hwnd && InitializeWithExistingWindow(hwnd) && swapChain->Initialize(this, hwnd, desc.width, desc.height)) {
return swapChain;
}
@@ -472,11 +567,16 @@ RHICommandQueue* OpenGLDevice::CreateCommandQueue(const CommandQueueDesc& desc)
RHIShader* OpenGLDevice::CreateShader(const ShaderCompileDesc& desc) {
auto* shader = new OpenGLShader();
if (!MakeContextCurrent()) {
delete shader;
return nullptr;
}
if (desc.sourceLanguage == ShaderLanguage::GLSL && !desc.source.empty()) {
const char* sourceStr = reinterpret_cast<const char*>(desc.source.data());
ShaderType shaderType = ShaderType::Vertex;
std::string profile(desc.profile.begin(), desc.profile.end());
std::string profile = NarrowAscii(desc.profile);
if (profile.find("vs") != std::string::npos) {
shaderType = ShaderType::Vertex;
} else if (profile.find("ps") != std::string::npos || profile.find("fs") != std::string::npos) {
@@ -496,10 +596,13 @@ RHIShader* OpenGLDevice::CreateShader(const ShaderCompileDesc& desc) {
if (!desc.fileName.empty()) {
std::wstring filePath = desc.fileName;
std::string entryPoint(desc.entryPoint.begin(), desc.entryPoint.end());
std::string profile(desc.profile.begin(), desc.profile.end());
shader->CompileFromFile(filePath.c_str(), entryPoint.c_str(), profile.c_str());
return shader;
std::string entryPoint = NarrowAscii(desc.entryPoint);
std::string profile = NarrowAscii(desc.profile);
if (shader->CompileFromFile(filePath.c_str(), entryPoint.c_str(), profile.c_str())) {
return shader;
}
delete shader;
return nullptr;
}
delete shader;

View File

@@ -3,10 +3,85 @@
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
namespace XCEngine {
namespace RHI {
namespace {
bool EndsWith(const std::string& value, const char* suffix) {
const size_t suffixLength = strlen(suffix);
return value.size() >= suffixLength && value.compare(value.size() - suffixLength, suffixLength, suffix) == 0;
}
std::string NarrowAscii(const std::wstring& value) {
std::string result;
result.reserve(value.size());
for (wchar_t ch : value) {
result.push_back(static_cast<char>(ch));
}
return result;
}
bool ResolveShaderType(const std::string& path, const char* target, ShaderType& type) {
if (target != nullptr) {
if (strstr(target, "vs_") != nullptr) {
type = ShaderType::Vertex;
return true;
}
if (strstr(target, "ps_") != nullptr || strstr(target, "fs_") != nullptr) {
type = ShaderType::Fragment;
return true;
}
if (strstr(target, "gs_") != nullptr) {
type = ShaderType::Geometry;
return true;
}
if (strstr(target, "cs_") != nullptr) {
type = ShaderType::Compute;
return true;
}
if (strstr(target, "hs_") != nullptr || strstr(target, "tcs") != nullptr) {
type = ShaderType::TessControl;
return true;
}
if (strstr(target, "ds_") != nullptr || strstr(target, "tes") != nullptr) {
type = ShaderType::TessEvaluation;
return true;
}
}
if (EndsWith(path, ".vert") || EndsWith(path, ".vs.glsl")) {
type = ShaderType::Vertex;
return true;
}
if (EndsWith(path, ".frag") || EndsWith(path, ".fs.glsl")) {
type = ShaderType::Fragment;
return true;
}
if (EndsWith(path, ".geom") || EndsWith(path, ".gs.glsl")) {
type = ShaderType::Geometry;
return true;
}
if (EndsWith(path, ".comp") || EndsWith(path, ".cs.glsl")) {
type = ShaderType::Compute;
return true;
}
if (EndsWith(path, ".tesc")) {
type = ShaderType::TessControl;
return true;
}
if (EndsWith(path, ".tese")) {
type = ShaderType::TessEvaluation;
return true;
}
return false;
}
} // namespace
OpenGLShader::OpenGLShader()
: m_program(0), m_uniformsCached(false) {
}
@@ -211,9 +286,30 @@ bool OpenGLShader::Compile(const char* source, ShaderType type) {
}
bool OpenGLShader::CompileFromFile(const wchar_t* filePath, const char* entryPoint, const char* target) {
std::wstring ws(filePath);
std::string path(ws.begin(), ws.end());
return CompileFromFile(path.c_str(), nullptr);
(void)entryPoint;
if (filePath == nullptr) {
return false;
}
const std::wstring ws(filePath);
const std::string path = NarrowAscii(ws);
std::ifstream shaderFile(path);
if (!shaderFile.is_open()) {
return false;
}
std::stringstream shaderStream;
shaderStream << shaderFile.rdbuf();
ShaderType type = ShaderType::Vertex;
if (!ResolveShaderType(path, target, type)) {
return false;
}
const std::string source = shaderStream.str();
return Compile(source.c_str(), type);
}
bool OpenGLShader::Compile(const void* sourceData, size_t sourceSize, const char* entryPoint, const char* target) {

View File

@@ -11,21 +11,48 @@ TEST_P(RHITestFixture, Device_Initialize_Shutdown) {
RHIDevice* device = RHIFactory::CreateRHIDevice(GetBackendType());
ASSERT_NE(device, nullptr);
bool initResult = false;
if (GetBackendType() == RHIType::D3D12) {
RHIDeviceDesc desc = {};
desc.enableDebugLayer = true;
initResult = device->Initialize(desc);
} else if (GetBackendType() == RHIType::OpenGL) {
auto* oglDevice = static_cast<OpenGLDevice*>(device);
initResult = oglDevice->InitializeWithExistingWindow(GetWindowHandle());
}
RHIDeviceDesc desc = {};
desc.enableDebugLayer = true;
const bool initResult = device->Initialize(desc);
ASSERT_TRUE(initResult);
device->Shutdown();
delete device;
}
TEST_P(RHITestFixture, Device_Initialize_OpenGLUnifiedPath_CanCreateSwapChain) {
if (GetBackendType() != RHIType::OpenGL) {
GTEST_SKIP() << "OpenGL-specific unified initialization verification";
}
auto* device = new OpenGLDevice();
ASSERT_NE(device, nullptr);
RHIDeviceDesc deviceDesc = {};
ASSERT_TRUE(device->Initialize(deviceDesc));
CommandQueueDesc queueDesc = {};
queueDesc.queueType = static_cast<uint32_t>(CommandQueueType::Direct);
RHICommandQueue* queue = device->CreateCommandQueue(queueDesc);
ASSERT_NE(queue, nullptr);
SwapChainDesc swapDesc = {};
swapDesc.windowHandle = GetWindowHandle();
swapDesc.width = 320;
swapDesc.height = 180;
RHISwapChain* swapChain = device->CreateSwapChain(swapDesc, queue);
ASSERT_NE(swapChain, nullptr);
swapChain->Present(0, 0);
swapChain->Shutdown();
delete swapChain;
queue->Shutdown();
delete queue;
device->Shutdown();
delete device;
}
TEST_P(RHITestFixture, Device_GetCapabilities_ReturnsValid) {
const auto& caps = GetDevice()->GetCapabilities();

View File

@@ -141,3 +141,39 @@ TEST_P(RHITestFixture, Shader_Shutdown_Invalidates) {
delete shader;
}
}
TEST_P(RHITestFixture, Shader_Compile_FromFile_ReturnsValidShader) {
ShaderCompileDesc desc = {};
if (GetBackendType() == RHIType::D3D12) {
desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/quad.hlsl";
desc.entryPoint = L"MainVS";
desc.profile = L"vs_5_0";
} else {
desc.fileName = L"tests/RHI/OpenGL/integration/triangle/Res/Shader/triangle.vert";
desc.entryPoint = L"main";
desc.profile = L"vs_4_30";
}
RHIShader* shader = GetDevice()->CreateShader(desc);
ASSERT_NE(shader, nullptr);
EXPECT_TRUE(shader->IsValid());
EXPECT_EQ(shader->GetType(), ShaderType::Vertex);
shader->Shutdown();
delete shader;
}
TEST_P(RHITestFixture, Shader_Compile_MissingFile_ReturnsNullptr) {
ShaderCompileDesc desc = {};
if (GetBackendType() == RHIType::D3D12) {
desc.fileName = L"tests/RHI/D3D12/integration/quad/Res/Shader/does_not_exist.hlsl";
desc.entryPoint = L"MainVS";
desc.profile = L"vs_5_0";
} else {
desc.fileName = L"tests/RHI/OpenGL/integration/triangle/Res/Shader/does_not_exist.vert";
desc.entryPoint = L"main";
desc.profile = L"vs_4_30";
}
RHIShader* shader = GetDevice()->CreateShader(desc);
EXPECT_EQ(shader, nullptr);
}