From 47db68d55756867469a0b9707b10724adaf7ac84 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 2 Oct 2026 02:11:36 -0400 Subject: [PATCH] [Feature] (GLX): serve as a glvnd GLX vendor and present remote X windows --- CMakeLists.txt | 3 +- MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp | 6 +- MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp | 79 ++++++----- MobileGL/MG_Impl/EGLImpl/WaylandWindow.h | 9 ++ .../MG_Impl/GLXImpl/Exporting/Definitions.cpp | 47 +++++++ .../MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp | 128 +++++++++++++++++ MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp | 131 ++++++++++++++++-- 7 files changed, 352 insertions(+), 51 deletions(-) create mode 100644 MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b04adef..6a97186e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -399,6 +399,7 @@ set(SOURCE_FILES MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp + MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp MobileGL/MG_Impl/GLXImpl/LookUp/LookUp.cpp @@ -837,7 +838,7 @@ target_compile_definitions(${CMAKE_PROJECT_NAME} if(UNIX AND NOT APPLE AND NOT ANDROID) foreach(MOBILEGL_LOADER_ALIAS - libEGL.so libEGL.so.1 libGL.so libGL.so.1) + libEGL.so libEGL.so.1 libGL.so libGL.so.1 libGLX_mobilegl.so.0) add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink "$" diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 8fa10876..cc7b84e3 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -636,10 +636,10 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLBoolean ReleaseThread() { + // Always succeeds (EGL 1.4 ยง3.11): after the last eglTerminate tore MobileGL down there is + // simply nothing of this thread's left to release. + if (!MG_State::pEGLContext) return EGL_TRUE; auto* state = GetState(); - if (!state) { - return EGL_FALSE; - } if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) { (void)backendObject->MakeEGLCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } diff --git a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp index 1fae94eb..69d8bc7a 100644 --- a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp +++ b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp @@ -25,6 +25,45 @@ #include #include +namespace MobileGL::MG_Impl::EGLImpl { + void ReadBackFrameBGRA(EGLint width, EGLint height, Vector& scratch, Uint8* dst, SizeT dstStride) { + namespace GL = MG_Impl::GLImpl; + GLint readFramebuffer = 0, packBuffer = 0, alignment = 4, rowLength = 0, skipRows = 0, skipPixels = 0; + GL::GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFramebuffer); + GL::GetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &packBuffer); + GL::GetIntegerv(GL_PACK_ALIGNMENT, &alignment); + GL::GetIntegerv(GL_PACK_ROW_LENGTH, &rowLength); + GL::GetIntegerv(GL_PACK_SKIP_ROWS, &skipRows); + GL::GetIntegerv(GL_PACK_SKIP_PIXELS, &skipPixels); + if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, 0); + if (packBuffer != 0) GL::BindBuffer(GL_PIXEL_PACK_BUFFER, 0); + GL::PixelStorei(GL_PACK_ALIGNMENT, 4); + GL::PixelStorei(GL_PACK_ROW_LENGTH, 0); + GL::PixelStorei(GL_PACK_SKIP_ROWS, 0); + GL::PixelStorei(GL_PACK_SKIP_PIXELS, 0); + const SizeT rowBytes = static_cast(width) * 4; + scratch.resize(rowBytes * static_cast(height)); + GL::ReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, scratch.data()); + GL::PixelStorei(GL_PACK_ALIGNMENT, alignment); + GL::PixelStorei(GL_PACK_ROW_LENGTH, rowLength); + GL::PixelStorei(GL_PACK_SKIP_ROWS, skipRows); + GL::PixelStorei(GL_PACK_SKIP_PIXELS, skipPixels); + if (packBuffer != 0) GL::BindBuffer(GL_PIXEL_PACK_BUFFER, static_cast(packBuffer)); + if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(readFramebuffer)); + + for (EGLint y = 0; y < height; ++y) { + const Uint8* src = scratch.data() + static_cast(height - 1 - y) * rowBytes; + Uint8* row = dst + static_cast(y) * dstStride; + for (EGLint x = 0; x < width; ++x) { + row[x * 4 + 0] = src[x * 4 + 2]; + row[x * 4 + 1] = src[x * 4 + 1]; + row[x * 4 + 2] = src[x * 4 + 0]; + row[x * 4 + 3] = src[x * 4 + 3]; + } + } + } +} // namespace MobileGL::MG_Impl::EGLImpl + namespace MobileGL::MG_Impl::EGLImpl::Wayland { namespace { // libwayland-client's types are opaque here: everything goes through wl_proxy_* and the @@ -345,45 +384,9 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { return false; } - // The frame, out of the default framebuffer, through this library's own entry points: the - // application's read framebuffer, pack buffer and pack state are saved and put back, so - // the readback is invisible to it. - namespace GL = MG_Impl::GLImpl; - GLint readFramebuffer = 0, packBuffer = 0, alignment = 4, rowLength = 0, skipRows = 0, skipPixels = 0; - GL::GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFramebuffer); - GL::GetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &packBuffer); - GL::GetIntegerv(GL_PACK_ALIGNMENT, &alignment); - GL::GetIntegerv(GL_PACK_ROW_LENGTH, &rowLength); - GL::GetIntegerv(GL_PACK_SKIP_ROWS, &skipRows); - GL::GetIntegerv(GL_PACK_SKIP_PIXELS, &skipPixels); - if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, 0); - if (packBuffer != 0) GL::BindBuffer(GL_PIXEL_PACK_BUFFER, 0); - GL::PixelStorei(GL_PACK_ALIGNMENT, 4); - GL::PixelStorei(GL_PACK_ROW_LENGTH, 0); - GL::PixelStorei(GL_PACK_SKIP_ROWS, 0); - GL::PixelStorei(GL_PACK_SKIP_PIXELS, 0); - const SizeT rowBytes = static_cast(impl.width) * 4; - impl.scratch.resize(rowBytes * static_cast(impl.height)); - GL::ReadPixels(0, 0, impl.width, impl.height, GL_RGBA, GL_UNSIGNED_BYTE, impl.scratch.data()); - GL::PixelStorei(GL_PACK_ALIGNMENT, alignment); - GL::PixelStorei(GL_PACK_ROW_LENGTH, rowLength); - GL::PixelStorei(GL_PACK_SKIP_ROWS, skipRows); - GL::PixelStorei(GL_PACK_SKIP_PIXELS, skipPixels); - if (packBuffer != 0) GL::BindBuffer(GL_PIXEL_PACK_BUFFER, static_cast(packBuffer)); - if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(readFramebuffer)); - // GL's rows run bottom-up and RGBA; a wl_shm buffer's run top-down and B, G, R, A. - auto* dst = static_cast(buffer->map); - for (EGLint y = 0; y < impl.height; ++y) { - const Uint8* src = impl.scratch.data() + static_cast(impl.height - 1 - y) * rowBytes; - Uint8* row = dst + static_cast(y) * static_cast(buffer->stride); - for (EGLint x = 0; x < impl.width; ++x) { - row[x * 4 + 0] = src[x * 4 + 2]; - row[x * 4 + 1] = src[x * 4 + 1]; - row[x * 4 + 2] = src[x * 4 + 0]; - row[x * 4 + 3] = src[x * 4 + 3]; - } - } + ReadBackFrameBGRA(impl.width, impl.height, impl.scratch, static_cast(buffer->map), + static_cast(buffer->stride)); wl_proxy* surface = impl.window->surface; const uint32_t surfaceVersion = api.getVersion(surface); diff --git a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h index 0317b6ad..4fc984ab 100644 --- a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h +++ b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h @@ -29,6 +29,15 @@ #include +namespace MobileGL::MG_Impl::EGLImpl { + // The current context's default-framebuffer frame, `width` x `height`, written top-down in + // B, G, R, A byte order (a wl_shm ARGB8888 buffer and a 24/32-bit little-endian X image both + // hold that) to `dst`, one row every `dstStride` bytes. Through this library's own entry + // points: the application's read framebuffer, pack buffer and pack state are saved and put + // back, so the readback is invisible to it. `scratch` is the caller's, reused across frames. + void ReadBackFrameBGRA(EGLint width, EGLint height, Vector& scratch, Uint8* dst, SizeT dstStride); +} // namespace MobileGL::MG_Impl::EGLImpl + namespace MobileGL::MG_Impl::EGLImpl::Wayland { // EGL_PLATFORM_WAYLAND_KHR and EGL_PLATFORM_WAYLAND_EXT are the same enum. inline constexpr EGLenum kPlatformWayland = 0x31D8; diff --git a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp index bc4cee2c..f7b0d96f 100644 --- a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp @@ -8,12 +8,20 @@ #include #include "../LookUp/LookUp.h" +#include "../../EGLImpl/EGLImpl.h" + +// Every GLX entry point holds the client's stream lock, as every EGL and GL one does: they are EGL +// calls underneath (and a swap reads the frame back), so they cannot interleave with another +// thread's GL records. +using GLXStreamLock = MobileGL::MG_Impl::EGLImpl::StreamLockScope; MOBILEGL_GLX_API void* glXGetProcAddress(const char* name) { + GLXStreamLock streamLock; return MG_Impl::GLXImpl::GetProcAddress(name); } MOBILEGL_GLX_API void* glXGetProcAddressARB(const char* name) { + GLXStreamLock streamLock; return MG_Impl::GLXImpl::GetProcAddressARB(name); } @@ -27,149 +35,184 @@ namespace GLXImpl = MobileGL::MG_Impl::GLXImpl; // and XVisualInfo* crosses as void*. MOBILEGL_GLX_API int glXQueryExtension(Display* dpy, int* errorBase, int* eventBase) { + GLXStreamLock streamLock; return GLXImpl::QueryExtension(dpy, errorBase, eventBase); } MOBILEGL_GLX_API int glXQueryVersion(Display* dpy, int* major, int* minor) { + GLXStreamLock streamLock; return GLXImpl::QueryVersion(dpy, major, minor); } MOBILEGL_GLX_API const char* glXQueryExtensionsString(Display* dpy, int screen) { + GLXStreamLock streamLock; return GLXImpl::QueryExtensionsString(dpy, screen); } MOBILEGL_GLX_API const char* glXGetClientString(Display* dpy, int name) { + GLXStreamLock streamLock; return GLXImpl::GetClientString(dpy, name); } MOBILEGL_GLX_API const char* glXQueryServerString(Display* dpy, int screen, int name) { + GLXStreamLock streamLock; return GLXImpl::QueryServerString(dpy, screen, name); } MOBILEGL_GLX_API void** glXGetFBConfigs(Display* dpy, int screen, int* nelements) { + GLXStreamLock streamLock; return GLXImpl::GetFBConfigs(dpy, screen, nelements); } MOBILEGL_GLX_API void** glXChooseFBConfig(Display* dpy, int screen, const int* attribList, int* nelements) { + GLXStreamLock streamLock; return GLXImpl::ChooseFBConfig(dpy, screen, attribList, nelements); } MOBILEGL_GLX_API int glXGetFBConfigAttrib(Display* dpy, void* config, int attribute, int* value) { + GLXStreamLock streamLock; return GLXImpl::GetFBConfigAttrib(dpy, config, attribute, value); } MOBILEGL_GLX_API void* glXGetVisualFromFBConfig(Display* dpy, void* config) { + GLXStreamLock streamLock; return GLXImpl::GetVisualFromFBConfig(dpy, config); } MOBILEGL_GLX_API void* glXChooseVisual(Display* dpy, int screen, int* attribList) { + GLXStreamLock streamLock; return GLXImpl::ChooseVisual(dpy, screen, attribList); } MOBILEGL_GLX_API int glXGetConfig(Display* dpy, void* visualInfo, int attribute, int* value) { + GLXStreamLock streamLock; return GLXImpl::GetConfig(dpy, visualInfo, attribute, value); } MOBILEGL_GLX_API void* glXCreateContext(Display* dpy, void* visualInfo, void* shareList, int direct) { + GLXStreamLock streamLock; return GLXImpl::CreateContext(dpy, visualInfo, shareList, direct); } MOBILEGL_GLX_API void* glXCreateNewContext(Display* dpy, void* config, int renderType, void* shareList, int direct) { + GLXStreamLock streamLock; return GLXImpl::CreateNewContext(dpy, config, renderType, shareList, direct); } MOBILEGL_GLX_API void* glXCreateContextAttribsARB(Display* dpy, void* config, void* shareContext, int direct, const int* attribList) { + GLXStreamLock streamLock; return GLXImpl::CreateContextAttribsARB(dpy, config, shareContext, direct, attribList); } MOBILEGL_GLX_API void glXDestroyContext(Display* dpy, void* context) { + GLXStreamLock streamLock; GLXImpl::DestroyContext(dpy, context); } MOBILEGL_GLX_API int glXMakeCurrent(Display* dpy, unsigned long drawable, void* context) { + GLXStreamLock streamLock; return GLXImpl::MakeCurrent(dpy, drawable, context); } MOBILEGL_GLX_API int glXMakeContextCurrent(Display* dpy, unsigned long draw, unsigned long read, void* context) { + GLXStreamLock streamLock; return GLXImpl::MakeContextCurrent(dpy, draw, read, context); } MOBILEGL_GLX_API void glXSwapBuffers(Display* dpy, unsigned long drawable) { + GLXStreamLock streamLock; GLXImpl::SwapBuffers(dpy, drawable); } MOBILEGL_GLX_API unsigned long glXCreateWindow(Display* dpy, void* config, unsigned long window, const int* attribList) { + GLXStreamLock streamLock; return GLXImpl::CreateWindow(dpy, config, window, attribList); } MOBILEGL_GLX_API void glXDestroyWindow(Display* dpy, unsigned long window) { + GLXStreamLock streamLock; GLXImpl::DestroyWindow(dpy, window); } MOBILEGL_GLX_API void* glXGetCurrentContext() { + GLXStreamLock streamLock; return GLXImpl::GetCurrentContext(); } MOBILEGL_GLX_API unsigned long glXGetCurrentDrawable() { + GLXStreamLock streamLock; return GLXImpl::GetCurrentDrawable(); } MOBILEGL_GLX_API unsigned long glXGetCurrentReadDrawable() { + GLXStreamLock streamLock; return GLXImpl::GetCurrentReadDrawable(); } MOBILEGL_GLX_API Display* glXGetCurrentDisplay() { + GLXStreamLock streamLock; return GLXImpl::GetCurrentDisplay(); } MOBILEGL_GLX_API int glXIsDirect(Display* dpy, void* context) { + GLXStreamLock streamLock; return GLXImpl::IsDirect(dpy, context); } MOBILEGL_GLX_API void glXWaitGL() { + GLXStreamLock streamLock; GLXImpl::WaitGL(); } MOBILEGL_GLX_API void glXWaitX() { + GLXStreamLock streamLock; GLXImpl::WaitX(); } MOBILEGL_GLX_API int glXQueryContext(Display* dpy, void* context, int attribute, int* value) { + GLXStreamLock streamLock; return GLXImpl::QueryContext(dpy, context, attribute, value); } MOBILEGL_GLX_API void glXQueryDrawable(Display* dpy, unsigned long drawable, int attribute, unsigned int* value) { + GLXStreamLock streamLock; GLXImpl::QueryDrawable(dpy, drawable, attribute, value); } MOBILEGL_GLX_API void glXSwapIntervalEXT(Display* dpy, unsigned long drawable, int interval) { + GLXStreamLock streamLock; GLXImpl::SwapIntervalEXT(dpy, drawable, interval); } MOBILEGL_GLX_API int glXSwapIntervalMESA(unsigned int interval) { + GLXStreamLock streamLock; return GLXImpl::SwapIntervalMESA(interval); } MOBILEGL_GLX_API int glXGetSwapIntervalMESA() { + GLXStreamLock streamLock; return GLXImpl::GetSwapIntervalMESA(); } MOBILEGL_GLX_API int glXSwapIntervalSGI(int interval) { + GLXStreamLock streamLock; 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) { + GLXStreamLock streamLock; MGLOG_W_ONCE("glx: glXCopyContext is not supported"); } MOBILEGL_GLX_API unsigned long glXCreateGLXPixmap(Display*, void*, unsigned long) { + GLXStreamLock streamLock; MGLOG_W_ONCE("glx: glXCreateGLXPixmap is not supported"); return 0; } @@ -177,6 +220,7 @@ MOBILEGL_GLX_API unsigned long glXCreateGLXPixmap(Display*, void*, unsigned long MOBILEGL_GLX_API void glXDestroyGLXPixmap(Display*, unsigned long) {} MOBILEGL_GLX_API unsigned long glXCreatePixmap(Display*, void*, unsigned long, const int*) { + GLXStreamLock streamLock; MGLOG_W_ONCE("glx: glXCreatePixmap is not supported"); return 0; } @@ -184,6 +228,7 @@ MOBILEGL_GLX_API unsigned long glXCreatePixmap(Display*, void*, unsigned long, c MOBILEGL_GLX_API void glXDestroyPixmap(Display*, unsigned long) {} MOBILEGL_GLX_API unsigned long glXCreatePbuffer(Display*, void*, const int*) { + GLXStreamLock streamLock; MGLOG_W_ONCE("glx: glXCreatePbuffer is not supported"); return 0; } @@ -191,12 +236,14 @@ MOBILEGL_GLX_API unsigned long glXCreatePbuffer(Display*, void*, const int*) { MOBILEGL_GLX_API void glXDestroyPbuffer(Display*, unsigned long) {} MOBILEGL_GLX_API void glXUseXFont(unsigned long, int, int, int) { + GLXStreamLock streamLock; MGLOG_W_ONCE("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) { + GLXStreamLock streamLock; if (eventMask) { *eventMask = 0; } diff --git a/MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp b/MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp new file mode 100644 index 00000000..16501431 --- /dev/null +++ b/MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp @@ -0,0 +1,128 @@ +// MobileGL - MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.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 + +// MOBILEGL AS A GLVND GLX VENDOR. +// +// The libGL.so.1 / libGLX.so.0 an X11 application links on a glvnd system is a dispatcher: it +// picks a vendor library per X screen - the one the X server names, or the one +// __GLX_VENDOR_LIBRARY_NAME names - loads it as libGLX_.so.0 and routes every GLX call and, +// once a context is current, every GL call to it. Under a Wayland compositor the X server is +// Xwayland, which names the distribution's driver; that driver then needs a GPU device this +// machine's X clients do not have, and glxinfo ends in "failed to retrieve device information". +// Installed as libGLX_mobilegl.so.0 and named by __GLX_VENDOR_LIBRARY_NAME=mobilegl, this library +// is the one asked instead, through the same entry points it exports directly. +// +// The ABI (libglvnd's libglxabi.h, vendor ABI 1.0) is mirrored here rather than included: that +// header includes GL/glx.h and Xlib.h, and this library's GLX layer is built without X headers. + +#include + +#if defined(__linux__) && !defined(__ANDROID__) +#include "../GLXImpl.h" +#include "../../GetProcAddress.h" + +namespace { + using namespace MobileGL; + + struct GLXVendorInfo; + using XBool = int; + + // __GLXapiExportsRec, field for field. + struct GLXApiExports { + GLXVendorInfo* (*getDynDispatch)(Display* dpy, int screen); + GLXVendorInfo* (*getCurrentDynDispatch)(); + void* (*fetchDispatchEntry)(GLXVendorInfo* dynDispatch, int index); + void* (*getCurrentContext)(); + int (*addVendorContextMapping)(Display* dpy, void* context, GLXVendorInfo* vendor); + void (*removeVendorContextMapping)(Display* dpy, void* context); + GLXVendorInfo* (*vendorFromContext)(void* context); + int (*addVendorFBConfigMapping)(Display* dpy, void* config, GLXVendorInfo* vendor); + void (*removeVendorFBConfigMapping)(Display* dpy, void* config); + GLXVendorInfo* (*vendorFromFBConfig)(Display* dpy, void* config); + int (*addVendorDrawableMapping)(Display* dpy, XID drawable, GLXVendorInfo* vendor); + void (*removeVendorDrawableMapping)(Display* dpy, XID drawable); + GLXVendorInfo* (*vendorFromDrawable)(Display* dpy, XID drawable); + }; + + // __GLXapiImportsRec, field for field; the patch hooks are optional and left null. + struct GLXApiImports { + XBool (*isScreenSupported)(Display* dpy, int screen); + void* (*getProcAddress)(const unsigned char* procName); + void* (*getDispatchAddress)(const unsigned char* procName); + void (*setDispatchIndex)(const unsigned char* procName, int index); + XBool (*notifyError)(Display* dpy, unsigned char error, XID resid, unsigned char opcode, XBool coreX11error); + unsigned char (*isPatchSupported)(int type, int stubSize); + unsigned char (*initiatePatch)(int type, int stubSize, void* lookupStubOffset); + void (*releasePatch)(); + void (*patchThreadAttach)(); + }; + + constexpr Uint32 kGLXVendorABIMajor = 1; + + const GLXApiExports* g_glxExports = nullptr; + GLXVendorInfo* g_glxVendor = nullptr; + + XBool VendorIsScreenSupported(Display*, int) { return 1; } + + // GLX names are the exported entry points (each holds the stream lock); GL names are the + // implementation's, as eglGetProcAddress hands them out. + void* VendorGetProcAddress(const unsigned char* procName) { + const char* name = reinterpret_cast(procName); + if (std::strncmp(name, "glX", 3) == 0) return MG_Impl::GLXImpl::GetGLXEntryPoint(name); + return MG_Impl::GetProcAddress(name); + } + + // A context made by an extension is one libGLX did not see created, so it is told whose it is + // - or glXMakeCurrent on it finds no vendor and fails. + void* VendorCreateContextAttribsARB(Display* dpy, void* config, void* share, int direct, const int* attribs) { + using Create = void* (*)(Display*, void*, void*, int, const int*); + auto* create = reinterpret_cast(MG_Impl::GLXImpl::GetGLXEntryPoint("glXCreateContextAttribsARB")); + void* context = create ? create(dpy, config, share, direct, attribs) : nullptr; + if (context && g_glxExports && g_glxExports->addVendorContextMapping(dpy, context, g_glxVendor) != 0) { + using Destroy = void (*)(Display*, void*); + if (auto* destroy = reinterpret_cast(MG_Impl::GLXImpl::GetGLXEntryPoint("glXDestroyContext"))) { + destroy(dpy, context); + } + return nullptr; + } + return context; + } + + // libGLX asks for the GLX functions it has no dispatcher of its own for (the extensions). This + // is the only vendor in the process that answers for its screens, so the "dispatcher" is the + // implementation itself; only context creation has bookkeeping to do on the way. + void* VendorGetDispatchAddress(const unsigned char* procName) { + const char* name = reinterpret_cast(procName); + if (std::strcmp(name, "glXCreateContextAttribsARB") == 0) { + return reinterpret_cast(&VendorCreateContextAttribsARB); + } + return MG_Impl::GLXImpl::GetGLXEntryPoint(name); + } + + void VendorSetDispatchIndex(const unsigned char*, int) {} +} // namespace + +extern "C" MOBILEGL_API int __glx_Main(Uint32 version, const void* exportsTable, void* vendor, void* importsTable) { + const auto* exports = static_cast(exportsTable); + auto* imports = static_cast(importsTable); + if ((version >> 16) != kGLXVendorABIMajor || exports == nullptr || imports == nullptr) return 0; + g_glxExports = exports; + g_glxVendor = static_cast(vendor); + imports->isScreenSupported = &VendorIsScreenSupported; + imports->getProcAddress = &VendorGetProcAddress; + imports->getDispatchAddress = &VendorGetDispatchAddress; + imports->setDispatchIndex = &VendorSetDispatchIndex; + imports->notifyError = nullptr; + imports->isPatchSupported = nullptr; + imports->initiatePatch = nullptr; + imports->releasePatch = nullptr; + imports->patchThreadAttach = nullptr; + return 1; +} + +#endif // __linux__ && !__ANDROID__ diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp index ce46a82c..d56623a6 100644 --- a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp @@ -10,6 +10,7 @@ #if defined(__linux__) && !defined(__ANDROID__) #include "../EGLImpl/EGLImpl.h" +#include "../EGLImpl/WaylandWindow.h" #include "../GetProcAddress.h" #include #include @@ -157,6 +158,53 @@ namespace MobileGL::MG_Impl::GLXImpl { return *functions; } + // ---- X11 presentation of a remote drawable (libxcb through libX11's own connection, + // dlopen'd like the rest; xcb's request is plain arguments, no struct layouts) ---- + struct XcbVoidCookie { + unsigned int Sequence; + }; + struct XcbFunctions { + void* (*GetXCBConnection)(Display*) = nullptr; + Uint32 (*GenerateId)(void*) = nullptr; + XcbVoidCookie (*CreateGC)(void*, Uint32, Uint32, Uint32, const void*) = nullptr; + XcbVoidCookie (*FreeGC)(void*, Uint32) = nullptr; + XcbVoidCookie (*PutImage)(void*, Uint8, Uint32, Uint32, Uint16, Uint16, Int16, Int16, Uint8, Uint8, + Uint32, const Uint8*) = nullptr; + Uint32 (*MaximumRequestLength)(void*) = nullptr; + int (*Flush)(void*) = nullptr; + + Bool Valid() const { + return GetXCBConnection && GenerateId && CreateGC && FreeGC && PutImage && MaximumRequestLength && + Flush; + } + }; + + const XcbFunctions& Xcb() { + static const XcbFunctions* functions = [] { + auto* fns = new XcbFunctions(); + void* x11xcb = dlopen("libX11-xcb.so.1", RTLD_LOCAL | RTLD_NOW); + void* xcb = dlopen("libxcb.so.1", RTLD_LOCAL | RTLD_NOW); + if (x11xcb && xcb) { + fns->GetXCBConnection = + reinterpret_castGetXCBConnection)>(dlsym(x11xcb, "XGetXCBConnection")); + fns->GenerateId = reinterpret_castGenerateId)>(dlsym(xcb, "xcb_generate_id")); + fns->CreateGC = reinterpret_castCreateGC)>(dlsym(xcb, "xcb_create_gc")); + fns->FreeGC = reinterpret_castFreeGC)>(dlsym(xcb, "xcb_free_gc")); + fns->PutImage = reinterpret_castPutImage)>(dlsym(xcb, "xcb_put_image")); + fns->MaximumRequestLength = reinterpret_castMaximumRequestLength)>( + dlsym(xcb, "xcb_get_maximum_request_length")); + fns->Flush = reinterpret_castFlush)>(dlsym(xcb, "xcb_flush")); + } + if (!fns->Valid()) { + MGLOG_E_ONCE("glx: libX11-xcb/libxcb entry points missing; remote windows are not shown"); + } + return fns; + }(); + return *functions; + } + + constexpr Uint8 kXcbImageFormatZPixmap = 2; + // ---- FBConfigs: mirror the two EGLState configs, stencil-8 first so // stencil-wanting choosers (GLFW's default hints) match config 1 ---- struct FBConfigInfo { @@ -189,6 +237,13 @@ namespace MobileGL::MG_Impl::GLXImpl { Uint32 Height = 0; std::chrono::steady_clock::time_point LastSizePoll{}; Bool RemotePbuffer = false; + // A remote drawable's presentation: the window's depth, the GC its frames are put + // with (0 until the first one) and the readback buffers. + ::Display* XDisplay = nullptr; + Uint32 Depth = 0; + Uint32 Gc = 0; + Vector Scratch; + Vector Image; }; std::recursive_mutex& RegistryMutex() { @@ -257,7 +312,8 @@ namespace MobileGL::MG_Impl::GLXImpl { return it == contexts.end() ? nullptr : &it->second; } - Bool QueryDrawableSize(Display* dpy, GLXDrawableHandle drawable, Uint32& width, Uint32& height) { + Bool QueryDrawableSize(Display* dpy, GLXDrawableHandle drawable, Uint32& width, Uint32& height, + Uint32* depthOut = nullptr) { const auto& x11 = X11(); if (!x11.Valid() || !dpy || drawable == 0) { return false; @@ -274,9 +330,59 @@ namespace MobileGL::MG_Impl::GLXImpl { } width = std::max(w, 1u); height = std::max(h, 1u); + if (depthOut) *depthOut = depth; return true; } + // A REMOTE DRAWABLE IS A PBUFFER, AND NOTHING OF IT REACHES THE X WINDOW BY ITSELF: the + // backend draws in another process (another OS), where an XID means nothing. So at every + // swap the frame is read back and put on the window with PutImage - the same contract as + // the Wayland windows' wl_shm presentation. Called with the drawable current. + void PresentRemoteDrawable(GLXDrawableHandle drawable, DrawableSurface& surface) { + const EGLImpl::GLStreamScope stream; // the readback is GL records of this thread's context + const auto& xcb = Xcb(); + if (!xcb.Valid() || !surface.XDisplay) return; + if (surface.Depth != 24 && surface.Depth != 32) { + MGLOG_W_ONCE("glx: a depth-%u window cannot be shown (24/32-bit only)", surface.Depth); + return; + } + void* connection = xcb.GetXCBConnection(surface.XDisplay); + if (!connection) return; + if (surface.Gc == 0) { + surface.Gc = xcb.GenerateId(connection); + (void)xcb.CreateGC(connection, surface.Gc, static_cast(drawable), 0, nullptr); + } + const EGLint width = static_cast(surface.Width); + const EGLint height = static_cast(surface.Height); + const SizeT rowBytes = static_cast(width) * 4; + surface.Image.resize(rowBytes * static_cast(height)); + EGLImpl::ReadBackFrameBGRA(width, height, surface.Scratch, surface.Image.data(), rowBytes); + + // A request over the server's maximum length closes the connection, so the image goes + // in bands of whole rows that each fit (the request header is 24 bytes). + const SizeT maxBytes = static_cast(xcb.MaximumRequestLength(connection)) * 4; + const SizeT budget = maxBytes > 64 ? maxBytes - 64 : rowBytes; + const EGLint bandRows = std::max(1, static_cast(budget / rowBytes)); + for (EGLint y = 0; y < height; y += bandRows) { + const EGLint rows = std::min(bandRows, height - y); + (void)xcb.PutImage(connection, kXcbImageFormatZPixmap, static_cast(drawable), surface.Gc, + static_cast(width), static_cast(rows), 0, static_cast(y), 0, + static_cast(surface.Depth), static_cast(rowBytes * rows), + surface.Image.data() + rowBytes * static_cast(y)); + } + (void)xcb.Flush(connection); + } + + void ReleaseDrawableSurface(DrawableSurface& surface) { + if (surface.Gc != 0 && surface.XDisplay) { + const auto& xcb = Xcb(); + if (void* connection = xcb.Valid() ? xcb.GetXCBConnection(surface.XDisplay) : nullptr) { + (void)xcb.FreeGC(connection, surface.Gc); + } + } + EGLImpl::DestroySurface(surface.Display, surface.Surface); + } + // 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 @@ -296,10 +402,7 @@ namespace MobileGL::MG_Impl::GLXImpl { if (width == surface.Width && height == surface.Height) { return; } - // A remote X drawable is backed by a pbuffer; an XID has no meaning - // to the Android worker. Its allocation cannot be resized by the - // native-window hook. Keep the current allocation until recreated. - if (surface.RemotePbuffer) return; + // A remote X drawable is a pbuffer; resizing it gives it a new one at the new size. if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface, static_cast(width), static_cast(height))) { @@ -319,7 +422,8 @@ namespace MobileGL::MG_Impl::GLXImpl { Uint32 width = 0; Uint32 height = 0; - if (!QueryDrawableSize(dpy, drawable, width, height)) { + Uint32 depth = 0; + if (!QueryDrawableSize(dpy, drawable, width, height, &depth)) { MGLOG_E_ONCE("glx: XGetGeometry failed for drawable 0x%lx", drawable); return nullptr; } @@ -329,7 +433,8 @@ namespace MobileGL::MG_Impl::GLXImpl { EGL_HEIGHT, static_cast(height), EGL_NONE, }; - const Bool remotePbuffer = MG_Config::Transport == MG_Config::TransportMode::Spawn; + const Bool remotePbuffer = MG_Config::Transport == MG_Config::TransportMode::Spawn || + MG_Config::Transport == MG_Config::TransportMode::UnixSocket; const EGLint pbufferAttribs[] = {EGL_WIDTH, static_cast(width), EGL_HEIGHT, static_cast(height), EGL_NONE}; EGLSurface surface = remotePbuffer @@ -348,6 +453,8 @@ namespace MobileGL::MG_Impl::GLXImpl { record.Width = width; record.Height = height; record.RemotePbuffer = remotePbuffer; + record.XDisplay = dpy; + record.Depth = depth; record.LastSizePoll = std::chrono::steady_clock::now(); auto [inserted, _] = surfaces.emplace(drawable, record); return &inserted->second; @@ -973,8 +1080,14 @@ namespace MobileGL::MG_Impl::GLXImpl { MGLOG_W_ONCE("glx: glXSwapBuffers with no surface for drawable 0x%lx", drawable); return; } - SyncSurfaceSize(dpy, drawable, it->second); + // What the window shows is the frame the swap finishes, read back while it is still the + // drawable's content (only from the thread it is current to: the readback is a GL call). + if (it->second.RemotePbuffer && t_current.Draw == drawable && t_current.Context) { + PresentRemoteDrawable(drawable, it->second); + } EGLImpl::SwapBuffers(it->second.Display, it->second.Surface); + // A resize takes effect for the next frame. + SyncSurfaceSize(dpy, drawable, it->second); } GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window, @@ -1000,7 +1113,7 @@ namespace MobileGL::MG_Impl::GLXImpl { if (it == surfaces.end()) { return; } - EGLImpl::DestroySurface(it->second.Display, it->second.Surface); + ReleaseDrawableSurface(it->second); surfaces.erase(it); }