[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.
This commit is contained in:
BZLZHH
2026-08-05 23:08:29 -04:00
parent d39a706d57
commit 08f98ad9ce
5 changed files with 1455 additions and 4 deletions
+1
View File
@@ -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
@@ -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);
}
}
#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<void*>(glXChooseFBConfig)},
{"glXChooseVisual", reinterpret_cast<void*>(glXChooseVisual)},
{"glXCopyContext", reinterpret_cast<void*>(glXCopyContext)},
{"glXCreateContext", reinterpret_cast<void*>(glXCreateContext)},
{"glXCreateContextAttribsARB", reinterpret_cast<void*>(glXCreateContextAttribsARB)},
{"glXCreateGLXPixmap", reinterpret_cast<void*>(glXCreateGLXPixmap)},
{"glXCreateNewContext", reinterpret_cast<void*>(glXCreateNewContext)},
{"glXCreatePbuffer", reinterpret_cast<void*>(glXCreatePbuffer)},
{"glXCreatePixmap", reinterpret_cast<void*>(glXCreatePixmap)},
{"glXCreateWindow", reinterpret_cast<void*>(glXCreateWindow)},
{"glXDestroyContext", reinterpret_cast<void*>(glXDestroyContext)},
{"glXDestroyGLXPixmap", reinterpret_cast<void*>(glXDestroyGLXPixmap)},
{"glXDestroyPbuffer", reinterpret_cast<void*>(glXDestroyPbuffer)},
{"glXDestroyPixmap", reinterpret_cast<void*>(glXDestroyPixmap)},
{"glXDestroyWindow", reinterpret_cast<void*>(glXDestroyWindow)},
{"glXGetClientString", reinterpret_cast<void*>(glXGetClientString)},
{"glXGetConfig", reinterpret_cast<void*>(glXGetConfig)},
{"glXGetCurrentContext", reinterpret_cast<void*>(glXGetCurrentContext)},
{"glXGetCurrentDisplay", reinterpret_cast<void*>(glXGetCurrentDisplay)},
{"glXGetCurrentDrawable", reinterpret_cast<void*>(glXGetCurrentDrawable)},
{"glXGetCurrentReadDrawable", reinterpret_cast<void*>(glXGetCurrentReadDrawable)},
{"glXGetFBConfigAttrib", reinterpret_cast<void*>(glXGetFBConfigAttrib)},
{"glXGetFBConfigs", reinterpret_cast<void*>(glXGetFBConfigs)},
{"glXGetProcAddress", reinterpret_cast<void*>(glXGetProcAddress)},
{"glXGetProcAddressARB", reinterpret_cast<void*>(glXGetProcAddressARB)},
{"glXGetSelectedEvent", reinterpret_cast<void*>(glXGetSelectedEvent)},
{"glXGetSwapIntervalMESA", reinterpret_cast<void*>(glXGetSwapIntervalMESA)},
{"glXGetVisualFromFBConfig", reinterpret_cast<void*>(glXGetVisualFromFBConfig)},
{"glXIsDirect", reinterpret_cast<void*>(glXIsDirect)},
{"glXMakeContextCurrent", reinterpret_cast<void*>(glXMakeContextCurrent)},
{"glXMakeCurrent", reinterpret_cast<void*>(glXMakeCurrent)},
{"glXQueryContext", reinterpret_cast<void*>(glXQueryContext)},
{"glXQueryDrawable", reinterpret_cast<void*>(glXQueryDrawable)},
{"glXQueryExtension", reinterpret_cast<void*>(glXQueryExtension)},
{"glXQueryExtensionsString", reinterpret_cast<void*>(glXQueryExtensionsString)},
{"glXQueryServerString", reinterpret_cast<void*>(glXQueryServerString)},
{"glXQueryVersion", reinterpret_cast<void*>(glXQueryVersion)},
{"glXSelectEvent", reinterpret_cast<void*>(glXSelectEvent)},
{"glXSwapBuffers", reinterpret_cast<void*>(glXSwapBuffers)},
{"glXSwapIntervalEXT", reinterpret_cast<void*>(glXSwapIntervalEXT)},
{"glXSwapIntervalMESA", reinterpret_cast<void*>(glXSwapIntervalMESA)},
{"glXSwapIntervalSGI", reinterpret_cast<void*>(glXSwapIntervalSGI)},
{"glXUseXFont", reinterpret_cast<void*>(glXUseXFont)},
{"glXWaitGL", reinterpret_cast<void*>(glXWaitGL)},
{"glXWaitX", reinterpret_cast<void*>(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__
File diff suppressed because it is too large Load Diff
+69
View File
@@ -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 <Includes.h>
#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__
+19 -3
View File
@@ -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
} // namespace MG_Impl::GLXImpl