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