mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feature] (GLX): serve as a glvnd GLX vendor and present remote X windows
This commit is contained in:
+2
-1
@@ -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}>"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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__
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user