From 08f98ad9ce9e16b63ff98e850fd6a19bb3cfeafb Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Wed, 5 Aug 2026 23:08:29 -0400 Subject: [PATCH] [Feat] (MG_Impl): implement GLX 1.4 on the EGL layer so GLFW apps run on Linux Desktop Linux GL apps (GLFW/LWJGL, glxgears, anything X11) create contexts through GLX, and MobileGL only spoke EGL - the two exported glX symbols were proc-address stubs that could resolve GL entry points but never produce a context. GLXImpl is the missing sibling of WGLImpl/CGLImpl: the same window-system-binding pattern, calling the internal MG_Impl::EGLImpl namespace directly. The surface covers exactly what GLFW 3.4 resolves via dlsym plus the legacy visual API: FBConfig enumeration mirrors the two EGLState configs (stencil-8 first so stencil-wanting choosers land on it), glXGetVisualFromFBConfig answers with the screen's default visual (falling back to any 24-bit TrueColor one), and glXCreateContextAttribsARB maps the ARB attribs onto EGL context attribs the way WGL's Ext_CreateContextAttribsARB does - profile mask only emitted for 3.2+ or an explicit profile request, since that bit is what keys MobileGL's relaxed-semantics compatibility mode. Legacy glXCreateContext/CreateNewContext hand out 3.3 compatibility contexts, matching wglCreateContext. Drawables follow the WGL HWND model: the GLXWindow is the X window itself, the EGL window surface is created lazily on first MakeCurrent and cached per XID, and the GLX layer owns size discovery per the platform-layer contract - it pushes changes through EGLImpl::ResizePlatformWindowSurface, polling XGetGeometry on MakeCurrent and on swaps throttled to 250ms so a fast-swapping app is not paying a server round trip per frame. libX11 is dlopen'd at runtime like everywhere else in the tree; Xlib.h is already in every TU via the vulkan include, so XVisualInfo gets an ABI mirror struct (Xutil.h needs the Bool and Status macros that Includes.h deliberately pops) and the caller's XFree pairs with our malloc. glXGetProcAddress now resolves glX names from the export table before falling through to the shared GL resolver, which previously returned nullptr for every glX extension entry point - GLFW requires glXCreateContextAttribsARB and glXSwapIntervalEXT to arrive that way. Verified with a smoke test replaying GLFW's exact call sequence (dlsym-only resolution, manual FBConfig filtering, 3.2 core forward-compatible context, glXCreateWindow, 60 swapped frames, clean glGetError) on both backends against the real NVIDIA driver, then with Minecraft 1.21.1, 1.21.4+Fabric+Sodium and 26.2-snapshot-6 reaching in-world rendering on both Espryt and Magma. --- CMakeLists.txt | 1 + .../MG_Impl/GLXImpl/Exporting/Definitions.cpp | 255 +++- MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp | 1112 +++++++++++++++++ MobileGL/MG_Impl/GLXImpl/GLXImpl.h | 69 + MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp | 22 +- 5 files changed, 1455 insertions(+), 4 deletions(-) create mode 100644 MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp create mode 100644 MobileGL/MG_Impl/GLXImpl/GLXImpl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d8b56dc6..de6af5ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,7 @@ set(SOURCE_FILES MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp + MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp diff --git a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp index 5fc67d43..778abe8e 100644 --- a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp @@ -15,4 +15,257 @@ MOBILEGL_GLX_API void* glXGetProcAddress(const char* name) { MOBILEGL_GLX_API void* glXGetProcAddressARB(const char* name) { return MG_Impl::GLXImpl::GetProcAddressARB(name); -} \ No newline at end of file +} + +#if defined(__linux__) && !defined(__ANDROID__) +#include "../GLXImpl.h" + +namespace GLXImpl = MobileGL::MG_Impl::GLXImpl; + +// GLX handle/type spellings from GL/glx.h, expressed without including it: +// GLXContext/GLXFBConfig are opaque pointers, drawables are XIDs, Bool is int, +// and XVisualInfo* crosses as void*. + +MOBILEGL_GLX_API int glXQueryExtension(Display* dpy, int* errorBase, int* eventBase) { + return GLXImpl::QueryExtension(dpy, errorBase, eventBase); +} + +MOBILEGL_GLX_API int glXQueryVersion(Display* dpy, int* major, int* minor) { + return GLXImpl::QueryVersion(dpy, major, minor); +} + +MOBILEGL_GLX_API const char* glXQueryExtensionsString(Display* dpy, int screen) { + return GLXImpl::QueryExtensionsString(dpy, screen); +} + +MOBILEGL_GLX_API const char* glXGetClientString(Display* dpy, int name) { + return GLXImpl::GetClientString(dpy, name); +} + +MOBILEGL_GLX_API const char* glXQueryServerString(Display* dpy, int screen, int name) { + return GLXImpl::QueryServerString(dpy, screen, name); +} + +MOBILEGL_GLX_API void** glXGetFBConfigs(Display* dpy, int screen, int* nelements) { + return GLXImpl::GetFBConfigs(dpy, screen, nelements); +} + +MOBILEGL_GLX_API void** glXChooseFBConfig(Display* dpy, int screen, const int* attribList, + int* nelements) { + return GLXImpl::ChooseFBConfig(dpy, screen, attribList, nelements); +} + +MOBILEGL_GLX_API int glXGetFBConfigAttrib(Display* dpy, void* config, int attribute, int* value) { + return GLXImpl::GetFBConfigAttrib(dpy, config, attribute, value); +} + +MOBILEGL_GLX_API void* glXGetVisualFromFBConfig(Display* dpy, void* config) { + return GLXImpl::GetVisualFromFBConfig(dpy, config); +} + +MOBILEGL_GLX_API void* glXChooseVisual(Display* dpy, int screen, int* attribList) { + return GLXImpl::ChooseVisual(dpy, screen, attribList); +} + +MOBILEGL_GLX_API int glXGetConfig(Display* dpy, void* visualInfo, int attribute, int* value) { + return GLXImpl::GetConfig(dpy, visualInfo, attribute, value); +} + +MOBILEGL_GLX_API void* glXCreateContext(Display* dpy, void* visualInfo, void* shareList, int direct) { + return GLXImpl::CreateContext(dpy, visualInfo, shareList, direct); +} + +MOBILEGL_GLX_API void* glXCreateNewContext(Display* dpy, void* config, int renderType, + void* shareList, int direct) { + return GLXImpl::CreateNewContext(dpy, config, renderType, shareList, direct); +} + +MOBILEGL_GLX_API void* glXCreateContextAttribsARB(Display* dpy, void* config, void* shareContext, + int direct, const int* attribList) { + return GLXImpl::CreateContextAttribsARB(dpy, config, shareContext, direct, attribList); +} + +MOBILEGL_GLX_API void glXDestroyContext(Display* dpy, void* context) { + GLXImpl::DestroyContext(dpy, context); +} + +MOBILEGL_GLX_API int glXMakeCurrent(Display* dpy, unsigned long drawable, void* context) { + return GLXImpl::MakeCurrent(dpy, drawable, context); +} + +MOBILEGL_GLX_API int glXMakeContextCurrent(Display* dpy, unsigned long draw, unsigned long read, + void* context) { + return GLXImpl::MakeContextCurrent(dpy, draw, read, context); +} + +MOBILEGL_GLX_API void glXSwapBuffers(Display* dpy, unsigned long drawable) { + GLXImpl::SwapBuffers(dpy, drawable); +} + +MOBILEGL_GLX_API unsigned long glXCreateWindow(Display* dpy, void* config, unsigned long window, + const int* attribList) { + return GLXImpl::CreateWindow(dpy, config, window, attribList); +} + +MOBILEGL_GLX_API void glXDestroyWindow(Display* dpy, unsigned long window) { + GLXImpl::DestroyWindow(dpy, window); +} + +MOBILEGL_GLX_API void* glXGetCurrentContext() { + return GLXImpl::GetCurrentContext(); +} + +MOBILEGL_GLX_API unsigned long glXGetCurrentDrawable() { + return GLXImpl::GetCurrentDrawable(); +} + +MOBILEGL_GLX_API unsigned long glXGetCurrentReadDrawable() { + return GLXImpl::GetCurrentReadDrawable(); +} + +MOBILEGL_GLX_API Display* glXGetCurrentDisplay() { + return GLXImpl::GetCurrentDisplay(); +} + +MOBILEGL_GLX_API int glXIsDirect(Display* dpy, void* context) { + return GLXImpl::IsDirect(dpy, context); +} + +MOBILEGL_GLX_API void glXWaitGL() { + GLXImpl::WaitGL(); +} + +MOBILEGL_GLX_API void glXWaitX() { + GLXImpl::WaitX(); +} + +MOBILEGL_GLX_API int glXQueryContext(Display* dpy, void* context, int attribute, int* value) { + return GLXImpl::QueryContext(dpy, context, attribute, value); +} + +MOBILEGL_GLX_API void glXQueryDrawable(Display* dpy, unsigned long drawable, int attribute, + unsigned int* value) { + GLXImpl::QueryDrawable(dpy, drawable, attribute, value); +} + +MOBILEGL_GLX_API void glXSwapIntervalEXT(Display* dpy, unsigned long drawable, int interval) { + GLXImpl::SwapIntervalEXT(dpy, drawable, interval); +} + +MOBILEGL_GLX_API int glXSwapIntervalMESA(unsigned int interval) { + return GLXImpl::SwapIntervalMESA(interval); +} + +MOBILEGL_GLX_API int glXGetSwapIntervalMESA() { + return GLXImpl::GetSwapIntervalMESA(); +} + +MOBILEGL_GLX_API int glXSwapIntervalSGI(int interval) { + return GLXImpl::SwapIntervalSGI(interval); +} + +// Legacy entry points some loaders probe for; harmless no-op stubs. +MOBILEGL_GLX_API void glXCopyContext(Display*, void*, void*, unsigned long) { + MGLOG_W("glx: glXCopyContext is not supported"); +} + +MOBILEGL_GLX_API unsigned long glXCreateGLXPixmap(Display*, void*, unsigned long) { + MGLOG_W("glx: glXCreateGLXPixmap is not supported"); + return 0; +} + +MOBILEGL_GLX_API void glXDestroyGLXPixmap(Display*, unsigned long) {} + +MOBILEGL_GLX_API unsigned long glXCreatePixmap(Display*, void*, unsigned long, const int*) { + MGLOG_W("glx: glXCreatePixmap is not supported"); + return 0; +} + +MOBILEGL_GLX_API void glXDestroyPixmap(Display*, unsigned long) {} + +MOBILEGL_GLX_API unsigned long glXCreatePbuffer(Display*, void*, const int*) { + MGLOG_W("glx: glXCreatePbuffer is not supported"); + return 0; +} + +MOBILEGL_GLX_API void glXDestroyPbuffer(Display*, unsigned long) {} + +MOBILEGL_GLX_API void glXUseXFont(unsigned long, int, int, int) { + MGLOG_W("glx: glXUseXFont is not supported"); +} + +MOBILEGL_GLX_API void glXSelectEvent(Display*, unsigned long, unsigned long) {} + +MOBILEGL_GLX_API void glXGetSelectedEvent(Display*, unsigned long, unsigned long* eventMask) { + if (eventMask) { + *eventMask = 0; + } +} + +namespace MobileGL::MG_Impl::GLXImpl { + namespace { + struct GLXEntryPoint { + const char* Name; + void* Proc; + }; + + const GLXEntryPoint kGLXEntryPoints[] = { + {"glXChooseFBConfig", reinterpret_cast(glXChooseFBConfig)}, + {"glXChooseVisual", reinterpret_cast(glXChooseVisual)}, + {"glXCopyContext", reinterpret_cast(glXCopyContext)}, + {"glXCreateContext", reinterpret_cast(glXCreateContext)}, + {"glXCreateContextAttribsARB", reinterpret_cast(glXCreateContextAttribsARB)}, + {"glXCreateGLXPixmap", reinterpret_cast(glXCreateGLXPixmap)}, + {"glXCreateNewContext", reinterpret_cast(glXCreateNewContext)}, + {"glXCreatePbuffer", reinterpret_cast(glXCreatePbuffer)}, + {"glXCreatePixmap", reinterpret_cast(glXCreatePixmap)}, + {"glXCreateWindow", reinterpret_cast(glXCreateWindow)}, + {"glXDestroyContext", reinterpret_cast(glXDestroyContext)}, + {"glXDestroyGLXPixmap", reinterpret_cast(glXDestroyGLXPixmap)}, + {"glXDestroyPbuffer", reinterpret_cast(glXDestroyPbuffer)}, + {"glXDestroyPixmap", reinterpret_cast(glXDestroyPixmap)}, + {"glXDestroyWindow", reinterpret_cast(glXDestroyWindow)}, + {"glXGetClientString", reinterpret_cast(glXGetClientString)}, + {"glXGetConfig", reinterpret_cast(glXGetConfig)}, + {"glXGetCurrentContext", reinterpret_cast(glXGetCurrentContext)}, + {"glXGetCurrentDisplay", reinterpret_cast(glXGetCurrentDisplay)}, + {"glXGetCurrentDrawable", reinterpret_cast(glXGetCurrentDrawable)}, + {"glXGetCurrentReadDrawable", reinterpret_cast(glXGetCurrentReadDrawable)}, + {"glXGetFBConfigAttrib", reinterpret_cast(glXGetFBConfigAttrib)}, + {"glXGetFBConfigs", reinterpret_cast(glXGetFBConfigs)}, + {"glXGetProcAddress", reinterpret_cast(glXGetProcAddress)}, + {"glXGetProcAddressARB", reinterpret_cast(glXGetProcAddressARB)}, + {"glXGetSelectedEvent", reinterpret_cast(glXGetSelectedEvent)}, + {"glXGetSwapIntervalMESA", reinterpret_cast(glXGetSwapIntervalMESA)}, + {"glXGetVisualFromFBConfig", reinterpret_cast(glXGetVisualFromFBConfig)}, + {"glXIsDirect", reinterpret_cast(glXIsDirect)}, + {"glXMakeContextCurrent", reinterpret_cast(glXMakeContextCurrent)}, + {"glXMakeCurrent", reinterpret_cast(glXMakeCurrent)}, + {"glXQueryContext", reinterpret_cast(glXQueryContext)}, + {"glXQueryDrawable", reinterpret_cast(glXQueryDrawable)}, + {"glXQueryExtension", reinterpret_cast(glXQueryExtension)}, + {"glXQueryExtensionsString", reinterpret_cast(glXQueryExtensionsString)}, + {"glXQueryServerString", reinterpret_cast(glXQueryServerString)}, + {"glXQueryVersion", reinterpret_cast(glXQueryVersion)}, + {"glXSelectEvent", reinterpret_cast(glXSelectEvent)}, + {"glXSwapBuffers", reinterpret_cast(glXSwapBuffers)}, + {"glXSwapIntervalEXT", reinterpret_cast(glXSwapIntervalEXT)}, + {"glXSwapIntervalMESA", reinterpret_cast(glXSwapIntervalMESA)}, + {"glXSwapIntervalSGI", reinterpret_cast(glXSwapIntervalSGI)}, + {"glXUseXFont", reinterpret_cast(glXUseXFont)}, + {"glXWaitGL", reinterpret_cast(glXWaitGL)}, + {"glXWaitX", reinterpret_cast(glXWaitX)}, + }; + } // namespace + + void* GetGLXEntryPoint(const char* name) { + for (const auto& entry : kGLXEntryPoints) { + if (std::strcmp(entry.Name, name) == 0) { + return entry.Proc; + } + } + return nullptr; + } +} // namespace MobileGL::MG_Impl::GLXImpl + +#endif // __linux__ && !__ANDROID__ diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp new file mode 100644 index 00000000..c8737775 --- /dev/null +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp @@ -0,0 +1,1112 @@ +// MobileGL - MobileGL/MG_Impl/GLXImpl/GLXImpl.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 "GLXImpl.h" + +#if defined(__linux__) && !defined(__ANDROID__) +#include "../EGLImpl/EGLImpl.h" +#include "../GetProcAddress.h" +#include + +namespace MobileGL::MG_Impl::GLXImpl { + namespace { + // ---- GLX tokens (defined locally: GL/glx.h drags in a conflicting gl.h) ---- + constexpr int GLX_USE_GL = 1; + constexpr int GLX_BUFFER_SIZE = 2; + constexpr int GLX_LEVEL = 3; + constexpr int GLX_RGBA = 4; + constexpr int GLX_DOUBLEBUFFER = 5; + constexpr int GLX_STEREO = 6; + constexpr int GLX_AUX_BUFFERS = 7; + constexpr int GLX_RED_SIZE = 8; + constexpr int GLX_GREEN_SIZE = 9; + constexpr int GLX_BLUE_SIZE = 10; + constexpr int GLX_ALPHA_SIZE = 11; + constexpr int GLX_DEPTH_SIZE = 12; + constexpr int GLX_STENCIL_SIZE = 13; + constexpr int GLX_ACCUM_RED_SIZE = 14; + constexpr int GLX_ACCUM_GREEN_SIZE = 15; + constexpr int GLX_ACCUM_BLUE_SIZE = 16; + constexpr int GLX_ACCUM_ALPHA_SIZE = 17; + // glXGetConfig/glXGetFBConfigAttrib error returns + constexpr int GLX_BAD_ATTRIBUTE = 2; + // glXGetClientString/glXQueryServerString names + constexpr int GLX_VENDOR = 1; + constexpr int GLX_VERSION = 2; + constexpr int GLX_EXTENSIONS = 3; + // GLX 1.3+ FBConfig attributes + constexpr int GLX_CONFIG_CAVEAT = 0x20; + constexpr int GLX_X_VISUAL_TYPE = 0x22; + constexpr int GLX_TRANSPARENT_TYPE = 0x23; + constexpr int GLX_TRANSPARENT_INDEX_VALUE = 0x24; + constexpr int GLX_TRANSPARENT_RED_VALUE = 0x25; + constexpr int GLX_TRANSPARENT_GREEN_VALUE = 0x26; + constexpr int GLX_TRANSPARENT_BLUE_VALUE = 0x27; + constexpr int GLX_TRANSPARENT_ALPHA_VALUE = 0x28; + constexpr int GLX_DONT_CARE = static_cast(0xFFFFFFFF); + constexpr int GLX_NONE = 0x8000; + constexpr int GLX_TRUE_COLOR = 0x8002; + constexpr int GLX_VISUAL_ID = 0x800B; + constexpr int GLX_SCREEN = 0x800C; + constexpr int GLX_DRAWABLE_TYPE = 0x8010; + constexpr int GLX_RENDER_TYPE = 0x8011; + constexpr int GLX_X_RENDERABLE = 0x8012; + constexpr int GLX_FBCONFIG_ID = 0x8013; + constexpr int GLX_RGBA_TYPE = 0x8014; + constexpr int GLX_MAX_PBUFFER_WIDTH = 0x8016; + constexpr int GLX_MAX_PBUFFER_HEIGHT = 0x8017; + constexpr int GLX_MAX_PBUFFER_PIXELS = 0x8018; + constexpr int GLX_WIDTH = 0x801D; + constexpr int GLX_HEIGHT = 0x801E; + constexpr int GLX_WINDOW_BIT = 0x00000001; + constexpr int GLX_RGBA_BIT = 0x00000001; + constexpr int GLX_SAMPLE_BUFFERS = 100000; + constexpr int GLX_SAMPLES = 100001; + // GLX_EXT_swap_control + constexpr int GLX_SWAP_INTERVAL_EXT = 0x20F1; + constexpr int GLX_MAX_SWAP_INTERVAL_EXT = 0x20F2; + // GLX_ARB_create_context / _profile / _no_error + constexpr int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091; + constexpr int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092; + constexpr int GLX_CONTEXT_FLAGS_ARB = 0x2094; + constexpr int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126; + constexpr int GLX_CONTEXT_DEBUG_BIT_ARB = 0x0001; + constexpr int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002; + constexpr int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001; + constexpr int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002; + constexpr int GLX_CONTEXT_OPENGL_NO_ERROR_ARB = 0x31B3; + + constexpr const char* kGLXExtensions = + "GLX_ARB_create_context GLX_ARB_create_context_no_error GLX_ARB_create_context_profile " + "GLX_ARB_get_proc_address GLX_EXT_swap_control GLX_MESA_swap_control GLX_SGI_swap_control"; + + // ---- Xlib access (dlopen'd at runtime, same convention as the backends; + // libX11 is never a link-time dependency of libMobileGL) ---- + + // ABI-stable mirror of XVisualInfo (Xutil.h is not includable here: the + // Bool/Status macros it needs were popped after the vulkan include). + struct XVisualInfoCompat { + void* visual; + VisualID visualid; + int screen; + int depth; + int c_class; + unsigned long red_mask; + unsigned long green_mask; + unsigned long blue_mask; + int colormap_size; + int bits_per_rgb; + }; + constexpr long kVisualIDMask = 0x1; + constexpr long kVisualScreenMask = 0x2; + constexpr long kVisualDepthMask = 0x4; + constexpr long kVisualClassMask = 0x8; + constexpr int kTrueColor = 4; + + struct X11Functions { + void* Library = nullptr; + int (*GetGeometry)(Display*, GLXDrawableHandle, GLXDrawableHandle*, int*, int*, unsigned int*, + unsigned int*, unsigned int*, unsigned int*) = nullptr; + XVisualInfoCompat* (*GetVisualInfo)(Display*, long, XVisualInfoCompat*, int*) = nullptr; + int (*GetDefaultScreen)(Display*) = nullptr; + void* (*GetDefaultVisual)(Display*, int) = nullptr; + VisualID (*VisualIDFromVisual)(void*) = nullptr; + int (*Free)(void*) = nullptr; + int (*Sync)(Display*, int) = nullptr; + + Bool Valid() const { + return GetGeometry && GetVisualInfo && GetDefaultScreen && GetDefaultVisual && + VisualIDFromVisual && Free; + } + }; + + const X11Functions& X11() { + static const X11Functions* functions = [] { + auto* fns = new X11Functions(); + for (const char* name : {"libX11.so.6", "libX11.so"}) { + fns->Library = dlopen(name, RTLD_LOCAL | RTLD_NOW); + if (fns->Library) { + break; + } + } + if (fns->Library) { + fns->GetGeometry = reinterpret_castGetGeometry)>( + dlsym(fns->Library, "XGetGeometry")); + fns->GetVisualInfo = reinterpret_castGetVisualInfo)>( + dlsym(fns->Library, "XGetVisualInfo")); + fns->GetDefaultScreen = reinterpret_castGetDefaultScreen)>( + dlsym(fns->Library, "XDefaultScreen")); + fns->GetDefaultVisual = reinterpret_castGetDefaultVisual)>( + dlsym(fns->Library, "XDefaultVisual")); + fns->VisualIDFromVisual = reinterpret_castVisualIDFromVisual)>( + dlsym(fns->Library, "XVisualIDFromVisual")); + fns->Free = reinterpret_castFree)>(dlsym(fns->Library, "XFree")); + fns->Sync = reinterpret_castSync)>(dlsym(fns->Library, "XSync")); + } + if (!fns->Valid()) { + MGLOG_E("glx: failed to load libX11 entry points"); + } + return fns; + }(); + return *functions; + } + + // ---- FBConfigs: mirror the two EGLState configs, stencil-8 first so + // stencil-wanting choosers (GLFW's default hints) match config 1 ---- + struct FBConfigInfo { + int FBConfigId; + GLint StencilBits; + }; + constexpr FBConfigInfo kFBConfigs[] = { + {1, 8}, + {2, 0}, + }; + constexpr int kFBConfigCount = static_cast(std::size(kFBConfigs)); + + const FBConfigInfo* TryGetFBConfig(GLXFBConfigHandle config) { + const auto* info = static_cast(config); + return (info >= kFBConfigs && info < kFBConfigs + kFBConfigCount) ? info : nullptr; + } + + struct ContextObject { + Display* XDisplay = nullptr; + EGLDisplay Display = EGL_NO_DISPLAY; + EGLConfig Config = nullptr; + EGLContext Context = EGL_NO_CONTEXT; + const FBConfigInfo* FBConfig = nullptr; + }; + + struct DrawableSurface { + EGLDisplay Display = EGL_NO_DISPLAY; + EGLSurface Surface = EGL_NO_SURFACE; + Uint32 Width = 0; + Uint32 Height = 0; + std::chrono::steady_clock::time_point LastSizePoll{}; + }; + + std::recursive_mutex& RegistryMutex() { + static auto* mutex = new std::recursive_mutex(); + return *mutex; + } + + UnorderedMap& Contexts() { + static auto* contexts = new UnorderedMap(); + return *contexts; + } + + UnorderedMap& DrawableSurfaces() { + static auto* surfaces = new UnorderedMap(); + return *surfaces; + } + + UnorderedMap& WindowFBConfigs() { + static auto* configs = new UnorderedMap(); + return *configs; + } + + Uint64& NextContextHandle() { + static auto* handle = new Uint64(0x10000); + return *handle; + } + + struct ThreadCurrent { + Display* XDisplay = nullptr; + GLXDrawableHandle Draw = 0; + GLXDrawableHandle Read = 0; + GLXContextHandle Context = nullptr; + }; + thread_local ThreadCurrent t_current; + + Int& SwapIntervalShadow() { + static auto* interval = new Int(1); + return *interval; + } + + void EnsureInitialized() { + // MobileGL::EnsureInitialized (not a local once_flag) so a fresh init + // can follow a full teardown from the last eglTerminate. + MobileGL::EnsureInitialized(); + } + + 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; + } + + GLXContextHandle EncodeContext(Uint64 handle) { + return reinterpret_cast(static_cast(handle)); + } + + ContextObject* TryGetContext(GLXContextHandle context) { + auto& contexts = Contexts(); + auto it = contexts.find(context); + return it == contexts.end() ? nullptr : &it->second; + } + + Bool QueryDrawableSize(Display* dpy, GLXDrawableHandle drawable, Uint32& width, Uint32& height) { + const auto& x11 = X11(); + if (!x11.Valid() || !dpy || drawable == 0) { + return false; + } + GLXDrawableHandle root = 0; + int x = 0; + int y = 0; + unsigned int w = 0; + unsigned int h = 0; + unsigned int border = 0; + unsigned int depth = 0; + if (!x11.GetGeometry(dpy, drawable, &root, &x, &y, &w, &h, &border, &depth)) { + return false; + } + width = std::max(w, 1u); + height = std::max(h, 1u); + return true; + } + + // The backends never query the X window size themselves; the GLX layer + // owns size discovery and pushes changes through the internal resize hook + // (same contract as the WGL and CGL layers). Polling XGetGeometry is a + // server round trip, so throttle steady-state swaps. + void SyncSurfaceSize(Display* dpy, GLXDrawableHandle drawable, DrawableSurface& surface, + Bool force = false) { + const auto now = std::chrono::steady_clock::now(); + if (!force && now - surface.LastSizePoll < std::chrono::milliseconds(250)) { + return; + } + surface.LastSizePoll = now; + Uint32 width = 0; + Uint32 height = 0; + if (!QueryDrawableSize(dpy, drawable, 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; + } + } + + DrawableSurface* EnsureDrawableSurface(Display* dpy, GLXDrawableHandle drawable, + const ContextObject& context) { + auto& surfaces = DrawableSurfaces(); + auto it = surfaces.find(drawable); + if (it != surfaces.end()) { + SyncSurfaceSize(dpy, drawable, it->second, true); + return &it->second; + } + + Uint32 width = 0; + Uint32 height = 0; + if (!QueryDrawableSize(dpy, drawable, width, height)) { + MGLOG_E("glx: XGetGeometry failed for drawable 0x%lx", drawable); + 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, reinterpret_cast(drawable), attribs); + if (surface == EGL_NO_SURFACE) { + MGLOG_E("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable, + width, height); + return nullptr; + } + + DrawableSurface record; + record.Display = context.Display; + record.Surface = surface; + record.Width = width; + record.Height = height; + record.LastSizePoll = std::chrono::steady_clock::now(); + auto [inserted, _] = surfaces.emplace(drawable, record); + return &inserted->second; + } + + GLXContextHandle CreateContextFromEGLAttribs(Display* dpy, const FBConfigInfo* fbconfig, + GLXContextHandle share, + const EGLint* contextAttribs) { + const std::lock_guard lock(RegistryMutex()); + EGLDisplay display = EnsureDisplay(); + if (display == EGL_NO_DISPLAY) { + MGLOG_E("glx: no EGL display"); + return nullptr; + } + EGLImpl::BindAPI(EGL_OPENGL_API); + + EGLContext shareContext = EGL_NO_CONTEXT; + if (share) { + auto* shareObject = TryGetContext(share); + if (!shareObject) { + return nullptr; + } + shareContext = shareObject->Context; + } + + const EGLint configAttribs[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, fbconfig->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("glx: eglChooseConfig failed"); + return nullptr; + } + + EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs); + if (eglContext == EGL_NO_CONTEXT) { + MGLOG_E("glx: eglCreateContext failed"); + return nullptr; + } + + ContextObject object; + object.XDisplay = dpy; + object.Display = display; + object.Config = config; + object.Context = eglContext; + object.FBConfig = fbconfig; + const auto handle = EncodeContext(NextContextHandle()++); + Contexts()[handle] = object; + MGLOG_I("glx: created context %p (EGL context %p, stencil %d)", handle, eglContext, + fbconfig->StencilBits); + return handle; + } + + int FBConfigAttribValue(Display* dpy, const FBConfigInfo& info, int attribute, int* value) { + const auto& x11 = X11(); + switch (attribute) { + case GLX_FBCONFIG_ID: + *value = info.FBConfigId; + return 0; + case GLX_BUFFER_SIZE: + *value = 32; + return 0; + case GLX_LEVEL: + *value = 0; + return 0; + case GLX_DOUBLEBUFFER: + *value = 1; + return 0; + case GLX_STEREO: + *value = 0; + return 0; + case GLX_AUX_BUFFERS: + *value = 0; + return 0; + case GLX_RED_SIZE: + case GLX_GREEN_SIZE: + case GLX_BLUE_SIZE: + case GLX_ALPHA_SIZE: + *value = 8; + return 0; + case GLX_DEPTH_SIZE: + *value = 24; + return 0; + case GLX_STENCIL_SIZE: + *value = info.StencilBits; + return 0; + case GLX_ACCUM_RED_SIZE: + case GLX_ACCUM_GREEN_SIZE: + case GLX_ACCUM_BLUE_SIZE: + case GLX_ACCUM_ALPHA_SIZE: + *value = 0; + return 0; + case GLX_RENDER_TYPE: + *value = GLX_RGBA_BIT; + return 0; + case GLX_DRAWABLE_TYPE: + *value = GLX_WINDOW_BIT; + return 0; + case GLX_X_RENDERABLE: + *value = 1; + return 0; + case GLX_X_VISUAL_TYPE: + *value = GLX_TRUE_COLOR; + return 0; + case GLX_CONFIG_CAVEAT: + case GLX_TRANSPARENT_TYPE: + *value = GLX_NONE; + return 0; + case GLX_TRANSPARENT_INDEX_VALUE: + case GLX_TRANSPARENT_RED_VALUE: + case GLX_TRANSPARENT_GREEN_VALUE: + case GLX_TRANSPARENT_BLUE_VALUE: + case GLX_TRANSPARENT_ALPHA_VALUE: + *value = 0; + return 0; + case GLX_VISUAL_ID: { + *value = 0; + if (x11.Valid() && dpy) { + const int screen = x11.GetDefaultScreen(dpy); + void* visual = x11.GetDefaultVisual(dpy, screen); + if (visual) { + *value = static_cast(x11.VisualIDFromVisual(visual)); + } + } + return 0; + } + case GLX_SCREEN: + *value = (x11.Valid() && dpy) ? x11.GetDefaultScreen(dpy) : 0; + return 0; + case GLX_MAX_PBUFFER_WIDTH: + case GLX_MAX_PBUFFER_HEIGHT: + *value = 16384; + return 0; + case GLX_MAX_PBUFFER_PIXELS: + *value = 16384 * 16384; + return 0; + case GLX_SAMPLE_BUFFERS: + case GLX_SAMPLES: + *value = 0; + return 0; + default: + *value = 0; + return GLX_BAD_ATTRIBUTE; + } + } + + // Resolve the X visual all our configs render to: the default visual of + // the screen (matches EGLState's EGL_NATIVE_VISUAL_ID policy), falling + // back to any 24-bit TrueColor visual. Caller frees with XFree. + XVisualInfoCompat* ResolveVisual(Display* dpy, int screen) { + const auto& x11 = X11(); + if (!x11.Valid() || !dpy) { + return nullptr; + } + if (screen < 0) { + screen = x11.GetDefaultScreen(dpy); + } + if (void* visual = x11.GetDefaultVisual(dpy, screen)) { + XVisualInfoCompat tmpl{}; + tmpl.visualid = x11.VisualIDFromVisual(visual); + int count = 0; + XVisualInfoCompat* info = x11.GetVisualInfo(dpy, kVisualIDMask, &tmpl, &count); + if (info && count >= 1 && info->depth >= 24 && info->c_class == kTrueColor) { + return info; + } + if (info) { + x11.Free(info); + } + } + XVisualInfoCompat tmpl{}; + tmpl.screen = screen; + tmpl.depth = 24; + tmpl.c_class = kTrueColor; + int count = 0; + XVisualInfoCompat* info = x11.GetVisualInfo( + dpy, kVisualScreenMask | kVisualDepthMask | kVisualClassMask, &tmpl, &count); + if (info && count < 1) { + x11.Free(info); + return nullptr; + } + return info; + } + } // namespace + + int QueryExtension(Display*, int* errorBase, int* eventBase) { + if (errorBase) { + *errorBase = 0; + } + if (eventBase) { + *eventBase = 0; + } + return 1; + } + + int QueryVersion(Display*, int* major, int* minor) { + if (major) { + *major = 1; + } + if (minor) { + *minor = 4; + } + return 1; + } + + const char* QueryExtensionsString(Display*, int) { + return kGLXExtensions; + } + + const char* GetClientString(Display*, int name) { + switch (name) { + case GLX_VENDOR: + return "MobileGL"; + case GLX_VERSION: + return "1.4"; + case GLX_EXTENSIONS: + return kGLXExtensions; + default: + return nullptr; + } + } + + const char* QueryServerString(Display* dpy, int, int name) { + return GetClientString(dpy, name); + } + + GLXFBConfigHandle* GetFBConfigs(Display*, int, int* nelements) { + auto** configs = static_cast( + std::malloc(sizeof(GLXFBConfigHandle) * kFBConfigCount)); + if (!configs) { + if (nelements) { + *nelements = 0; + } + return nullptr; + } + for (int i = 0; i < kFBConfigCount; ++i) { + configs[i] = const_cast(&kFBConfigs[i]); + } + if (nelements) { + *nelements = kFBConfigCount; + } + return configs; + } + + GLXFBConfigHandle* ChooseFBConfig(Display* dpy, int screen, const int* attribList, int* nelements) { + // Every config satisfies >= size matches for our fixed RGBA8888/24 caps; + // the only distinguishing filters are stencil size and hard mismatches. + Bool acceptConfig[kFBConfigCount] = {true, true}; + if (attribList) { + for (SizeT i = 0; attribList[i] != 0; i += 2) { + const int attrib = attribList[i]; + const int value = attribList[i + 1]; + if (value == GLX_DONT_CARE) { + continue; + } + switch (attrib) { + case GLX_STENCIL_SIZE: + for (int c = 0; c < kFBConfigCount; ++c) { + if (kFBConfigs[c].StencilBits < value) { + acceptConfig[c] = false; + } + } + break; + case GLX_FBCONFIG_ID: + for (int c = 0; c < kFBConfigCount; ++c) { + if (kFBConfigs[c].FBConfigId != value) { + acceptConfig[c] = false; + } + } + break; + case GLX_RED_SIZE: + case GLX_GREEN_SIZE: + case GLX_BLUE_SIZE: + case GLX_ALPHA_SIZE: + if (value > 8) { + acceptConfig[0] = acceptConfig[1] = false; + } + break; + case GLX_BUFFER_SIZE: + if (value > 32) { + acceptConfig[0] = acceptConfig[1] = false; + } + break; + case GLX_DEPTH_SIZE: + if (value > 24) { + acceptConfig[0] = acceptConfig[1] = false; + } + break; + case GLX_DOUBLEBUFFER: + if (value != 1) { + acceptConfig[0] = acceptConfig[1] = false; + } + break; + case GLX_STEREO: + case GLX_AUX_BUFFERS: + case GLX_SAMPLE_BUFFERS: + case GLX_SAMPLES: + case GLX_LEVEL: + case GLX_ACCUM_RED_SIZE: + case GLX_ACCUM_GREEN_SIZE: + case GLX_ACCUM_BLUE_SIZE: + case GLX_ACCUM_ALPHA_SIZE: + if (value > 0) { + acceptConfig[0] = acceptConfig[1] = false; + } + break; + case GLX_RENDER_TYPE: + if ((value & GLX_RGBA_BIT) == 0) { + acceptConfig[0] = acceptConfig[1] = false; + } + break; + case GLX_DRAWABLE_TYPE: + if ((value & ~GLX_WINDOW_BIT) != 0) { + acceptConfig[0] = acceptConfig[1] = false; + } + break; + default: + // Lenient like EGLState's ChooseConfig: unknown attributes + // are ignored rather than failing the whole request. + break; + } + } + } + + int count = 0; + for (int c = 0; c < kFBConfigCount; ++c) { + if (acceptConfig[c]) { + ++count; + } + } + if (count == 0) { + if (nelements) { + *nelements = 0; + } + return nullptr; + } + auto** configs = + static_cast(std::malloc(sizeof(GLXFBConfigHandle) * count)); + if (!configs) { + if (nelements) { + *nelements = 0; + } + return nullptr; + } + int out = 0; + for (int c = 0; c < kFBConfigCount; ++c) { + if (acceptConfig[c]) { + configs[out++] = const_cast(&kFBConfigs[c]); + } + } + if (nelements) { + *nelements = count; + } + (void)dpy; + (void)screen; + return configs; + } + + int GetFBConfigAttrib(Display* dpy, GLXFBConfigHandle config, int attribute, int* value) { + const auto* info = TryGetFBConfig(config); + if (!info || !value) { + return GLX_BAD_ATTRIBUTE; + } + return FBConfigAttribValue(dpy, *info, attribute, value); + } + + void* GetVisualFromFBConfig(Display* dpy, GLXFBConfigHandle config) { + if (!TryGetFBConfig(config)) { + return nullptr; + } + return ResolveVisual(dpy, -1); + } + + void* ChooseVisual(Display* dpy, int screen, int* attribList) { + // Legacy visual chooser: our single RGBA8888/24/8 double-buffered visual + // satisfies any RGBA request; color-index and stereo are unsupported. + Bool wantsRGBA = false; + if (attribList) { + for (SizeT i = 0; attribList[i] != 0;) { + const int attrib = attribList[i]; + if (attrib == GLX_RGBA || attrib == GLX_USE_GL || attrib == GLX_DOUBLEBUFFER) { + wantsRGBA = wantsRGBA || attrib == GLX_RGBA; + ++i; + continue; + } + if (attrib == GLX_STEREO) { + return nullptr; + } + i += 2; + } + } + if (!wantsRGBA) { + return nullptr; + } + return ResolveVisual(dpy, screen); + } + + int GetConfig(Display*, void* visualInfo, int attribute, int* value) { + if (!visualInfo || !value) { + return GLX_BAD_ATTRIBUTE; + } + switch (attribute) { + case GLX_USE_GL: + case GLX_RGBA: + case GLX_DOUBLEBUFFER: + *value = 1; + return 0; + case GLX_STEREO: + case GLX_LEVEL: + case GLX_AUX_BUFFERS: + case GLX_ACCUM_RED_SIZE: + case GLX_ACCUM_GREEN_SIZE: + case GLX_ACCUM_BLUE_SIZE: + case GLX_ACCUM_ALPHA_SIZE: + *value = 0; + return 0; + case GLX_BUFFER_SIZE: + *value = 32; + return 0; + case GLX_RED_SIZE: + case GLX_GREEN_SIZE: + case GLX_BLUE_SIZE: + case GLX_ALPHA_SIZE: + *value = 8; + return 0; + case GLX_DEPTH_SIZE: + *value = 24; + return 0; + case GLX_STENCIL_SIZE: + *value = 8; + return 0; + default: + *value = 0; + return GLX_BAD_ATTRIBUTE; + } + } + + GLXContextHandle CreateContext(Display* dpy, void* visualInfo, GLXContextHandle share, int) { + EnsureInitialized(); + MGLOG_I("glx: glXCreateContext(visual=%p)", visualInfo); + if (!visualInfo) { + return nullptr; + } + // A legacy GLX 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(dpy, &kFBConfigs[0], share, attribs); + } + + GLXContextHandle CreateNewContext(Display* dpy, GLXFBConfigHandle config, int renderType, + GLXContextHandle share, int) { + EnsureInitialized(); + const auto* info = TryGetFBConfig(config); + if (!info || renderType != GLX_RGBA_TYPE) { + return nullptr; + } + 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(dpy, info, share, attribs); + } + + GLXContextHandle CreateContextAttribsARB(Display* dpy, GLXFBConfigHandle config, + GLXContextHandle share, int, const int* attribList) { + EnsureInitialized(); + const auto* info = TryGetFBConfig(config); + if (!info) { + return nullptr; + } + + 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 GLX_CONTEXT_MAJOR_VERSION_ARB: + major = value; + break; + case GLX_CONTEXT_MINOR_VERSION_ARB: + minor = value; + break; + case GLX_CONTEXT_PROFILE_MASK_ARB: + profileMask = value; + break; + case GLX_CONTEXT_FLAGS_ARB: + flags = value; + break; + case GLX_CONTEXT_OPENGL_NO_ERROR_ARB: + // Accepted and ignored: MobileGL always validates. + break; + default: + MGLOG_D("glXCreateContextAttribsARB: ignoring attrib 0x%04x = 0x%x", attrib, + value); + break; + } + } + } + + if (major < 1 || (profileMask & ~(GLX_CONTEXT_CORE_PROFILE_BIT_ARB | + GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB))) { + return nullptr; + } + + Vector attribs = { + EGL_CONTEXT_MAJOR_VERSION, major, + EGL_CONTEXT_MINOR_VERSION, minor, + }; + const Bool wantsCompat = (profileMask & GLX_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 & GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB) { + attribs.push_back(EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE); + attribs.push_back(EGL_TRUE); + } + if (flags & GLX_CONTEXT_DEBUG_BIT_ARB) { + attribs.push_back(EGL_CONTEXT_OPENGL_DEBUG); + attribs.push_back(EGL_TRUE); + } + attribs.push_back(EGL_NONE); + + return CreateContextFromEGLAttribs(dpy, info, share, attribs.data()); + } + + void DestroyContext(Display*, GLXContextHandle context) { + EnsureInitialized(); + MGLOG_I("glx: glXDestroyContext(%p)", context); + if (t_current.Context == context) { + MakeCurrent(t_current.XDisplay, 0, nullptr); + } + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(context); + if (!object) { + return; + } + if (object->Context != EGL_NO_CONTEXT) { + EGLImpl::DestroyContext(object->Display, object->Context); + } + Contexts().erase(context); + } + + int MakeCurrent(Display* dpy, GLXDrawableHandle drawable, GLXContextHandle context) { + EnsureInitialized(); + MGLOG_D("glx: glXMakeCurrent(drawable=0x%lx, ctx=%p)", drawable, context); + if (!context) { + if (!t_current.Context) { + t_current = {}; + return 1; + } + const EGLBoolean released = + EGLImpl::MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + t_current = {}; + return released == EGL_TRUE ? 1 : 0; + } + + if (drawable == 0) { + return 0; + } + + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(context); + if (!object) { + return 0; + } + + DrawableSurface* surface = EnsureDrawableSurface(dpy, drawable, *object); + if (!surface) { + return 0; + } + + if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface, + object->Context)) { + MGLOG_E("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context); + return 0; + } + t_current = {dpy, drawable, drawable, context}; + return 1; + } + + int MakeContextCurrent(Display* dpy, GLXDrawableHandle draw, GLXDrawableHandle read, + GLXContextHandle context) { + if (context && draw != read) { + // MobileGL's backends reject split draw/read surfaces; bind the draw + // drawable for both, which is what every real caller here needs. + MGLOG_W("glx: glXMakeContextCurrent draw 0x%lx != read 0x%lx, using draw for both", draw, + read); + } + const int result = MakeCurrent(dpy, draw, context); + if (result && context) { + t_current.Read = read; + } + return result; + } + + void SwapBuffers(Display* dpy, GLXDrawableHandle drawable) { + const std::lock_guard lock(RegistryMutex()); + auto& surfaces = DrawableSurfaces(); + auto it = surfaces.find(drawable); + if (it == surfaces.end()) { + MGLOG_W("glx: glXSwapBuffers with no surface for drawable 0x%lx", drawable); + return; + } + SyncSurfaceSize(dpy, drawable, it->second); + EGLImpl::SwapBuffers(it->second.Display, it->second.Surface); + } + + GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window, + const int*) { + EnsureInitialized(); + const auto* info = TryGetFBConfig(config); + if (!info || window == 0) { + return 0; + } + const std::lock_guard lock(RegistryMutex()); + WindowFBConfigs()[window] = info; + // The GLXWindow is the X window itself; the EGL surface is created + // lazily on the first MakeCurrent (same pattern as WGL's HWND cache). + return window; + } + + void DestroyWindow(Display*, GLXDrawableHandle window) { + EnsureInitialized(); + const std::lock_guard lock(RegistryMutex()); + WindowFBConfigs().erase(window); + auto& surfaces = DrawableSurfaces(); + auto it = surfaces.find(window); + if (it == surfaces.end()) { + return; + } + EGLImpl::DestroySurface(it->second.Display, it->second.Surface); + surfaces.erase(it); + } + + GLXContextHandle GetCurrentContext() { + return t_current.Context; + } + + GLXDrawableHandle GetCurrentDrawable() { + return t_current.Draw; + } + + GLXDrawableHandle GetCurrentReadDrawable() { + return t_current.Read; + } + + Display* GetCurrentDisplay() { + return t_current.XDisplay; + } + + int IsDirect(Display*, GLXContextHandle) { + return 1; + } + + void WaitGL() { + EGLImpl::WaitGL(); + } + + void WaitX() { + const auto& x11 = X11(); + if (x11.Valid() && x11.Sync && t_current.XDisplay) { + x11.Sync(t_current.XDisplay, 0); + } + } + + int QueryContext(Display*, GLXContextHandle context, int attribute, int* value) { + const std::lock_guard lock(RegistryMutex()); + auto* object = TryGetContext(context); + if (!object || !value) { + return GLX_BAD_ATTRIBUTE; + } + switch (attribute) { + case GLX_FBCONFIG_ID: + *value = object->FBConfig ? object->FBConfig->FBConfigId : 0; + return 0; + case GLX_RENDER_TYPE: + *value = GLX_RGBA_TYPE; + return 0; + case GLX_SCREEN: + *value = 0; + return 0; + default: + *value = 0; + return GLX_BAD_ATTRIBUTE; + } + } + + void QueryDrawable(Display* dpy, GLXDrawableHandle drawable, int attribute, unsigned int* value) { + if (!value) { + return; + } + switch (attribute) { + case GLX_WIDTH: + case GLX_HEIGHT: { + Uint32 width = 0; + Uint32 height = 0; + if (QueryDrawableSize(dpy, drawable, width, height)) { + *value = attribute == GLX_WIDTH ? width : height; + } + return; + } + case GLX_SWAP_INTERVAL_EXT: + *value = static_cast(SwapIntervalShadow()); + return; + case GLX_MAX_SWAP_INTERVAL_EXT: + *value = 4; + return; + case GLX_FBCONFIG_ID: { + const std::lock_guard lock(RegistryMutex()); + auto& configs = WindowFBConfigs(); + auto it = configs.find(drawable); + *value = it == configs.end() ? 0 : it->second->FBConfigId; + return; + } + default: + *value = 0; + return; + } + } + + void SwapIntervalEXT(Display*, GLXDrawableHandle, int interval) { + const std::lock_guard lock(RegistryMutex()); + EGLDisplay display = EnsureDisplay(); + if (display == EGL_NO_DISPLAY) { + return; + } + // MobileGL validates 0..4; adaptive vsync (negative) clamps to regular. + interval = std::clamp(interval, 0, 4); + EGLImpl::SwapInterval(display, interval); + SwapIntervalShadow() = interval; + } + + int SwapIntervalMESA(unsigned int interval) { + SwapIntervalEXT(nullptr, 0, static_cast(interval)); + return 0; + } + + int GetSwapIntervalMESA() { + const std::lock_guard lock(RegistryMutex()); + return SwapIntervalShadow(); + } + + int SwapIntervalSGI(int interval) { + if (interval < 1) { + interval = 1; + } + SwapIntervalEXT(nullptr, 0, interval); + return 0; + } +} // namespace MobileGL::MG_Impl::GLXImpl + +#endif // __linux__ && !__ANDROID__ diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.h b/MobileGL/MG_Impl/GLXImpl/GLXImpl.h new file mode 100644 index 00000000..7eac092f --- /dev/null +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.h @@ -0,0 +1,69 @@ +// MobileGL - MobileGL/MG_Impl/GLXImpl/GLXImpl.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(__linux__) && !defined(__ANDROID__) + +namespace MobileGL::MG_Impl::GLXImpl { + // GLX layered on MobileGL's own EGL, mirroring WGLImpl/CGLImpl. Handles are + // opaque to callers; XVisualInfo crosses the ABI as void* so this header + // needs no Xlib includes (Includes.h forward-declares Display/XID/Window). + using GLXFBConfigHandle = void*; + using GLXContextHandle = void*; + using GLXDrawableHandle = unsigned long; // XID + + int QueryExtension(Display* dpy, int* errorBase, int* eventBase); + int QueryVersion(Display* dpy, int* major, int* minor); + const char* QueryExtensionsString(Display* dpy, int screen); + const char* GetClientString(Display* dpy, int name); + const char* QueryServerString(Display* dpy, int screen, int name); + + GLXFBConfigHandle* GetFBConfigs(Display* dpy, int screen, int* nelements); + GLXFBConfigHandle* ChooseFBConfig(Display* dpy, int screen, const int* attribList, int* nelements); + int GetFBConfigAttrib(Display* dpy, GLXFBConfigHandle config, int attribute, int* value); + void* GetVisualFromFBConfig(Display* dpy, GLXFBConfigHandle config); + void* ChooseVisual(Display* dpy, int screen, int* attribList); + int GetConfig(Display* dpy, void* visualInfo, int attribute, int* value); + + GLXContextHandle CreateContext(Display* dpy, void* visualInfo, GLXContextHandle share, int direct); + GLXContextHandle CreateNewContext(Display* dpy, GLXFBConfigHandle config, int renderType, + GLXContextHandle share, int direct); + GLXContextHandle CreateContextAttribsARB(Display* dpy, GLXFBConfigHandle config, GLXContextHandle share, + int direct, const int* attribList); + void DestroyContext(Display* dpy, GLXContextHandle context); + int MakeCurrent(Display* dpy, GLXDrawableHandle drawable, GLXContextHandle context); + int MakeContextCurrent(Display* dpy, GLXDrawableHandle draw, GLXDrawableHandle read, + GLXContextHandle context); + void SwapBuffers(Display* dpy, GLXDrawableHandle drawable); + + GLXDrawableHandle CreateWindow(Display* dpy, GLXFBConfigHandle config, GLXDrawableHandle window, + const int* attribList); + void DestroyWindow(Display* dpy, GLXDrawableHandle window); + + GLXContextHandle GetCurrentContext(); + GLXDrawableHandle GetCurrentDrawable(); + GLXDrawableHandle GetCurrentReadDrawable(); + Display* GetCurrentDisplay(); + int IsDirect(Display* dpy, GLXContextHandle context); + void WaitGL(); + void WaitX(); + int QueryContext(Display* dpy, GLXContextHandle context, int attribute, int* value); + void QueryDrawable(Display* dpy, GLXDrawableHandle drawable, int attribute, unsigned int* value); + + void SwapIntervalEXT(Display* dpy, GLXDrawableHandle drawable, int interval); + int SwapIntervalMESA(unsigned int interval); + int GetSwapIntervalMESA(); + int SwapIntervalSGI(int interval); + + // Name -> exported glX entry point (table lives with the exports). + void* GetGLXEntryPoint(const char* name); +} // namespace MobileGL::MG_Impl::GLXImpl + +#endif // __linux__ && !__ANDROID__ diff --git a/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp b/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp index 79286214..924ea2f8 100644 --- a/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp +++ b/MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp @@ -8,11 +8,27 @@ #include "LookUp.h" -namespace MG_Impl::GLXImpl { - // TODO: implement complete GLX functionality +#if defined(__linux__) && !defined(__ANDROID__) +#include "../GLXImpl.h" +#endif +namespace MG_Impl::GLXImpl { void* GetProcAddress(const char* name) { + if (!name) { + return nullptr; + } MGLOG_D("glXGetProcAddress(\"%s\")", name); +#if defined(__linux__) && !defined(__ANDROID__) + if (name[0] == 'g' && name[1] == 'l' && name[2] == 'X') { + // glX entry points resolve from the GLX layer's own table; GL/EGL + // names fall through to the shared resolver below. + void* proc = MobileGL::MG_Impl::GLXImpl::GetGLXEntryPoint(name); + if (!proc) { + MGLOG_D("glXGetProcAddress: unknown glX entry point %s", name); + } + return proc; + } +#endif void* proc = MobileGL::MG_Impl::GetProcAddress(name); if (!proc) { MGLOG_W("Failed to get function: %s", (const char*)name); @@ -25,4 +41,4 @@ namespace MG_Impl::GLXImpl { void* GetProcAddressARB(const char* name) { return GetProcAddress(name); } -} // namespace MG_Impl::GLXImpl \ No newline at end of file +} // namespace MG_Impl::GLXImpl