diff --git a/CMakeLists.txt b/CMakeLists.txt index 77305a17..8778865b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,6 +303,13 @@ if (ANDROID) ) endif() +if (WIN32) + list(APPEND SOURCE_FILES + MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp + MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp + ) +endif() + set(MOBILEGL_LINK_LIBRARIES glslang::glslang spirv-cross-c @@ -331,10 +338,18 @@ set(MOBILEGL_INCLUDE_DIR ${SPIRV-Headers_SOURCE_DIR}/include ) -add_library(${CMAKE_PROJECT_NAME} SHARED +add_library(${CMAKE_PROJECT_NAME} SHARED ${SOURCE_FILES} ) +if (WIN32) + # The wgl* entry points are exported via .def (see the comment in wgl.def); + # only the shared library links it. + target_sources(${CMAKE_PROJECT_NAME} PRIVATE + MobileGL/MG_Impl/WGLImpl/Exporting/wgl.def + ) +endif() + if (CMAKE_BUILD_TYPE STREQUAL "Debug") set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES C_VISIBILITY_PRESET default @@ -377,6 +392,18 @@ if(UNIX AND NOT APPLE AND NOT ANDROID) endforeach() endif() +if(WIN32) + # Drop-in for the classic GL loader path: a copy named opengl32.dll placed + # next to a host executable is what LoadLibrary("opengl32.dll") and gdi32's + # pixel-format forwarding will resolve. + add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$/opengl32.dll" + COMMENT "Creating opengl32.dll drop-in copy" + ) +endif() + if(NOT ANDROID) add_library(${CMAKE_PROJECT_NAME}_s STATIC ${SOURCE_FILES} diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index 76afb047..31663ca8 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -34,6 +34,7 @@ #define MOBILEGL_EGL_API MOBILEGL_API #define MOBILEGL_CGL_API MOBILEGL_API #define MOBILEGL_NSOPENGL_API MOBILEGL_API +#define MOBILEGL_WGL_API MOBILEGL_API // ====================== MobileGL configurations ======================= // #ifndef MOBILEGL_LOG_ACTIVE_LEVEL diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 8a2d560d..2d08bce4 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -9,6 +9,7 @@ #include "Init.h" #include "Config.h" #include +#include #include #include #include @@ -87,17 +88,35 @@ namespace MobileGL { #endif #ifdef _WIN32 - BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { - switch (ul_reason_for_call) { - case DLL_PROCESS_ATTACH: - Initialize(); - break; - - case DLL_PROCESS_DETACH: - Destroy(); - break; + namespace { + // Called on DLL_PROCESS_DETACH, which runs BEFORE the CRT executes this + // DLL's static destructors. Releasing (deliberately leaking) the global + // singletons here keeps those destructors from tearing down the backend + // inside a dying process - other threads have already been terminated, + // so Vulkan/GLES cleanup crashes with the process half-dead. The process + // is exiting; the OS reclaims everything. + void AbandonAtProcessExit() { + MGLOG_I("MobileGL: process detach, abandoning global state"); + MG_Backend::DirectVulkan::pVulkanRenderer.release(); + MG_Backend::pActiveBackendObject.release(); + MG_State::pGLContext.release(); + MG_State::pEGLContext.release(); + MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.release(); + MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.release(); + MGLOG_I("MobileGL: global state abandoned"); } - return TRUE; - } + } // namespace #endif } // namespace MobileGL + +#ifdef _WIN32 +// Initialization is NOT done here: MobileGL::Initialize() loads libraries +// (Vulkan/ANGLE) and spins up glslang, none of which is safe under the loader +// lock. The WGL host layer calls Initialize() lazily on its first entry point. +extern "C" BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) { + if (reason == DLL_PROCESS_DETACH) { + MobileGL::AbandonAtProcessExit(); + } + return TRUE; +} +#endif diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index f5e61501..1167ab0d 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -313,7 +313,8 @@ namespace MobileGL { Android, X11, MetalLayer, - // TODO: Wayland, Windows, etc. + Win32, // Handle is an HWND + // TODO: Wayland, etc. WindowBackendCount, Unknown = -1 }; diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index bac6689b..1baa3541 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -701,9 +701,10 @@ namespace MobileGL::MG_Backend::DirectGLES { if ((handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 && - handle.Backend != WindowBackend::MetalLayer) || + handle.Backend != WindowBackend::MetalLayer && + handle.Backend != WindowBackend::Win32) || !handle.Handle) { - MGLOG_E("DirectGLES backend only supports Android, X11, and CAMetalLayer native windows"); + MGLOG_E("DirectGLES backend only supports Android, X11, CAMetalLayer, and Win32 native windows"); return false; } diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 5c36ffe0..dc013e58 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -4307,9 +4307,23 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!ChooseConfigForSurface(surfaceBit, g_Config, window)) return false; - const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + // Negotiate the highest ES 3.x context. Version-strict EGL implementations + // (ANGLE) return exactly the requested minor, and a bare CLIENT_VERSION 3 + // request yields a 3.0 context that lacks the 3.1/3.2 texture targets the + // capability probes exercise; mobile drivers ignore the minor and hand out + // their maximum either way. + for (const EGLint minorVersion : {2, 1, 0}) { + const EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_MINOR_VERSION, minorVersion, + EGL_NONE}; + g_Context = g_EGLFuncs.eglCreateContext(g_Display, g_Config, EGL_NO_CONTEXT, contextAttribs); + if (g_Context != EGL_NO_CONTEXT) { + return true; + } + } - g_Context = g_EGLFuncs.eglCreateContext(g_Display, g_Config, EGL_NO_CONTEXT, contextAttribs); + const EGLint legacyContextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + g_Context = g_EGLFuncs.eglCreateContext(g_Display, g_Config, EGL_NO_CONTEXT, legacyContextAttribs); return g_Context != EGL_NO_CONTEXT; } diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index a10ee89a..a2d3774b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -397,8 +397,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (!handle.Handle || (handle.Backend != WindowBackend::Android && handle.Backend != WindowBackend::X11 && - handle.Backend != WindowBackend::MetalLayer)) { - MGLOG_E("DirectVulkan backend only supports Android, X11, and CAMetalLayer native windows"); + handle.Backend != WindowBackend::MetalLayer && + handle.Backend != WindowBackend::Win32)) { + MGLOG_E("DirectVulkan backend only supports Android, X11, CAMetalLayer, and Win32 native windows"); return false; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 38fd2167..a7f34aaa 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -7560,6 +7560,10 @@ void main() { MOBILEGL_ASSERT(hwnd, "HWND is null"); VkWin32SurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR}; + sci.hinstance = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_HINSTANCE)); + if (!sci.hinstance) { + sci.hinstance = GetModuleHandleW(nullptr); + } sci.hwnd = hwnd; VK_VERIFY(vkCreateWin32SurfaceKHR(m_instance, &sci, nullptr, &m_surface), "vkCreateWin32SurfaceKHR failed"); #elif defined VK_USE_PLATFORM_METAL_EXT diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 1c49fdf3..42590ad7 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -49,6 +49,8 @@ namespace MobileGL::MG_Impl::EGLImpl { return MG_Backend::WindowBackend::Android; #elif defined(__APPLE__) return MG_Backend::WindowBackend::MetalLayer; +#elif defined(_WIN32) + return MG_Backend::WindowBackend::Win32; #elif defined(__linux__) return MG_Backend::WindowBackend::X11; #else diff --git a/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp new file mode 100644 index 00000000..8fadd48f --- /dev/null +++ b/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp @@ -0,0 +1,156 @@ +// MobileGL - MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +// wingdi.h declares most wgl* entry points as WINGDIAPI (__declspec(dllimport)), +// which would reject our definitions. _GDI32_ is the SDK's "I am the module that +// implements these" switch: it turns WINGDIAPI into a plain declaration. It must +// be defined before the first windows.h inclusion in this translation unit. +#if defined(_WIN32) && !defined(_GDI32_) +#define _GDI32_ 1 +#endif + +#include + +#if defined(_WIN32) +#include "../WGLImpl.h" + +namespace WGL = MobileGL::MG_Impl::WGLImpl; + +// ---- Pixel-format entry points (gdi32 forwards ChoosePixelFormat/SetPixelFormat/ +// ---- DescribePixelFormat/GetPixelFormat/SwapBuffers into these exports) ---- + +extern "C" int WINAPI wglChoosePixelFormat(HDC hdc, CONST PIXELFORMATDESCRIPTOR* ppfd) { + return WGL::ChoosePixelFormat(hdc, ppfd); +} + +extern "C" int WINAPI wglDescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, + LPPIXELFORMATDESCRIPTOR ppfd) { + return WGL::DescribePixelFormat(hdc, iPixelFormat, nBytes, ppfd); +} + +extern "C" int WINAPI wglGetPixelFormat(HDC hdc) { + return WGL::GetPixelFormat(hdc); +} + +extern "C" BOOL WINAPI wglSetPixelFormat(HDC hdc, int iPixelFormat, CONST PIXELFORMATDESCRIPTOR* ppfd) { + return WGL::SetPixelFormat(hdc, iPixelFormat, ppfd); +} + +extern "C" BOOL WINAPI wglSwapBuffers(HDC hdc) { + return WGL::SwapBuffers(hdc); +} + +// ---- Context management ---- + +extern "C" HGLRC WINAPI wglCreateContext(HDC hdc) { + return WGL::CreateContext(hdc); +} + +extern "C" HGLRC WINAPI wglCreateLayerContext(HDC hdc, int iLayerPlane) { + return iLayerPlane == 0 ? WGL::CreateContext(hdc) : nullptr; +} + +extern "C" BOOL WINAPI wglCopyContext(HGLRC, HGLRC, UINT) { + MGLOG_W("wglCopyContext is not supported"); + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; +} + +extern "C" BOOL WINAPI wglDeleteContext(HGLRC hglrc) { + return WGL::DeleteContext(hglrc); +} + +extern "C" HGLRC WINAPI wglGetCurrentContext(VOID) { + return WGL::GetCurrentContext(); +} + +extern "C" HDC WINAPI wglGetCurrentDC(VOID) { + return WGL::GetCurrentDC(); +} + +extern "C" BOOL WINAPI wglMakeCurrent(HDC hdc, HGLRC hglrc) { + return WGL::MakeCurrent(hdc, hglrc); +} + +extern "C" BOOL WINAPI wglShareLists(HGLRC hglrcShare, HGLRC hglrcDest) { + return WGL::ShareLists(hglrcShare, hglrcDest); +} + +// ---- Proc address ---- + +extern "C" PROC WINAPI wglGetProcAddress(LPCSTR lpszProc) { + return WGL::GetProcAddress(lpszProc); +} + +extern "C" PROC WINAPI wglGetDefaultProcAddress(LPCSTR lpszProc) { + return WGL::GetProcAddress(lpszProc); +} + +// ---- Layer planes and palettes (unsupported; overlay planes do not exist here) ---- + +extern "C" BOOL WINAPI wglDescribeLayerPlane(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR) { + return FALSE; +} + +extern "C" int WINAPI wglSetLayerPaletteEntries(HDC, int, int, int, CONST COLORREF*) { + return 0; +} + +extern "C" int WINAPI wglGetLayerPaletteEntries(HDC, int, int, int, COLORREF*) { + return 0; +} + +extern "C" BOOL WINAPI wglRealizeLayerPalette(HDC, int, BOOL) { + return FALSE; +} + +extern "C" BOOL WINAPI wglSwapLayerBuffers(HDC hdc, UINT fuPlanes) { + if (fuPlanes & WGL_SWAP_MAIN_PLANE) { + return WGL::SwapBuffers(hdc); + } + return FALSE; +} + +extern "C" DWORD WINAPI wglSwapMultipleBuffers(UINT n, CONST WGLSWAP* ps) { + if (!ps) { + return 0; + } + DWORD swapped = 0; + for (UINT i = 0; i < n; ++i) { + if (WGL::SwapBuffers(ps[i].hdc)) { + ++swapped; + } + } + return swapped; +} + +// ---- Font rendering (legacy immediate-mode feature; not supported) ---- + +extern "C" BOOL WINAPI wglUseFontBitmapsA(HDC, DWORD, DWORD, DWORD) { + MGLOG_W("wglUseFontBitmapsA is not supported"); + return FALSE; +} + +extern "C" BOOL WINAPI wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD) { + MGLOG_W("wglUseFontBitmapsW is not supported"); + return FALSE; +} + +extern "C" BOOL WINAPI wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, + LPGLYPHMETRICSFLOAT) { + MGLOG_W("wglUseFontOutlinesA is not supported"); + return FALSE; +} + +extern "C" BOOL WINAPI wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, + LPGLYPHMETRICSFLOAT) { + MGLOG_W("wglUseFontOutlinesW is not supported"); + return FALSE; +} + +#endif // _WIN32 diff --git a/MobileGL/MG_Impl/WGLImpl/Exporting/wgl.def b/MobileGL/MG_Impl/WGLImpl/Exporting/wgl.def new file mode 100644 index 00000000..7b7fc912 --- /dev/null +++ b/MobileGL/MG_Impl/WGLImpl/Exporting/wgl.def @@ -0,0 +1,30 @@ +; MobileGL WGL exports. The wgl* entry points are defined without +; __declspec(dllexport) because wingdi.h pre-declares them (with _GDI32_ they +; become plain declarations, and MSVC rejects adding dllexport afterwards), +; so this .def file is what actually exports them from the DLL. +EXPORTS + wglChoosePixelFormat + wglCopyContext + wglCreateContext + wglCreateLayerContext + wglDeleteContext + wglDescribeLayerPlane + wglDescribePixelFormat + wglGetCurrentContext + wglGetCurrentDC + wglGetDefaultProcAddress + wglGetLayerPaletteEntries + wglGetPixelFormat + wglGetProcAddress + wglMakeCurrent + wglRealizeLayerPalette + wglSetLayerPaletteEntries + wglSetPixelFormat + wglShareLists + wglSwapBuffers + wglSwapLayerBuffers + wglSwapMultipleBuffers + wglUseFontBitmapsA + wglUseFontBitmapsW + wglUseFontOutlinesA + wglUseFontOutlinesW diff --git a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp new file mode 100644 index 00000000..09663a0e --- /dev/null +++ b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp @@ -0,0 +1,731 @@ +// MobileGL - MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "WGLImpl.h" + +#if defined(_WIN32) +#include "../EGLImpl/EGLImpl.h" +#include "../GetProcAddress.h" +#include + +namespace MobileGL::MG_Impl::WGLImpl { + namespace { + // WGL_ARB_pixel_format + constexpr int WGL_NUMBER_PIXEL_FORMATS_ARB = 0x2000; + constexpr int WGL_DRAW_TO_WINDOW_ARB = 0x2001; + constexpr int WGL_DRAW_TO_BITMAP_ARB = 0x2002; + constexpr int WGL_ACCELERATION_ARB = 0x2003; + constexpr int WGL_NEED_PALETTE_ARB = 0x2004; + constexpr int WGL_NEED_SYSTEM_PALETTE_ARB = 0x2005; + constexpr int WGL_SWAP_LAYER_BUFFERS_ARB = 0x2006; + constexpr int WGL_SWAP_METHOD_ARB = 0x2007; + constexpr int WGL_NUMBER_OVERLAYS_ARB = 0x2008; + constexpr int WGL_NUMBER_UNDERLAYS_ARB = 0x2009; + constexpr int WGL_TRANSPARENT_ARB = 0x200A; + constexpr int WGL_SHARE_DEPTH_ARB = 0x200C; + constexpr int WGL_SHARE_STENCIL_ARB = 0x200D; + constexpr int WGL_SHARE_ACCUM_ARB = 0x200E; + constexpr int WGL_SUPPORT_GDI_ARB = 0x200F; + constexpr int WGL_SUPPORT_OPENGL_ARB = 0x2010; + constexpr int WGL_DOUBLE_BUFFER_ARB = 0x2011; + constexpr int WGL_STEREO_ARB = 0x2012; + constexpr int WGL_PIXEL_TYPE_ARB = 0x2013; + constexpr int WGL_COLOR_BITS_ARB = 0x2014; + constexpr int WGL_RED_BITS_ARB = 0x2015; + constexpr int WGL_RED_SHIFT_ARB = 0x2016; + constexpr int WGL_GREEN_BITS_ARB = 0x2017; + constexpr int WGL_GREEN_SHIFT_ARB = 0x2018; + constexpr int WGL_BLUE_BITS_ARB = 0x2019; + constexpr int WGL_BLUE_SHIFT_ARB = 0x201A; + constexpr int WGL_ALPHA_BITS_ARB = 0x201B; + constexpr int WGL_ALPHA_SHIFT_ARB = 0x201C; + constexpr int WGL_ACCUM_BITS_ARB = 0x201D; + constexpr int WGL_ACCUM_RED_BITS_ARB = 0x201E; + constexpr int WGL_ACCUM_GREEN_BITS_ARB = 0x201F; + constexpr int WGL_ACCUM_BLUE_BITS_ARB = 0x2020; + constexpr int WGL_ACCUM_ALPHA_BITS_ARB = 0x2021; + constexpr int WGL_DEPTH_BITS_ARB = 0x2022; + constexpr int WGL_STENCIL_BITS_ARB = 0x2023; + constexpr int WGL_AUX_BUFFERS_ARB = 0x2024; + constexpr int WGL_NO_ACCELERATION_ARB = 0x2025; + constexpr int WGL_FULL_ACCELERATION_ARB = 0x2027; + constexpr int WGL_SWAP_EXCHANGE_ARB = 0x2028; + constexpr int WGL_TYPE_RGBA_ARB = 0x202B; + // WGL_ARB_multisample + constexpr int WGL_SAMPLE_BUFFERS_ARB = 0x2041; + constexpr int WGL_SAMPLES_ARB = 0x2042; + // WGL_ARB_create_context / _profile / _no_error + constexpr int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; + constexpr int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; + constexpr int WGL_CONTEXT_LAYER_PLANE_ARB = 0x2093; + constexpr int WGL_CONTEXT_FLAGS_ARB = 0x2094; + constexpr int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; + constexpr int WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001; + constexpr int WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; + constexpr int WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; + constexpr int WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; + constexpr int WGL_CONTEXT_OPENGL_NO_ERROR_ARB = 0x31B3; + constexpr DWORD ERROR_INVALID_VERSION_ARB = 0x2095; + constexpr DWORD ERROR_INVALID_PROFILE_ARB = 0x2096; + + struct PixelFormatInfo { + GLint AlphaBits; + GLint DepthBits; + GLint StencilBits; + }; + + // Mirrors the two EGLState configs (RGBA8 + depth24, stencil 8 / stencil 0). + constexpr PixelFormatInfo kPixelFormats[] = { + {8, 24, 8}, + {8, 24, 0}, + }; + constexpr int kPixelFormatCount = static_cast(std::size(kPixelFormats)); + + struct ContextObject { + EGLDisplay Display = EGL_NO_DISPLAY; + EGLConfig Config = nullptr; + EGLContext Context = EGL_NO_CONTEXT; + }; + + struct WindowSurface { + EGLDisplay Display = EGL_NO_DISPLAY; + EGLSurface Surface = EGL_NO_SURFACE; + Uint32 Width = 0; + Uint32 Height = 0; + }; + + std::recursive_mutex& RegistryMutex() { + static auto* mutex = new std::recursive_mutex(); + return *mutex; + } + + UnorderedMap& Contexts() { + static auto* contexts = new UnorderedMap(); + return *contexts; + } + + UnorderedMap& WindowSurfaces() { + static auto* surfaces = new UnorderedMap(); + return *surfaces; + } + + UnorderedMap& WindowPixelFormats() { + static auto* formats = new UnorderedMap(); + return *formats; + } + + Uint64& NextContextHandle() { + static auto* handle = new Uint64(0x10000); + return *handle; + } + + struct ThreadCurrent { + HDC DC = nullptr; + HGLRC Context = nullptr; + }; + thread_local ThreadCurrent t_current; + + Int& SwapIntervalShadow() { + static auto* interval = new Int(1); + return *interval; + } + + void EnsureInitialized() { + // Initialize() loads backend libraries and glslang, which must not run + // under the loader lock; first WGL call is the earliest safe moment. + static std::once_flag once; + std::call_once(once, [] { MobileGL::Initialize(); }); + } + + EGLDisplay EnsureDisplay() { + EnsureInitialized(); + EGLDisplay display = EGLImpl::GetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + return EGL_NO_DISPLAY; + } + if (!EGLImpl::Initialize(display, nullptr, nullptr)) { + return EGL_NO_DISPLAY; + } + return display; + } + + HGLRC EncodeContext(Uint64 handle) { + return reinterpret_cast(static_cast(handle)); + } + + ContextObject* TryGetContext(HGLRC hglrc) { + auto& contexts = Contexts(); + auto it = contexts.find(hglrc); + return it == contexts.end() ? nullptr : &it->second; + } + + const PixelFormatInfo& PixelFormatForWindow(HWND hwnd) { + auto& formats = WindowPixelFormats(); + auto it = formats.find(hwnd); + int index = it == formats.end() ? 1 : it->second; + if (index < 1 || index > kPixelFormatCount) { + index = 1; + } + return kPixelFormats[index - 1]; + } + + Bool QueryClientSize(HWND hwnd, Uint32& width, Uint32& height) { + RECT rect{}; + if (!GetClientRect(hwnd, &rect)) { + return false; + } + width = static_cast(std::max(rect.right - rect.left, 1)); + height = static_cast(std::max(rect.bottom - rect.top, 1)); + return true; + } + + // The backends never query the HWND client size themselves; the WGL layer + // owns size discovery and pushes changes through the internal resize hook + // (same contract as the macOS CGL layer). + void SyncSurfaceSize(HWND hwnd, WindowSurface& surface) { + Uint32 width = 0; + Uint32 height = 0; + if (!QueryClientSize(hwnd, width, height)) { + return; + } + if (width == surface.Width && height == surface.Height) { + return; + } + if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface, + static_cast(width), static_cast(height))) { + surface.Width = width; + surface.Height = height; + } + } + + WindowSurface* EnsureWindowSurface(HWND hwnd, const ContextObject& context) { + auto& surfaces = WindowSurfaces(); + auto it = surfaces.find(hwnd); + if (it != surfaces.end()) { + SyncSurfaceSize(hwnd, it->second); + return &it->second; + } + + Uint32 width = 0; + Uint32 height = 0; + if (!QueryClientSize(hwnd, width, height)) { + MGLOG_E("wgl: GetClientRect failed for HWND %p", hwnd); + return nullptr; + } + + const EGLAttrib attribs[] = { + EGL_WIDTH, static_cast(width), + EGL_HEIGHT, static_cast(height), + EGL_NONE, + }; + EGLSurface surface = + EGLImpl::CreatePlatformWindowSurface(context.Display, context.Config, hwnd, attribs); + if (surface == EGL_NO_SURFACE) { + MGLOG_E("wgl: failed to create window surface for HWND %p (%ux%u)", hwnd, width, height); + return nullptr; + } + + WindowSurface record; + record.Display = context.Display; + record.Surface = surface; + record.Width = width; + record.Height = height; + auto [inserted, _] = surfaces.emplace(hwnd, record); + return &inserted->second; + } + + HGLRC CreateContextFromEGLAttribs(HDC hdc, HGLRC share, const EGLint* contextAttribs) { + const std::lock_guard lock(RegistryMutex()); + EGLDisplay display = EnsureDisplay(); + if (display == EGL_NO_DISPLAY) { + MGLOG_E("wgl: no EGL display"); + return nullptr; + } + EGLImpl::BindAPI(EGL_OPENGL_API); + + EGLContext shareContext = EGL_NO_CONTEXT; + if (share) { + auto* shareObject = TryGetContext(share); + if (!shareObject) { + SetLastError(ERROR_INVALID_HANDLE); + return nullptr; + } + shareContext = shareObject->Context; + } + + HWND hwnd = WindowFromDC(hdc); + const PixelFormatInfo& pixelFormat = PixelFormatForWindow(hwnd); + const EGLint configAttribs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, pixelFormat.AlphaBits, + EGL_DEPTH_SIZE, pixelFormat.DepthBits, + EGL_STENCIL_SIZE, pixelFormat.StencilBits, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_NONE, + }; + EGLConfig config = nullptr; + EGLint configCount = 0; + if (!EGLImpl::ChooseConfig(display, configAttribs, &config, 1, &configCount) || configCount <= 0) { + MGLOG_E("wgl: eglChooseConfig failed"); + return nullptr; + } + + EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs); + if (eglContext == EGL_NO_CONTEXT) { + MGLOG_E("wgl: eglCreateContext failed"); + return nullptr; + } + + ContextObject object; + object.Display = display; + object.Config = config; + object.Context = eglContext; + const auto handle = EncodeContext(NextContextHandle()++); + Contexts()[handle] = object; + MGLOG_I("wgl: created context %p (EGL context %p)", handle, eglContext); + return handle; + } + + // ---- WGL extension entry points (resolved via wglGetProcAddress only) ---- + + const char* WINAPI Ext_GetExtensionsStringARB(HDC) { + return "WGL_ARB_create_context WGL_ARB_create_context_no_error WGL_ARB_create_context_profile " + "WGL_ARB_extensions_string WGL_ARB_pixel_format WGL_EXT_extensions_string WGL_EXT_swap_control"; + } + + const char* WINAPI Ext_GetExtensionsStringEXT() { + return Ext_GetExtensionsStringARB(nullptr); + } + + HGLRC WINAPI Ext_CreateContextAttribsARB(HDC hdc, HGLRC hShareContext, const int* attribList) { + EnsureInitialized(); + int major = 1; + int minor = 0; + int profileMask = 0; + int flags = 0; + if (attribList) { + for (SizeT i = 0; attribList[i] != 0; i += 2) { + const int attrib = attribList[i]; + const int value = attribList[i + 1]; + switch (attrib) { + case WGL_CONTEXT_MAJOR_VERSION_ARB: + major = value; + break; + case WGL_CONTEXT_MINOR_VERSION_ARB: + minor = value; + break; + case WGL_CONTEXT_PROFILE_MASK_ARB: + profileMask = value; + break; + case WGL_CONTEXT_FLAGS_ARB: + flags = value; + break; + case WGL_CONTEXT_LAYER_PLANE_ARB: + if (value != 0) { + SetLastError(ERROR_INVALID_PARAMETER); + return nullptr; + } + break; + case WGL_CONTEXT_OPENGL_NO_ERROR_ARB: + // Accepted and ignored: MobileGL always validates. + break; + default: + MGLOG_D("wglCreateContextAttribsARB: ignoring attrib 0x%04x = 0x%x", attrib, value); + break; + } + } + } + + if (major < 1 || (profileMask & ~(WGL_CONTEXT_CORE_PROFILE_BIT_ARB | + WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB))) { + SetLastError(profileMask ? ERROR_INVALID_PROFILE_ARB : ERROR_INVALID_VERSION_ARB); + return nullptr; + } + + Vector attribs = { + EGL_CONTEXT_MAJOR_VERSION, major, + EGL_CONTEXT_MINOR_VERSION, minor, + }; + const Bool wantsCompat = (profileMask & WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB) != 0; + if (major > 3 || (major == 3 && minor >= 2) || profileMask != 0) { + attribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK); + attribs.push_back(wantsCompat ? EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT + : EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + } + if (flags & WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB) { + attribs.push_back(EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE); + attribs.push_back(EGL_TRUE); + } + if (flags & WGL_CONTEXT_DEBUG_BIT_ARB) { + attribs.push_back(EGL_CONTEXT_OPENGL_DEBUG); + attribs.push_back(EGL_TRUE); + } + attribs.push_back(EGL_NONE); + + return CreateContextFromEGLAttribs(hdc, hShareContext, attribs.data()); + } + + BOOL WINAPI Ext_SwapIntervalEXT(int interval) { + const std::lock_guard lock(RegistryMutex()); + EGLDisplay display = EnsureDisplay(); + if (display == EGL_NO_DISPLAY) { + return FALSE; + } + if (interval < 0) { + // Adaptive vsync is not supported; clamp to regular vsync. + interval = 1; + } + EGLImpl::SwapInterval(display, interval); + SwapIntervalShadow() = interval; + return TRUE; + } + + int WINAPI Ext_GetSwapIntervalEXT() { + const std::lock_guard lock(RegistryMutex()); + return SwapIntervalShadow(); + } + + int PixelFormatAttribValue(int format, int attrib) { + const PixelFormatInfo& info = kPixelFormats[format - 1]; + switch (attrib) { + case WGL_NUMBER_PIXEL_FORMATS_ARB: + return kPixelFormatCount; + case WGL_SUPPORT_OPENGL_ARB: + case WGL_DRAW_TO_WINDOW_ARB: + case WGL_DOUBLE_BUFFER_ARB: + return 1; + case WGL_ACCELERATION_ARB: + return WGL_FULL_ACCELERATION_ARB; + case WGL_PIXEL_TYPE_ARB: + return WGL_TYPE_RGBA_ARB; + case WGL_COLOR_BITS_ARB: + return 32; + case WGL_RED_BITS_ARB: + case WGL_GREEN_BITS_ARB: + case WGL_BLUE_BITS_ARB: + return 8; + case WGL_RED_SHIFT_ARB: + return 16; + case WGL_GREEN_SHIFT_ARB: + return 8; + case WGL_BLUE_SHIFT_ARB: + return 0; + case WGL_ALPHA_BITS_ARB: + return info.AlphaBits; + case WGL_ALPHA_SHIFT_ARB: + return 24; + case WGL_DEPTH_BITS_ARB: + return info.DepthBits; + case WGL_STENCIL_BITS_ARB: + return info.StencilBits; + case WGL_SWAP_METHOD_ARB: + return WGL_SWAP_EXCHANGE_ARB; + case WGL_DRAW_TO_BITMAP_ARB: + case WGL_NEED_PALETTE_ARB: + case WGL_NEED_SYSTEM_PALETTE_ARB: + case WGL_SWAP_LAYER_BUFFERS_ARB: + case WGL_NUMBER_OVERLAYS_ARB: + case WGL_NUMBER_UNDERLAYS_ARB: + case WGL_TRANSPARENT_ARB: + case WGL_SHARE_DEPTH_ARB: + case WGL_SHARE_STENCIL_ARB: + case WGL_SHARE_ACCUM_ARB: + case WGL_SUPPORT_GDI_ARB: + case WGL_STEREO_ARB: + case WGL_ACCUM_BITS_ARB: + case WGL_ACCUM_RED_BITS_ARB: + case WGL_ACCUM_GREEN_BITS_ARB: + case WGL_ACCUM_BLUE_BITS_ARB: + case WGL_ACCUM_ALPHA_BITS_ARB: + case WGL_AUX_BUFFERS_ARB: + case WGL_SAMPLE_BUFFERS_ARB: + case WGL_SAMPLES_ARB: + default: + return 0; + } + } + + BOOL WINAPI Ext_GetPixelFormatAttribivARB(HDC, int iPixelFormat, int iLayerPlane, UINT nAttributes, + const int* piAttributes, int* piValues) { + if (iLayerPlane != 0 || !piAttributes || !piValues) { + return FALSE; + } + // Format 0 is only valid for WGL_NUMBER_PIXEL_FORMATS_ARB queries. + if (iPixelFormat < 0 || iPixelFormat > kPixelFormatCount) { + return FALSE; + } + const int format = iPixelFormat == 0 ? 1 : iPixelFormat; + for (UINT i = 0; i < nAttributes; ++i) { + piValues[i] = PixelFormatAttribValue(format, piAttributes[i]); + } + return TRUE; + } + + BOOL WINAPI Ext_GetPixelFormatAttribfvARB(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, + const int* piAttributes, FLOAT* pfValues) { + if (!pfValues) { + return FALSE; + } + Vector values(nAttributes); + if (!Ext_GetPixelFormatAttribivARB(hdc, iPixelFormat, iLayerPlane, nAttributes, piAttributes, + values.data())) { + return FALSE; + } + for (UINT i = 0; i < nAttributes; ++i) { + pfValues[i] = static_cast(values[i]); + } + return TRUE; + } + + BOOL WINAPI Ext_ChoosePixelFormatARB(HDC, const int* piAttribIList, const FLOAT*, UINT nMaxFormats, + int* piFormats, UINT* nNumFormats) { + if (!piFormats || !nNumFormats) { + return FALSE; + } + int wantedStencil = 0; + if (piAttribIList) { + for (SizeT i = 0; piAttribIList[i] != 0; i += 2) { + if (piAttribIList[i] == WGL_STENCIL_BITS_ARB) { + wantedStencil = piAttribIList[i + 1]; + } + } + } + UINT count = 0; + const int preferred = wantedStencil > 0 ? 1 : 2; + const int fallback = wantedStencil > 0 ? 2 : 1; + if (count < nMaxFormats) { + piFormats[count++] = preferred; + } + if (count < nMaxFormats) { + piFormats[count++] = fallback; + } + *nNumFormats = count; + return TRUE; + } + + struct WGLExtensionProc { + const char* Name; + PROC Proc; + }; + + const WGLExtensionProc kWGLExtensionProcs[] = { + {"wglGetExtensionsStringARB", reinterpret_cast(Ext_GetExtensionsStringARB)}, + {"wglGetExtensionsStringEXT", reinterpret_cast(Ext_GetExtensionsStringEXT)}, + {"wglCreateContextAttribsARB", reinterpret_cast(Ext_CreateContextAttribsARB)}, + {"wglSwapIntervalEXT", reinterpret_cast(Ext_SwapIntervalEXT)}, + {"wglGetSwapIntervalEXT", reinterpret_cast(Ext_GetSwapIntervalEXT)}, + {"wglGetPixelFormatAttribivARB", reinterpret_cast(Ext_GetPixelFormatAttribivARB)}, + {"wglGetPixelFormatAttribfvARB", reinterpret_cast(Ext_GetPixelFormatAttribfvARB)}, + {"wglChoosePixelFormatARB", reinterpret_cast(Ext_ChoosePixelFormatARB)}, + }; + } // namespace + + int ChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR* pfd) { + EnsureInitialized(); + MGLOG_D("wglChoosePixelFormat(hdc=%p)", hdc); + // Format 1 (RGBA8 + depth24/stencil8) satisfies every request; a format + // exceeding the asked-for capabilities is a legal ChoosePixelFormat answer. + (void)pfd; + return 1; + } + + int DescribePixelFormat(HDC hdc, int format, UINT size, PIXELFORMATDESCRIPTOR* pfd) { + EnsureInitialized(); + MGLOG_D("wglDescribePixelFormat(hdc=%p, format=%d)", hdc, format); + if (!pfd) { + return kPixelFormatCount; + } + if (size < sizeof(PIXELFORMATDESCRIPTOR) || format < 1 || format > kPixelFormatCount) { + return 0; + } + + const PixelFormatInfo& info = kPixelFormats[format - 1]; + std::memset(pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); + pfd->nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd->nVersion = 1; + pfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_SWAP_EXCHANGE +#if defined(PFD_SUPPORT_COMPOSITION) + | PFD_SUPPORT_COMPOSITION +#endif + ; + pfd->iPixelType = PFD_TYPE_RGBA; + pfd->cColorBits = 32; + pfd->cRedBits = 8; + pfd->cRedShift = 16; + pfd->cGreenBits = 8; + pfd->cGreenShift = 8; + pfd->cBlueBits = 8; + pfd->cBlueShift = 0; + pfd->cAlphaBits = static_cast(info.AlphaBits); + pfd->cAlphaShift = 24; + pfd->cDepthBits = static_cast(info.DepthBits); + pfd->cStencilBits = static_cast(info.StencilBits); + pfd->iLayerType = PFD_MAIN_PLANE; + return kPixelFormatCount; + } + + int GetPixelFormat(HDC hdc) { + EnsureInitialized(); + HWND hwnd = WindowFromDC(hdc); + if (!hwnd) { + return 0; + } + const std::lock_guard lock(RegistryMutex()); + auto& formats = WindowPixelFormats(); + auto it = formats.find(hwnd); + return it == formats.end() ? 0 : it->second; + } + + BOOL SetPixelFormat(HDC hdc, int format, const PIXELFORMATDESCRIPTOR*) { + EnsureInitialized(); + MGLOG_D("wglSetPixelFormat(hdc=%p, format=%d)", hdc, format); + if (format < 1 || format > kPixelFormatCount) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + HWND hwnd = WindowFromDC(hdc); + if (!hwnd) { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + const std::lock_guard lock(RegistryMutex()); + WindowPixelFormats()[hwnd] = format; + return TRUE; + } + + BOOL SwapBuffers(HDC hdc) { + HWND hwnd = WindowFromDC(hdc); + if (!hwnd) { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + const std::lock_guard lock(RegistryMutex()); + auto& surfaces = WindowSurfaces(); + auto it = surfaces.find(hwnd); + if (it == surfaces.end()) { + MGLOG_W("wglSwapBuffers: no surface for HWND %p", hwnd); + return FALSE; + } + SyncSurfaceSize(hwnd, it->second); + return EGLImpl::SwapBuffers(it->second.Display, it->second.Surface) == EGL_TRUE ? TRUE : FALSE; + } + + HGLRC CreateContext(HDC hdc) { + EnsureInitialized(); + MGLOG_I("wglCreateContext(hdc=%p)", hdc); + // A legacy WGL context is a compatibility-profile context; MobileGL keys + // its relaxed-semantics mode off the explicit compatibility bit. + const EGLint attribs[] = { + EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_MINOR_VERSION, 3, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, + EGL_NONE, + }; + return CreateContextFromEGLAttribs(hdc, nullptr, attribs); + } + + BOOL DeleteContext(HGLRC hglrc) { + EnsureInitialized(); + MGLOG_I("wglDeleteContext(%p)", hglrc); + if (t_current.Context == hglrc) { + MakeCurrent(nullptr, nullptr); + } + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(hglrc); + if (!object) { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + if (object->Context != EGL_NO_CONTEXT) { + EGLImpl::DestroyContext(object->Display, object->Context); + } + Contexts().erase(hglrc); + return TRUE; + } + + BOOL MakeCurrent(HDC hdc, HGLRC hglrc) { + EnsureInitialized(); + MGLOG_D("wglMakeCurrent(hdc=%p, hglrc=%p)", hdc, hglrc); + if (!hglrc) { + if (!t_current.Context) { + t_current = {}; + return TRUE; + } + const EGLBoolean released = + EGLImpl::MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + t_current = {}; + return released == EGL_TRUE ? TRUE : FALSE; + } + + HWND hwnd = WindowFromDC(hdc); + if (!hwnd) { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(hglrc); + if (!object) { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + WindowSurface* surface = EnsureWindowSurface(hwnd, *object); + if (!surface) { + return FALSE; + } + + if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface, object->Context)) { + MGLOG_E("wglMakeCurrent: eglMakeCurrent failed (hdc=%p, hglrc=%p)", hdc, hglrc); + return FALSE; + } + t_current = {hdc, hglrc}; + return TRUE; + } + + HGLRC GetCurrentContext() { + return t_current.Context; + } + + HDC GetCurrentDC() { + return t_current.DC; + } + + BOOL ShareLists(HGLRC hglrcShare, HGLRC hglrcDest) { + // All MobileGL contexts alias one global GL object namespace, so every + // pair of contexts already shares. + const std::lock_guard lock(RegistryMutex()); + if (!TryGetContext(hglrcShare) || !TryGetContext(hglrcDest)) { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + return TRUE; + } + + PROC GetProcAddress(const char* name) { + EnsureInitialized(); + if (!name) { + return nullptr; + } + if (name[0] == 'w' && name[1] == 'g' && name[2] == 'l') { + for (const auto& entry : kWGLExtensionProcs) { + if (std::strcmp(entry.Name, name) == 0) { + return entry.Proc; + } + } + MGLOG_D("wglGetProcAddress: unknown wgl entry point %s", name); + return nullptr; + } + return reinterpret_cast(MG_Impl::GetProcAddress(name)); + } +} // namespace MobileGL::MG_Impl::WGLImpl + +#endif // _WIN32 diff --git a/MobileGL/MG_Impl/WGLImpl/WGLImpl.h b/MobileGL/MG_Impl/WGLImpl/WGLImpl.h new file mode 100644 index 00000000..83adfcd9 --- /dev/null +++ b/MobileGL/MG_Impl/WGLImpl/WGLImpl.h @@ -0,0 +1,34 @@ +// MobileGL - MobileGL/MG_Impl/WGLImpl/WGLImpl.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include + +#if defined(_WIN32) + +namespace MobileGL::MG_Impl::WGLImpl { + // Classic opengl32.dll surface. gdi32's ChoosePixelFormat/SetPixelFormat/ + // DescribePixelFormat/GetPixelFormat/SwapBuffers forward into the loaded + // opengl32.dll's wgl* exports, so these back both call paths. + int ChoosePixelFormat(HDC hdc, const PIXELFORMATDESCRIPTOR* pfd); + int DescribePixelFormat(HDC hdc, int format, UINT size, PIXELFORMATDESCRIPTOR* pfd); + int GetPixelFormat(HDC hdc); + BOOL SetPixelFormat(HDC hdc, int format, const PIXELFORMATDESCRIPTOR* pfd); + BOOL SwapBuffers(HDC hdc); + + HGLRC CreateContext(HDC hdc); + BOOL DeleteContext(HGLRC hglrc); + BOOL MakeCurrent(HDC hdc, HGLRC hglrc); + HGLRC GetCurrentContext(); + HDC GetCurrentDC(); + BOOL ShareLists(HGLRC hglrcShare, HGLRC hglrcDest); + + PROC GetProcAddress(const char* name); +} // namespace MobileGL::MG_Impl::WGLImpl + +#endif // _WIN32 diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 15ee1748..83101308 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -9,7 +9,12 @@ #include "Loader.h" #include "MG_Util/Types.h" #include -#if !defined(__WIN32) && !defined(_WIN32) +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#else #include #endif @@ -60,7 +65,14 @@ namespace MobileGL::MG_Util::BackendLoader { #endif static void* OpenLib(const Vector& names) { -#if !defined(__WIN32) && !defined(_WIN32) && (!defined(__APPLE__) || defined(MOBILEGL_IOS)) +#if defined(_WIN32) + for (const auto& name : names) { + if (HMODULE lib = LoadLibraryA(name.c_str())) { + MGLOG_I("Loaded GL backend library: %s", name.c_str()); + return reinterpret_cast(lib); + } + } +#elif !defined(__APPLE__) || defined(MOBILEGL_IOS) static const String LibPathPrefixes[] = { #if defined(MOBILEGL_IOS) "@rpath/", "@executable_path/Frameworks/", "@loader_path/Frameworks/", @@ -104,7 +116,9 @@ namespace MobileGL::MG_Util::BackendLoader { } inline void* ProcAddress(void* lib, const char* name) { -#if !defined(__WIN32) && !defined(_WIN32) && (!defined(__APPLE__) || defined(MOBILEGL_IOS)) +#if defined(_WIN32) + return reinterpret_cast(::GetProcAddress(reinterpret_cast(lib), name)); +#elif !defined(__APPLE__) || defined(MOBILEGL_IOS) return dlsym(lib, name); #else return nullptr; @@ -520,6 +534,15 @@ namespace MobileGL::MG_Util::BackendLoader { #if defined(MOBILEGL_TRACE_ANGLE_VARIANTS) && defined(__ANDROID__) void* angleGlesLib = nullptr; #endif +#if defined(_WIN32) + // ANGLE is the GLES provider on Windows regardless of UseAngle(). Preload + // libGLESv2.dll so libEGL.dll resolves its dependency from the same directory. + if (!OpenLib({"libGLESv2.dll"})) { + MGLOG_E("Failed to open ANGLE libGLESv2.dll"); + return; + } + eglLib = OpenLib({"libEGL.dll"}); +#else if (UseAngle()) { void* glesLib = OpenLib({"libGLESv2_angle.so"}); if (!glesLib) { @@ -541,6 +564,7 @@ namespace MobileGL::MG_Util::BackendLoader { eglLib = OpenLib({"libEGL.so"}); #endif } +#endif // !_WIN32 if (!eglLib) { MGLOG_E("Failed to open EGL library");