[Feature] (GLX): serve as a glvnd GLX vendor and present remote X windows

This commit is contained in:
2026-10-02 02:11:36 -04:00
parent ce11c9accd
commit 47db68d557
7 changed files with 352 additions and 51 deletions
+2 -1
View File
@@ -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
"$<TARGET_FILE_NAME:${CMAKE_PROJECT_NAME}>"
+3 -3
View File
@@ -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);
}
+41 -38
View File
@@ -25,6 +25,45 @@
#include <sys/mman.h>
#include <unistd.h>
namespace MobileGL::MG_Impl::EGLImpl {
void ReadBackFrameBGRA(EGLint width, EGLint height, Vector<Uint8>& 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<SizeT>(width) * 4;
scratch.resize(rowBytes * static_cast<SizeT>(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<GLuint>(packBuffer));
if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, static_cast<GLuint>(readFramebuffer));
for (EGLint y = 0; y < height; ++y) {
const Uint8* src = scratch.data() + static_cast<SizeT>(height - 1 - y) * rowBytes;
Uint8* row = dst + static_cast<SizeT>(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<SizeT>(impl.width) * 4;
impl.scratch.resize(rowBytes * static_cast<SizeT>(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<GLuint>(packBuffer));
if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, static_cast<GLuint>(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<Uint8*>(buffer->map);
for (EGLint y = 0; y < impl.height; ++y) {
const Uint8* src = impl.scratch.data() + static_cast<SizeT>(impl.height - 1 - y) * rowBytes;
Uint8* row = dst + static_cast<SizeT>(y) * static_cast<SizeT>(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<Uint8*>(buffer->map),
static_cast<SizeT>(buffer->stride));
wl_proxy* surface = impl.window->surface;
const uint32_t surfaceVersion = api.getVersion(surface);
+9
View File
@@ -29,6 +29,15 @@
#include <EGL/egl.h>
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<Uint8>& 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;
@@ -8,12 +8,20 @@
#include <Includes.h>
#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;
}
@@ -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_<name>.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 <Includes.h>
#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<const char*>(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<Create>(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<Destroy>(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<const char*>(procName);
if (std::strcmp(name, "glXCreateContextAttribsARB") == 0) {
return reinterpret_cast<void*>(&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<const GLXApiExports*>(exportsTable);
auto* imports = static_cast<GLXApiImports*>(importsTable);
if ((version >> 16) != kGLXVendorABIMajor || exports == nullptr || imports == nullptr) return 0;
g_glxExports = exports;
g_glxVendor = static_cast<GLXVendorInfo*>(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__
+122 -9
View File
@@ -10,6 +10,7 @@
#if defined(__linux__) && !defined(__ANDROID__)
#include "../EGLImpl/EGLImpl.h"
#include "../EGLImpl/WaylandWindow.h"
#include "../GetProcAddress.h"
#include <Init.h>
#include <Config.h>
@@ -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_cast<decltype(fns->GetXCBConnection)>(dlsym(x11xcb, "XGetXCBConnection"));
fns->GenerateId = reinterpret_cast<decltype(fns->GenerateId)>(dlsym(xcb, "xcb_generate_id"));
fns->CreateGC = reinterpret_cast<decltype(fns->CreateGC)>(dlsym(xcb, "xcb_create_gc"));
fns->FreeGC = reinterpret_cast<decltype(fns->FreeGC)>(dlsym(xcb, "xcb_free_gc"));
fns->PutImage = reinterpret_cast<decltype(fns->PutImage)>(dlsym(xcb, "xcb_put_image"));
fns->MaximumRequestLength = reinterpret_cast<decltype(fns->MaximumRequestLength)>(
dlsym(xcb, "xcb_get_maximum_request_length"));
fns->Flush = reinterpret_cast<decltype(fns->Flush)>(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<Uint8> Scratch;
Vector<Uint8> 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<Uint32>(drawable), 0, nullptr);
}
const EGLint width = static_cast<EGLint>(surface.Width);
const EGLint height = static_cast<EGLint>(surface.Height);
const SizeT rowBytes = static_cast<SizeT>(width) * 4;
surface.Image.resize(rowBytes * static_cast<SizeT>(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<SizeT>(xcb.MaximumRequestLength(connection)) * 4;
const SizeT budget = maxBytes > 64 ? maxBytes - 64 : rowBytes;
const EGLint bandRows = std::max<EGLint>(1, static_cast<EGLint>(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<Uint32>(drawable), surface.Gc,
static_cast<Uint16>(width), static_cast<Uint16>(rows), 0, static_cast<Int16>(y), 0,
static_cast<Uint8>(surface.Depth), static_cast<Uint32>(rowBytes * rows),
surface.Image.data() + rowBytes * static_cast<SizeT>(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<EGLint>(width),
static_cast<EGLint>(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<EGLAttrib>(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<EGLint>(width),
EGL_HEIGHT, static_cast<EGLint>(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);
}