Files
MobileGL/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp
T

1113 lines
41 KiB
C++

// MobileGL - MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "GLXImpl.h"
#if defined(__linux__) && !defined(__ANDROID__)
#include "../EGLImpl/EGLImpl.h"
#include "../GetProcAddress.h"
#include <Init.h>
namespace MobileGL::MG_Impl::GLXImpl {
namespace {
// ---- GLX tokens (defined locally: GL/glx.h drags in a conflicting gl.h) ----
constexpr int GLX_USE_GL = 1;
constexpr int GLX_BUFFER_SIZE = 2;
constexpr int GLX_LEVEL = 3;
constexpr int GLX_RGBA = 4;
constexpr int GLX_DOUBLEBUFFER = 5;
constexpr int GLX_STEREO = 6;
constexpr int GLX_AUX_BUFFERS = 7;
constexpr int GLX_RED_SIZE = 8;
constexpr int GLX_GREEN_SIZE = 9;
constexpr int GLX_BLUE_SIZE = 10;
constexpr int GLX_ALPHA_SIZE = 11;
constexpr int GLX_DEPTH_SIZE = 12;
constexpr int GLX_STENCIL_SIZE = 13;
constexpr int GLX_ACCUM_RED_SIZE = 14;
constexpr int GLX_ACCUM_GREEN_SIZE = 15;
constexpr int GLX_ACCUM_BLUE_SIZE = 16;
constexpr int GLX_ACCUM_ALPHA_SIZE = 17;
// glXGetConfig/glXGetFBConfigAttrib error returns
constexpr int GLX_BAD_ATTRIBUTE = 2;
// glXGetClientString/glXQueryServerString names
constexpr int GLX_VENDOR = 1;
constexpr int GLX_VERSION = 2;
constexpr int GLX_EXTENSIONS = 3;
// GLX 1.3+ FBConfig attributes
constexpr int GLX_CONFIG_CAVEAT = 0x20;
constexpr int GLX_X_VISUAL_TYPE = 0x22;
constexpr int GLX_TRANSPARENT_TYPE = 0x23;
constexpr int GLX_TRANSPARENT_INDEX_VALUE = 0x24;
constexpr int GLX_TRANSPARENT_RED_VALUE = 0x25;
constexpr int GLX_TRANSPARENT_GREEN_VALUE = 0x26;
constexpr int GLX_TRANSPARENT_BLUE_VALUE = 0x27;
constexpr int GLX_TRANSPARENT_ALPHA_VALUE = 0x28;
constexpr int GLX_DONT_CARE = static_cast<int>(0xFFFFFFFF);
constexpr int GLX_NONE = 0x8000;
constexpr int GLX_TRUE_COLOR = 0x8002;
constexpr int GLX_VISUAL_ID = 0x800B;
constexpr int GLX_SCREEN = 0x800C;
constexpr int GLX_DRAWABLE_TYPE = 0x8010;
constexpr int GLX_RENDER_TYPE = 0x8011;
constexpr int GLX_X_RENDERABLE = 0x8012;
constexpr int GLX_FBCONFIG_ID = 0x8013;
constexpr int GLX_RGBA_TYPE = 0x8014;
constexpr int GLX_MAX_PBUFFER_WIDTH = 0x8016;
constexpr int GLX_MAX_PBUFFER_HEIGHT = 0x8017;
constexpr int GLX_MAX_PBUFFER_PIXELS = 0x8018;
constexpr int GLX_WIDTH = 0x801D;
constexpr int GLX_HEIGHT = 0x801E;
constexpr int GLX_WINDOW_BIT = 0x00000001;
constexpr int GLX_RGBA_BIT = 0x00000001;
constexpr int GLX_SAMPLE_BUFFERS = 100000;
constexpr int GLX_SAMPLES = 100001;
// GLX_EXT_swap_control
constexpr int GLX_SWAP_INTERVAL_EXT = 0x20F1;
constexpr int GLX_MAX_SWAP_INTERVAL_EXT = 0x20F2;
// GLX_ARB_create_context / _profile / _no_error
constexpr int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
constexpr int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
constexpr int GLX_CONTEXT_FLAGS_ARB = 0x2094;
constexpr int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126;
constexpr int GLX_CONTEXT_DEBUG_BIT_ARB = 0x0001;
constexpr int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
constexpr int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
constexpr int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
constexpr int GLX_CONTEXT_OPENGL_NO_ERROR_ARB = 0x31B3;
constexpr const char* kGLXExtensions =
"GLX_ARB_create_context GLX_ARB_create_context_no_error GLX_ARB_create_context_profile "
"GLX_ARB_get_proc_address GLX_EXT_swap_control GLX_MESA_swap_control GLX_SGI_swap_control";
// ---- Xlib access (dlopen'd at runtime, same convention as the backends;
// libX11 is never a link-time dependency of libMobileGL) ----
// ABI-stable mirror of XVisualInfo (Xutil.h is not includable here: the
// Bool/Status macros it needs were popped after the vulkan include).
struct XVisualInfoCompat {
void* visual;
VisualID visualid;
int screen;
int depth;
int c_class;
unsigned long red_mask;
unsigned long green_mask;
unsigned long blue_mask;
int colormap_size;
int bits_per_rgb;
};
constexpr long kVisualIDMask = 0x1;
constexpr long kVisualScreenMask = 0x2;
constexpr long kVisualDepthMask = 0x4;
constexpr long kVisualClassMask = 0x8;
constexpr int kTrueColor = 4;
struct X11Functions {
void* Library = nullptr;
int (*GetGeometry)(Display*, GLXDrawableHandle, GLXDrawableHandle*, int*, int*, unsigned int*,
unsigned int*, unsigned int*, unsigned int*) = nullptr;
XVisualInfoCompat* (*GetVisualInfo)(Display*, long, XVisualInfoCompat*, int*) = nullptr;
int (*GetDefaultScreen)(Display*) = nullptr;
void* (*GetDefaultVisual)(Display*, int) = nullptr;
VisualID (*VisualIDFromVisual)(void*) = nullptr;
int (*Free)(void*) = nullptr;
int (*Sync)(Display*, int) = nullptr;
Bool Valid() const {
return GetGeometry && GetVisualInfo && GetDefaultScreen && GetDefaultVisual &&
VisualIDFromVisual && Free;
}
};
const X11Functions& X11() {
static const X11Functions* functions = [] {
auto* fns = new X11Functions();
for (const char* name : {"libX11.so.6", "libX11.so"}) {
fns->Library = dlopen(name, RTLD_LOCAL | RTLD_NOW);
if (fns->Library) {
break;
}
}
if (fns->Library) {
fns->GetGeometry = reinterpret_cast<decltype(fns->GetGeometry)>(
dlsym(fns->Library, "XGetGeometry"));
fns->GetVisualInfo = reinterpret_cast<decltype(fns->GetVisualInfo)>(
dlsym(fns->Library, "XGetVisualInfo"));
fns->GetDefaultScreen = reinterpret_cast<decltype(fns->GetDefaultScreen)>(
dlsym(fns->Library, "XDefaultScreen"));
fns->GetDefaultVisual = reinterpret_cast<decltype(fns->GetDefaultVisual)>(
dlsym(fns->Library, "XDefaultVisual"));
fns->VisualIDFromVisual = reinterpret_cast<decltype(fns->VisualIDFromVisual)>(
dlsym(fns->Library, "XVisualIDFromVisual"));
fns->Free = reinterpret_cast<decltype(fns->Free)>(dlsym(fns->Library, "XFree"));
fns->Sync = reinterpret_cast<decltype(fns->Sync)>(dlsym(fns->Library, "XSync"));
}
if (!fns->Valid()) {
MGLOG_E_ONCE("glx: failed to load libX11 entry points");
}
return fns;
}();
return *functions;
}
// ---- FBConfigs: mirror the two EGLState configs, stencil-8 first so
// stencil-wanting choosers (GLFW's default hints) match config 1 ----
struct FBConfigInfo {
int FBConfigId;
GLint StencilBits;
};
constexpr FBConfigInfo kFBConfigs[] = {
{1, 8},
{2, 0},
};
constexpr int kFBConfigCount = static_cast<int>(std::size(kFBConfigs));
const FBConfigInfo* TryGetFBConfig(GLXFBConfigHandle config) {
const auto* info = static_cast<const FBConfigInfo*>(config);
return (info >= kFBConfigs && info < kFBConfigs + kFBConfigCount) ? info : nullptr;
}
struct ContextObject {
Display* XDisplay = nullptr;
EGLDisplay Display = EGL_NO_DISPLAY;
EGLConfig Config = nullptr;
EGLContext Context = EGL_NO_CONTEXT;
const FBConfigInfo* FBConfig = nullptr;
};
struct DrawableSurface {
EGLDisplay Display = EGL_NO_DISPLAY;
EGLSurface Surface = EGL_NO_SURFACE;
Uint32 Width = 0;
Uint32 Height = 0;
std::chrono::steady_clock::time_point LastSizePoll{};
};
std::recursive_mutex& RegistryMutex() {
static auto* mutex = new std::recursive_mutex();
return *mutex;
}
UnorderedMap<GLXContextHandle, ContextObject>& Contexts() {
static auto* contexts = new UnorderedMap<GLXContextHandle, ContextObject>();
return *contexts;
}
UnorderedMap<GLXDrawableHandle, DrawableSurface>& DrawableSurfaces() {
static auto* surfaces = new UnorderedMap<GLXDrawableHandle, DrawableSurface>();
return *surfaces;
}
UnorderedMap<GLXDrawableHandle, const FBConfigInfo*>& WindowFBConfigs() {
static auto* configs = new UnorderedMap<GLXDrawableHandle, const FBConfigInfo*>();
return *configs;
}
Uint64& NextContextHandle() {
static auto* handle = new Uint64(0x10000);
return *handle;
}
struct ThreadCurrent {
Display* XDisplay = nullptr;
GLXDrawableHandle Draw = 0;
GLXDrawableHandle Read = 0;
GLXContextHandle Context = nullptr;
};
thread_local ThreadCurrent t_current;
Int& SwapIntervalShadow() {
static auto* interval = new Int(1);
return *interval;
}
void EnsureInitialized() {
// MobileGL::EnsureInitialized (not a local once_flag) so a fresh init
// can follow a full teardown from the last eglTerminate.
MobileGL::EnsureInitialized();
}
EGLDisplay EnsureDisplay() {
EnsureInitialized();
EGLDisplay display = EGLImpl::GetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY) {
return EGL_NO_DISPLAY;
}
if (!EGLImpl::Initialize(display, nullptr, nullptr)) {
return EGL_NO_DISPLAY;
}
return display;
}
GLXContextHandle EncodeContext(Uint64 handle) {
return reinterpret_cast<GLXContextHandle>(static_cast<SizeT>(handle));
}
ContextObject* TryGetContext(GLXContextHandle context) {
auto& contexts = Contexts();
auto it = contexts.find(context);
return it == contexts.end() ? nullptr : &it->second;
}
Bool QueryDrawableSize(Display* dpy, GLXDrawableHandle drawable, Uint32& width, Uint32& height) {
const auto& x11 = X11();
if (!x11.Valid() || !dpy || drawable == 0) {
return false;
}
GLXDrawableHandle root = 0;
int x = 0;
int y = 0;
unsigned int w = 0;
unsigned int h = 0;
unsigned int border = 0;
unsigned int depth = 0;
if (!x11.GetGeometry(dpy, drawable, &root, &x, &y, &w, &h, &border, &depth)) {
return false;
}
width = std::max(w, 1u);
height = std::max(h, 1u);
return true;
}
// The backends never query the X window size themselves; the GLX layer
// owns size discovery and pushes changes through the internal resize hook
// (same contract as the WGL and CGL layers). Polling XGetGeometry is a
// server round trip, so throttle steady-state swaps.
void SyncSurfaceSize(Display* dpy, GLXDrawableHandle drawable, DrawableSurface& surface,
Bool force = false) {
const auto now = std::chrono::steady_clock::now();
if (!force && now - surface.LastSizePoll < std::chrono::milliseconds(250)) {
return;
}
surface.LastSizePoll = now;
Uint32 width = 0;
Uint32 height = 0;
if (!QueryDrawableSize(dpy, drawable, width, height)) {
return;
}
if (width == surface.Width && height == surface.Height) {
return;
}
if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface,
static_cast<EGLint>(width),
static_cast<EGLint>(height))) {
surface.Width = width;
surface.Height = height;
}
}
DrawableSurface* EnsureDrawableSurface(Display* dpy, GLXDrawableHandle drawable,
const ContextObject& context) {
auto& surfaces = DrawableSurfaces();
auto it = surfaces.find(drawable);
if (it != surfaces.end()) {
SyncSurfaceSize(dpy, drawable, it->second, true);
return &it->second;
}
Uint32 width = 0;
Uint32 height = 0;
if (!QueryDrawableSize(dpy, drawable, width, height)) {
MGLOG_E_ONCE("glx: XGetGeometry failed for drawable 0x%lx", drawable);
return nullptr;
}
const EGLAttrib attribs[] = {
EGL_WIDTH, static_cast<EGLAttrib>(width),
EGL_HEIGHT, static_cast<EGLAttrib>(height),
EGL_NONE,
};
EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(
context.Display, context.Config, reinterpret_cast<void*>(drawable), attribs);
if (surface == EGL_NO_SURFACE) {
MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable,
width, height);
return nullptr;
}
DrawableSurface record;
record.Display = context.Display;
record.Surface = surface;
record.Width = width;
record.Height = height;
record.LastSizePoll = std::chrono::steady_clock::now();
auto [inserted, _] = surfaces.emplace(drawable, record);
return &inserted->second;
}
GLXContextHandle CreateContextFromEGLAttribs(Display* dpy, const FBConfigInfo* fbconfig,
GLXContextHandle share,
const EGLint* contextAttribs) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
EGLDisplay display = EnsureDisplay();
if (display == EGL_NO_DISPLAY) {
MGLOG_E_ONCE("glx: no EGL display");
return nullptr;
}
EGLImpl::BindAPI(EGL_OPENGL_API);
EGLContext shareContext = EGL_NO_CONTEXT;
if (share) {
auto* shareObject = TryGetContext(share);
if (!shareObject) {
return nullptr;
}
shareContext = shareObject->Context;
}
const EGLint configAttribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, fbconfig->StencilBits,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_NONE,
};
EGLConfig config = nullptr;
EGLint configCount = 0;
if (!EGLImpl::ChooseConfig(display, configAttribs, &config, 1, &configCount) ||
configCount <= 0) {
MGLOG_E_ONCE("glx: eglChooseConfig failed");
return nullptr;
}
EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs);
if (eglContext == EGL_NO_CONTEXT) {
MGLOG_E_ONCE("glx: eglCreateContext failed");
return nullptr;
}
ContextObject object;
object.XDisplay = dpy;
object.Display = display;
object.Config = config;
object.Context = eglContext;
object.FBConfig = fbconfig;
const auto handle = EncodeContext(NextContextHandle()++);
Contexts()[handle] = object;
MGLOG_I("glx: created context %p (EGL context %p, stencil %d)", handle, eglContext,
fbconfig->StencilBits);
return handle;
}
int FBConfigAttribValue(Display* dpy, const FBConfigInfo& info, int attribute, int* value) {
const auto& x11 = X11();
switch (attribute) {
case GLX_FBCONFIG_ID:
*value = info.FBConfigId;
return 0;
case GLX_BUFFER_SIZE:
*value = 32;
return 0;
case GLX_LEVEL:
*value = 0;
return 0;
case GLX_DOUBLEBUFFER:
*value = 1;
return 0;
case GLX_STEREO:
*value = 0;
return 0;
case GLX_AUX_BUFFERS:
*value = 0;
return 0;
case GLX_RED_SIZE:
case GLX_GREEN_SIZE:
case GLX_BLUE_SIZE:
case GLX_ALPHA_SIZE:
*value = 8;
return 0;
case GLX_DEPTH_SIZE:
*value = 24;
return 0;
case GLX_STENCIL_SIZE:
*value = info.StencilBits;
return 0;
case GLX_ACCUM_RED_SIZE:
case GLX_ACCUM_GREEN_SIZE:
case GLX_ACCUM_BLUE_SIZE:
case GLX_ACCUM_ALPHA_SIZE:
*value = 0;
return 0;
case GLX_RENDER_TYPE:
*value = GLX_RGBA_BIT;
return 0;
case GLX_DRAWABLE_TYPE:
*value = GLX_WINDOW_BIT;
return 0;
case GLX_X_RENDERABLE:
*value = 1;
return 0;
case GLX_X_VISUAL_TYPE:
*value = GLX_TRUE_COLOR;
return 0;
case GLX_CONFIG_CAVEAT:
case GLX_TRANSPARENT_TYPE:
*value = GLX_NONE;
return 0;
case GLX_TRANSPARENT_INDEX_VALUE:
case GLX_TRANSPARENT_RED_VALUE:
case GLX_TRANSPARENT_GREEN_VALUE:
case GLX_TRANSPARENT_BLUE_VALUE:
case GLX_TRANSPARENT_ALPHA_VALUE:
*value = 0;
return 0;
case GLX_VISUAL_ID: {
*value = 0;
if (x11.Valid() && dpy) {
const int screen = x11.GetDefaultScreen(dpy);
void* visual = x11.GetDefaultVisual(dpy, screen);
if (visual) {
*value = static_cast<int>(x11.VisualIDFromVisual(visual));
}
}
return 0;
}
case GLX_SCREEN:
*value = (x11.Valid() && dpy) ? x11.GetDefaultScreen(dpy) : 0;
return 0;
case GLX_MAX_PBUFFER_WIDTH:
case GLX_MAX_PBUFFER_HEIGHT:
*value = 16384;
return 0;
case GLX_MAX_PBUFFER_PIXELS:
*value = 16384 * 16384;
return 0;
case GLX_SAMPLE_BUFFERS:
case GLX_SAMPLES:
*value = 0;
return 0;
default:
*value = 0;
return GLX_BAD_ATTRIBUTE;
}
}
// Resolve the X visual all our configs render to: the default visual of
// the screen (matches EGLState's EGL_NATIVE_VISUAL_ID policy), falling
// back to any 24-bit TrueColor visual. Caller frees with XFree.
XVisualInfoCompat* ResolveVisual(Display* dpy, int screen) {
const auto& x11 = X11();
if (!x11.Valid() || !dpy) {
return nullptr;
}
if (screen < 0) {
screen = x11.GetDefaultScreen(dpy);
}
if (void* visual = x11.GetDefaultVisual(dpy, screen)) {
XVisualInfoCompat tmpl{};
tmpl.visualid = x11.VisualIDFromVisual(visual);
int count = 0;
XVisualInfoCompat* info = x11.GetVisualInfo(dpy, kVisualIDMask, &tmpl, &count);
if (info && count >= 1 && info->depth >= 24 && info->c_class == kTrueColor) {
return info;
}
if (info) {
x11.Free(info);
}
}
XVisualInfoCompat tmpl{};
tmpl.screen = screen;
tmpl.depth = 24;
tmpl.c_class = kTrueColor;
int count = 0;
XVisualInfoCompat* info = x11.GetVisualInfo(
dpy, kVisualScreenMask | kVisualDepthMask | kVisualClassMask, &tmpl, &count);
if (info && count < 1) {
x11.Free(info);
return nullptr;
}
return info;
}
} // namespace
int QueryExtension(Display*, int* errorBase, int* eventBase) {
if (errorBase) {
*errorBase = 0;
}
if (eventBase) {
*eventBase = 0;
}
return 1;
}
int QueryVersion(Display*, int* major, int* minor) {
if (major) {
*major = 1;
}
if (minor) {
*minor = 4;
}
return 1;
}
const char* QueryExtensionsString(Display*, int) {
return kGLXExtensions;
}
const char* GetClientString(Display*, int name) {
switch (name) {
case GLX_VENDOR:
return "MobileGL";
case GLX_VERSION:
return "1.4";
case GLX_EXTENSIONS:
return kGLXExtensions;
default:
return nullptr;
}
}
const char* QueryServerString(Display* dpy, int, int name) {
return GetClientString(dpy, name);
}
GLXFBConfigHandle* GetFBConfigs(Display*, int, int* nelements) {
auto** configs = static_cast<GLXFBConfigHandle*>(
std::malloc(sizeof(GLXFBConfigHandle) * kFBConfigCount));
if (!configs) {
if (nelements) {
*nelements = 0;
}
return nullptr;
}
for (int i = 0; i < kFBConfigCount; ++i) {
configs[i] = const_cast<FBConfigInfo*>(&kFBConfigs[i]);
}
if (nelements) {
*nelements = kFBConfigCount;
}
return configs;
}
GLXFBConfigHandle* ChooseFBConfig(Display* dpy, int screen, const int* attribList, int* nelements) {
// Every config satisfies >= size matches for our fixed RGBA8888/24 caps;
// the only distinguishing filters are stencil size and hard mismatches.
Bool acceptConfig[kFBConfigCount] = {true, true};
if (attribList) {
for (SizeT i = 0; attribList[i] != 0; i += 2) {
const int attrib = attribList[i];
const int value = attribList[i + 1];
if (value == GLX_DONT_CARE) {
continue;
}
switch (attrib) {
case GLX_STENCIL_SIZE:
for (int c = 0; c < kFBConfigCount; ++c) {
if (kFBConfigs[c].StencilBits < value) {
acceptConfig[c] = false;
}
}
break;
case GLX_FBCONFIG_ID:
for (int c = 0; c < kFBConfigCount; ++c) {
if (kFBConfigs[c].FBConfigId != value) {
acceptConfig[c] = false;
}
}
break;
case GLX_RED_SIZE:
case GLX_GREEN_SIZE:
case GLX_BLUE_SIZE:
case GLX_ALPHA_SIZE:
if (value > 8) {
acceptConfig[0] = acceptConfig[1] = false;
}
break;
case GLX_BUFFER_SIZE:
if (value > 32) {
acceptConfig[0] = acceptConfig[1] = false;
}
break;
case GLX_DEPTH_SIZE:
if (value > 24) {
acceptConfig[0] = acceptConfig[1] = false;
}
break;
case GLX_DOUBLEBUFFER:
if (value != 1) {
acceptConfig[0] = acceptConfig[1] = false;
}
break;
case GLX_STEREO:
case GLX_AUX_BUFFERS:
case GLX_SAMPLE_BUFFERS:
case GLX_SAMPLES:
case GLX_LEVEL:
case GLX_ACCUM_RED_SIZE:
case GLX_ACCUM_GREEN_SIZE:
case GLX_ACCUM_BLUE_SIZE:
case GLX_ACCUM_ALPHA_SIZE:
if (value > 0) {
acceptConfig[0] = acceptConfig[1] = false;
}
break;
case GLX_RENDER_TYPE:
if ((value & GLX_RGBA_BIT) == 0) {
acceptConfig[0] = acceptConfig[1] = false;
}
break;
case GLX_DRAWABLE_TYPE:
if ((value & ~GLX_WINDOW_BIT) != 0) {
acceptConfig[0] = acceptConfig[1] = false;
}
break;
default:
// Lenient like EGLState's ChooseConfig: unknown attributes
// are ignored rather than failing the whole request.
break;
}
}
}
int count = 0;
for (int c = 0; c < kFBConfigCount; ++c) {
if (acceptConfig[c]) {
++count;
}
}
if (count == 0) {
if (nelements) {
*nelements = 0;
}
return nullptr;
}
auto** configs =
static_cast<GLXFBConfigHandle*>(std::malloc(sizeof(GLXFBConfigHandle) * count));
if (!configs) {
if (nelements) {
*nelements = 0;
}
return nullptr;
}
int out = 0;
for (int c = 0; c < kFBConfigCount; ++c) {
if (acceptConfig[c]) {
configs[out++] = const_cast<FBConfigInfo*>(&kFBConfigs[c]);
}
}
if (nelements) {
*nelements = count;
}
(void)dpy;
(void)screen;
return configs;
}
int GetFBConfigAttrib(Display* dpy, GLXFBConfigHandle config, int attribute, int* value) {
const auto* info = TryGetFBConfig(config);
if (!info || !value) {
return GLX_BAD_ATTRIBUTE;
}
return FBConfigAttribValue(dpy, *info, attribute, value);
}
void* GetVisualFromFBConfig(Display* dpy, GLXFBConfigHandle config) {
if (!TryGetFBConfig(config)) {
return nullptr;
}
return ResolveVisual(dpy, -1);
}
void* ChooseVisual(Display* dpy, int screen, int* attribList) {
// Legacy visual chooser: our single RGBA8888/24/8 double-buffered visual
// satisfies any RGBA request; color-index and stereo are unsupported.
Bool wantsRGBA = false;
if (attribList) {
for (SizeT i = 0; attribList[i] != 0;) {
const int attrib = attribList[i];
if (attrib == GLX_RGBA || attrib == GLX_USE_GL || attrib == GLX_DOUBLEBUFFER) {
wantsRGBA = wantsRGBA || attrib == GLX_RGBA;
++i;
continue;
}
if (attrib == GLX_STEREO) {
return nullptr;
}
i += 2;
}
}
if (!wantsRGBA) {
return nullptr;
}
return ResolveVisual(dpy, screen);
}
int GetConfig(Display*, void* visualInfo, int attribute, int* value) {
if (!visualInfo || !value) {
return GLX_BAD_ATTRIBUTE;
}
switch (attribute) {
case GLX_USE_GL:
case GLX_RGBA:
case GLX_DOUBLEBUFFER:
*value = 1;
return 0;
case GLX_STEREO:
case GLX_LEVEL:
case GLX_AUX_BUFFERS:
case GLX_ACCUM_RED_SIZE:
case GLX_ACCUM_GREEN_SIZE:
case GLX_ACCUM_BLUE_SIZE:
case GLX_ACCUM_ALPHA_SIZE:
*value = 0;
return 0;
case GLX_BUFFER_SIZE:
*value = 32;
return 0;
case GLX_RED_SIZE:
case GLX_GREEN_SIZE:
case GLX_BLUE_SIZE:
case GLX_ALPHA_SIZE:
*value = 8;
return 0;
case GLX_DEPTH_SIZE:
*value = 24;
return 0;
case GLX_STENCIL_SIZE:
*value = 8;
return 0;
default:
*value = 0;
return GLX_BAD_ATTRIBUTE;
}
}
GLXContextHandle CreateContext(Display* dpy, void* visualInfo, GLXContextHandle share, int) {
EnsureInitialized();
MGLOG_I("glx: glXCreateContext(visual=%p)", visualInfo);
if (!visualInfo) {
return nullptr;
}
// A legacy GLX context is a compatibility-profile context; MobileGL keys
// its relaxed-semantics mode off the explicit compatibility bit.
const EGLint attribs[] = {
EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, 3,
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
EGL_NONE,
};
return CreateContextFromEGLAttribs(dpy, &kFBConfigs[0], share, attribs);
}
GLXContextHandle CreateNewContext(Display* dpy, GLXFBConfigHandle config, int renderType,
GLXContextHandle share, int) {
EnsureInitialized();
const auto* info = TryGetFBConfig(config);
if (!info || renderType != GLX_RGBA_TYPE) {
return nullptr;
}
const EGLint attribs[] = {
EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, 3,
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
EGL_NONE,
};
return CreateContextFromEGLAttribs(dpy, info, share, attribs);
}
GLXContextHandle CreateContextAttribsARB(Display* dpy, GLXFBConfigHandle config,
GLXContextHandle share, int, const int* attribList) {
EnsureInitialized();
const auto* info = TryGetFBConfig(config);
if (!info) {
return nullptr;
}
int major = 1;
int minor = 0;
int profileMask = 0;
int flags = 0;
if (attribList) {
for (SizeT i = 0; attribList[i] != 0; i += 2) {
const int attrib = attribList[i];
const int value = attribList[i + 1];
switch (attrib) {
case GLX_CONTEXT_MAJOR_VERSION_ARB:
major = value;
break;
case GLX_CONTEXT_MINOR_VERSION_ARB:
minor = value;
break;
case GLX_CONTEXT_PROFILE_MASK_ARB:
profileMask = value;
break;
case GLX_CONTEXT_FLAGS_ARB:
flags = value;
break;
case GLX_CONTEXT_OPENGL_NO_ERROR_ARB:
// Accepted and ignored: MobileGL always validates.
break;
default:
MGLOG_D("glXCreateContextAttribsARB: ignoring attrib 0x%04x = 0x%x", attrib,
value);
break;
}
}
}
if (major < 1 || (profileMask & ~(GLX_CONTEXT_CORE_PROFILE_BIT_ARB |
GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB))) {
return nullptr;
}
Vector<EGLint> attribs = {
EGL_CONTEXT_MAJOR_VERSION, major,
EGL_CONTEXT_MINOR_VERSION, minor,
};
const Bool wantsCompat = (profileMask & GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB) != 0;
if (major > 3 || (major == 3 && minor >= 2) || profileMask != 0) {
attribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK);
attribs.push_back(wantsCompat ? EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT
: EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT);
}
if (flags & GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB) {
attribs.push_back(EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE);
attribs.push_back(EGL_TRUE);
}
if (flags & GLX_CONTEXT_DEBUG_BIT_ARB) {
attribs.push_back(EGL_CONTEXT_OPENGL_DEBUG);
attribs.push_back(EGL_TRUE);
}
attribs.push_back(EGL_NONE);
return CreateContextFromEGLAttribs(dpy, info, share, attribs.data());
}
void DestroyContext(Display*, GLXContextHandle context) {
EnsureInitialized();
MGLOG_I("glx: glXDestroyContext(%p)", context);
if (t_current.Context == context) {
MakeCurrent(t_current.XDisplay, 0, nullptr);
}
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(context);
if (!object) {
return;
}
if (object->Context != EGL_NO_CONTEXT) {
EGLImpl::DestroyContext(object->Display, object->Context);
}
Contexts().erase(context);
}
int MakeCurrent(Display* dpy, GLXDrawableHandle drawable, GLXContextHandle context) {
EnsureInitialized();
MGLOG_D("glx: glXMakeCurrent(drawable=0x%lx, ctx=%p)", drawable, context);
if (!context) {
if (!t_current.Context) {
t_current = {};
return 1;
}
const EGLBoolean released =
EGLImpl::MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
t_current = {};
return released == EGL_TRUE ? 1 : 0;
}
if (drawable == 0) {
return 0;
}
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(context);
if (!object) {
return 0;
}
DrawableSurface* surface = EnsureDrawableSurface(dpy, drawable, *object);
if (!surface) {
return 0;
}
if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface,
object->Context)) {
MGLOG_E_ONCE("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context);
return 0;
}
t_current = {dpy, drawable, drawable, context};
return 1;
}
int MakeContextCurrent(Display* dpy, GLXDrawableHandle draw, GLXDrawableHandle read,
GLXContextHandle context) {
if (context && draw != read) {
// MobileGL's backends reject split draw/read surfaces; bind the draw
// drawable for both, which is what every real caller here needs.
MGLOG_W_ONCE("glx: glXMakeContextCurrent draw 0x%lx != read 0x%lx, using draw for both", draw,
read);
}
const int result = MakeCurrent(dpy, draw, context);
if (result && context) {
t_current.Read = read;
}
return result;
}
void SwapBuffers(Display* dpy, GLXDrawableHandle drawable) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto& surfaces = DrawableSurfaces();
auto it = surfaces.find(drawable);
if (it == surfaces.end()) {
MGLOG_W_ONCE("glx: glXSwapBuffers with no surface for drawable 0x%lx", drawable);
return;
}
SyncSurfaceSize(dpy, drawable, it->second);
EGLImpl::SwapBuffers(it->second.Display, it->second.Surface);
}
GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window,
const int*) {
EnsureInitialized();
const auto* info = TryGetFBConfig(config);
if (!info || window == 0) {
return 0;
}
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
WindowFBConfigs()[window] = info;
// The GLXWindow is the X window itself; the EGL surface is created
// lazily on the first MakeCurrent (same pattern as WGL's HWND cache).
return window;
}
void DestroyWindow(Display*, GLXDrawableHandle window) {
EnsureInitialized();
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
WindowFBConfigs().erase(window);
auto& surfaces = DrawableSurfaces();
auto it = surfaces.find(window);
if (it == surfaces.end()) {
return;
}
EGLImpl::DestroySurface(it->second.Display, it->second.Surface);
surfaces.erase(it);
}
GLXContextHandle GetCurrentContext() {
return t_current.Context;
}
GLXDrawableHandle GetCurrentDrawable() {
return t_current.Draw;
}
GLXDrawableHandle GetCurrentReadDrawable() {
return t_current.Read;
}
Display* GetCurrentDisplay() {
return t_current.XDisplay;
}
int IsDirect(Display*, GLXContextHandle) {
return 1;
}
void WaitGL() {
EGLImpl::WaitGL();
}
void WaitX() {
const auto& x11 = X11();
if (x11.Valid() && x11.Sync && t_current.XDisplay) {
x11.Sync(t_current.XDisplay, 0);
}
}
int QueryContext(Display*, GLXContextHandle context, int attribute, int* value) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto* object = TryGetContext(context);
if (!object || !value) {
return GLX_BAD_ATTRIBUTE;
}
switch (attribute) {
case GLX_FBCONFIG_ID:
*value = object->FBConfig ? object->FBConfig->FBConfigId : 0;
return 0;
case GLX_RENDER_TYPE:
*value = GLX_RGBA_TYPE;
return 0;
case GLX_SCREEN:
*value = 0;
return 0;
default:
*value = 0;
return GLX_BAD_ATTRIBUTE;
}
}
void QueryDrawable(Display* dpy, GLXDrawableHandle drawable, int attribute, unsigned int* value) {
if (!value) {
return;
}
switch (attribute) {
case GLX_WIDTH:
case GLX_HEIGHT: {
Uint32 width = 0;
Uint32 height = 0;
if (QueryDrawableSize(dpy, drawable, width, height)) {
*value = attribute == GLX_WIDTH ? width : height;
}
return;
}
case GLX_SWAP_INTERVAL_EXT:
*value = static_cast<unsigned int>(SwapIntervalShadow());
return;
case GLX_MAX_SWAP_INTERVAL_EXT:
*value = 4;
return;
case GLX_FBCONFIG_ID: {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
auto& configs = WindowFBConfigs();
auto it = configs.find(drawable);
*value = it == configs.end() ? 0 : it->second->FBConfigId;
return;
}
default:
*value = 0;
return;
}
}
void SwapIntervalEXT(Display*, GLXDrawableHandle, int interval) {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
EGLDisplay display = EnsureDisplay();
if (display == EGL_NO_DISPLAY) {
return;
}
// MobileGL validates 0..4; adaptive vsync (negative) clamps to regular.
interval = std::clamp(interval, 0, 4);
EGLImpl::SwapInterval(display, interval);
SwapIntervalShadow() = interval;
}
int SwapIntervalMESA(unsigned int interval) {
SwapIntervalEXT(nullptr, 0, static_cast<int>(interval));
return 0;
}
int GetSwapIntervalMESA() {
const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
return SwapIntervalShadow();
}
int SwapIntervalSGI(int interval) {
if (interval < 1) {
interval = 1;
}
SwapIntervalEXT(nullptr, 0, interval);
return 0;
}
} // namespace MobileGL::MG_Impl::GLXImpl
#endif // __linux__ && !__ANDROID__