[Fix] (EGL+GL): let ANGLE's GL backend run on MobileGL (GBM displays, foreign wl_display, precision query, ES-free GL_VERSION)

This commit is contained in:
2026-10-02 02:58:55 -04:00
parent 47db68d557
commit 0b6842ac8b
8 changed files with 107 additions and 5 deletions
+25
View File
@@ -194,6 +194,19 @@ namespace MobileGL::MG_Impl::EGLImpl {
// reads the frame back and attaches it to the wl_surface (WaylandWindow.h). Answers false
// when the display is not a Wayland one; otherwise *out is the surface, or EGL_NO_SURFACE
// with the error set.
// A gbm_surface is scanout memory of a device this library does not draw with; a window
// on a GBM display is refused rather than taken for some other kind of window.
template <typename State>
Bool RefuseGbmWindow(State* state, EGLDisplay dpy) {
constexpr EGLenum kPlatformGbm = 0x31D7; // EGL_PLATFORM_GBM_KHR == EGL_PLATFORM_GBM_MESA
Uint64 nativeDisplay = 0;
EGLenum platform = EGL_NONE;
if (!state->GetDisplayNative(dpy, &nativeDisplay, &platform) || platform != kPlatformGbm) return false;
MGLOG_W_ONCE("EGL: window surfaces on a GBM display are not supported (offscreen display only)");
state->SetError(EGL_BAD_NATIVE_WINDOW);
return true;
}
template <typename State>
Bool CreateWaylandWindowSurface(State* state, EGLDisplay dpy, EGLConfig config, void* window,
Bool platformWindow, EGLSurface* out) {
@@ -274,6 +287,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
return EGL_NO_SURFACE;
}
#if MOBILEGL_WAYLAND_WINDOWS
if (RefuseGbmWindow(state, dpy)) return EGL_NO_SURFACE;
{
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
EGLSurface waylandSurface = EGL_NO_SURFACE;
@@ -960,6 +974,16 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state) {
return EGL_NO_DISPLAY;
}
#if MOBILEGL_WAYLAND_WINDOWS
// A "GBM" display whose native display is a wl_display is a Wayland one: Chromium's
// Wayland GL layer names the GBM platform for every display it asks for, including the
// one on its own Wayland connection whose windows are wl_egl_windows.
constexpr EGLenum kPlatformGbm = 0x31D7;
if (platform == kPlatformGbm && native_display != nullptr &&
Wayland::IsWaylandDisplay(reinterpret_cast<Uint64>(native_display), EGL_NONE)) {
platform = Wayland::kPlatformWayland;
}
#endif
return state->GetPlatformDisplay(platform, native_display);
}
@@ -970,6 +994,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
return EGL_NO_SURFACE;
}
#if MOBILEGL_WAYLAND_WINDOWS
if (RefuseGbmWindow(state, dpy)) return EGL_NO_SURFACE;
{
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
EGLSurface waylandSurface = EGL_NO_SURFACE;
@@ -6,7 +6,12 @@ namespace MobileGL::MG_Impl::EGLImpl {
// are absent until their entry points and image paths exist.
inline constexpr const char* kDeviceExtensionString = "";
#if defined(__linux__) && !defined(__ANDROID__)
#define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS " EGL_KHR_platform_wayland EGL_EXT_platform_wayland"
// GBM is claimed as a display platform only: a display on a gbm_device is an offscreen display
// like a surfaceless one (the frames come from the backend, not from the device), and window
// surfaces on it are refused. Chromium's Wayland GPU process hands its GL implementation nothing
// but a GBM display, so without the claim it never reaches this library at all.
#define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS \
" EGL_KHR_platform_wayland EGL_EXT_platform_wayland EGL_KHR_platform_gbm EGL_MESA_platform_gbm"
#else
#define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS ""
#endif
+22 -1
View File
@@ -23,6 +23,7 @@
#include <fcntl.h>
#include <mutex>
#include <sys/mman.h>
#include <sys/uio.h>
#include <unistd.h>
namespace MobileGL::MG_Impl::EGLImpl {
@@ -302,12 +303,32 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
void OnWindowDestroyed(void* data) { static_cast<WindowSurface::Impl*>(data)->window = nullptr; }
} // namespace
namespace {
// Copies `size` bytes at `address` of this process, or answers false where nothing is
// mapped - the kernel does the reading, so a wild pointer is an error code, not a fault.
Bool ReadOwnMemory(const void* address, void* out, SizeT size) {
iovec local{out, size};
iovec remote{const_cast<void*>(address), size};
return process_vm_readv(getpid(), &local, 1, &remote, 1, 0) == static_cast<ssize_t>(size);
}
} // namespace
Bool IsWaylandDisplay(Uint64 nativeDisplay, EGLenum platform) {
if (platform == kPlatformWayland) return true;
if (nativeDisplay == 0 || (platform != EGL_NONE && platform != 0)) return false;
const Api& api = WaylandApi();
if (!api.loaded) return false;
return *reinterpret_cast<const void* const*>(static_cast<uintptr_t>(nativeDisplay)) == api.displayInterface;
const void* interface = *reinterpret_cast<const void* const*>(static_cast<uintptr_t>(nativeDisplay));
if (interface == api.displayInterface) return true;
// AN APPLICATION WITH ITS OWN COPY OF LIBWAYLAND-CLIENT (Chromium links one in statically)
// has its own wl_display_interface, at its own address. The interface is still the
// protocol's: a wl_interface whose first member names it "wl_display". Whatever else the
// first word of a native display is (a gbm_device's is a function address), the probe
// only reads through the kernel.
const char* name = nullptr;
char text[sizeof("wl_display")] = {};
return interface != nullptr && ReadOwnMemory(interface, &name, sizeof(name)) && name != nullptr &&
ReadOwnMemory(name, text, sizeof(text)) && std::memcmp(text, "wl_display", sizeof(text)) == 0;
}
Bool IsWaylandWindow(const void* window) {
+2 -1
View File
@@ -44,7 +44,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
// Whether a display is a Wayland one: asked for as EGL_PLATFORM_WAYLAND, or (eglGetDisplay with
// no platform) a native display that is a wl_display - recognised the way Mesa does, by its
// first word being libwayland-client's wl_display_interface.
// first word being libwayland-client's wl_display_interface (or, for an application with its
// own copy of libwayland, an interface named "wl_display").
Bool IsWaylandDisplay(Uint64 nativeDisplay, EGLenum platform);
// Whether `window` looks like a wl_egl_window this library can present to.
@@ -156,7 +156,7 @@ DECLARE_GL_FUNCTION_HEAD(void, GetProgramInfoLog, GLuint program, GLsizei bufSiz
DECLARE_GL_FUNCTION_HEAD(void, GetRenderbufferParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetRenderbufferParameteriv, target, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, GetShaderiv, GLuint shader, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetShaderiv, shader, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, GetShaderInfoLog, GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetShaderInfoLog, shader, bufSize, length, infoLog)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetShaderPrecisionFormat, GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetShaderPrecisionFormat, shadertype, precisiontype, range, precision)
DECLARE_GL_FUNCTION_HEAD(void, GetShaderPrecisionFormat, GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetShaderPrecisionFormat, shadertype, precisiontype, range, precision)
DECLARE_GL_FUNCTION_HEAD(void, GetShaderSource, GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetShaderSource, shader, bufSize, length, source)
DECLARE_GL_FUNCTION_HEAD(void, GetTexParameterfv, GLenum target, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameterfv, target, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, GetTexParameteriv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameteriv, target, pname, params)
+9 -1
View File
@@ -617,11 +617,19 @@ namespace MobileGL::MG_Impl::GLImpl {
MGLOG_D("vendorString: %s", vendorString.c_str());
return (const GLubyte*)vendorString.c_str();
case GL_VERSION: {
// "OpenGL ES" must not appear anywhere in a desktop GL_VERSION: some callers (ANGLE's GL
// backend among them) search the whole string for it rather than its prefix, and
// then take this for an ES driver, read the version from the wrong offsets and look
// for ES-named extensions. The backend is named "GLES" here for that reason only.
String backendName = rendererInfo.BackendName;
if (const auto at = backendName.find("OpenGL ES"); at != String::npos) {
backendName.replace(at, sizeof("OpenGL ES") - 1, "GLES");
}
versionStr =
std::format("{} {} {}, {} Backend, GIT@" GIT_COMMIT_HASH_SHORT,
rendererInfo.RendererGLInfo.TargetGLVersion.toString(), MG_Config::ProjectName,
MG_Config::CoreVersion.toFormattedString(MG_Config::DefaultVersionStringFormatAttrib),
rendererInfo.BackendName);
backendName);
MGLOG_D("versionStr: %s", versionStr.c_str());
return (const GLubyte*)versionStr.c_str();
}
@@ -2405,6 +2405,47 @@ namespace MobileGL::MG_Impl::GLImpl {
ShaderBinary_State(count, shaders, binaryformat, binary, length);
}
// GL 4.6 core 7.13 (ARB_ES2_compatibility). The shading language here is desktop GLSL, whose
// float and int are 32-bit IEEE-754 / two's complement at every precision qualifier, so every
// precision answers the same: log2 of the range and the mantissa bits. A silent stub answered
// zeros, which an ES-on-GL layer reads as "no highp" and refuses ES 2.0 outright.
void GetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision) {
if (shadertype != GL_VERTEX_SHADER && shadertype != GL_FRAGMENT_SHADER) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"shadertype must be GL_VERTEX_SHADER or GL_FRAGMENT_SHADER."));
return;
}
GLint rangeLow = 0, rangeHigh = 0, bits = 0;
switch (precisiontype) {
case GL_LOW_FLOAT:
case GL_MEDIUM_FLOAT:
case GL_HIGH_FLOAT:
rangeLow = 127;
rangeHigh = 127;
bits = 23;
break;
case GL_LOW_INT:
case GL_MEDIUM_INT:
case GL_HIGH_INT:
rangeLow = 31;
rangeHigh = 30;
bits = 0;
break;
default:
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "precisiontype is not a precision."));
return;
}
if (range) {
range[0] = rangeLow;
range[1] = rangeHigh;
}
if (precision) *precision = bits;
}
void SpecializeShader(GLuint shader, const GLchar* pEntryPoint, GLuint numSpecializationConstants,
const GLuint* pConstantIndex, const GLuint* pConstantValue) {
SpecializeShader_State(shader, pEntryPoint, numSpecializationConstants, pConstantIndex, pConstantValue);
@@ -41,6 +41,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetShaderiv(GLuint shader, GLenum pname, GLint* params);
void GetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog);
void GetShaderSource(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source);
void GetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision);
GLint GetUniformLocation(GLuint program, const GLchar* name);
void GetUniformfv(GLuint program, GLint location, GLfloat* params);
void GetUniformiv(GLuint program, GLint location, GLint* params);