From 552dd3d39ba006cb0b02742f41e92347883fbefc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 2 Oct 2026 19:49:32 -0400 Subject: [PATCH] [Fix] (EGL): decline another backend's GBM device and ask X for a visual only on X displays --- MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp | 40 ++++++++++++----- MobileGL/MG_State/EGLState/Core.cpp | 14 +++++- MobileGL/MG_Test/Wire/CMakeLists.txt | 4 +- MobileGL/MG_Test/Wire/ServerProbeTest.cpp | 54 +++++++++++++++++++++++ 4 files changed, 100 insertions(+), 12 deletions(-) diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 5b5336c5..81fb993c 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -25,8 +25,12 @@ // file must not name ServerLoop's forwarders directly. #include #endif +#include #include #include +#if defined(__linux__) && !defined(__ANDROID__) +#include +#endif #include #include @@ -1193,6 +1197,32 @@ namespace MobileGL::MG_Impl::EGLImpl { (void)attrib_list; // As GetDisplay. + constexpr EGLenum kPlatformGbm = 0x31D7; +#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. + if (platform == kPlatformGbm && native_display != nullptr && + Wayland::IsWaylandDisplay(reinterpret_cast(native_display), EGL_NONE)) { + platform = Wayland::kPlatformWayland; + } +#endif +#if defined(__linux__) && !defined(__ANDROID__) + // A GBM device some other backend created (the system's own driver's) is not one this + // implementation can draw for: its buffers come from that driver, never from the server. + // Declined before anything is brought up, so a loader with more than one EGL vendor hands + // the device to the one it belongs to. Only a device whose backend names itself is asked + // about; one from a loader too old to say is served as before. + if (platform == kPlatformGbm && native_display != nullptr) { + using BackendNameFn = const char* (*)(void*); + static const auto backendName = + reinterpret_cast(dlsym(RTLD_DEFAULT, "gbm_device_get_backend_name")); + const char* name = backendName != nullptr ? backendName(native_display) : nullptr; + if (name != nullptr && std::strcmp(name, "mobilegl") != 0) { + return EGL_NO_DISPLAY; + } + } +#endif if (!MobileGL::ImplementationAvailable()) { return EGL_NO_DISPLAY; } @@ -1200,16 +1230,6 @@ 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(native_display), EGL_NONE)) { - platform = Wayland::kPlatformWayland; - } -#endif return state->GetPlatformDisplay(platform, native_display); } diff --git a/MobileGL/MG_State/EGLState/Core.cpp b/MobileGL/MG_State/EGLState/Core.cpp index d445f0ea..c4400f81 100644 --- a/MobileGL/MG_State/EGLState/Core.cpp +++ b/MobileGL/MG_State/EGLState/Core.cpp @@ -152,6 +152,18 @@ namespace MobileGL { displayObject.Configs.push_back(CreateDefaultConfig(display, 1, 0)); displayObject.Configs.push_back(CreateDefaultConfig(display, 2, 8)); +#if !defined(ANDROID) && !defined(__ANDROID__) + // The X visual only means something on an X display: the default display (which is + // DISPLAY's) or an explicit X11 one. Asking the X server on any other platform + // costs a connection a Wayland/GBM/surfaceless client never needed - and an X + // server that is itself creating this display (a rootless X server's GL + // acceleration) would be waiting on its own connection. + constexpr EGLenum kPlatformX11 = 0x31D5; + if (platform == EGL_NONE || platform == kPlatformX11) { + const EGLint visualId = QueryDefaultX11VisualId(); + for (const auto config : displayObject.Configs) m_configs[config].NativeVisualId = visualId; + } +#endif m_displays[display] = displayObject; m_displayLookup[key] = display; @@ -174,7 +186,7 @@ namespace MobileGL { .RenderableType = EGL_OPENGL_BIT | EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT, .MinSwapInterval = 0, .MaxSwapInterval = 4, - .NativeVisualId = QueryDefaultX11VisualId(), + .NativeVisualId = 0, // the display's X visual, set by GetOrCreateDisplay }; #if defined(ANDROID) || defined(__ANDROID__) cfg.NativeVisualId = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; diff --git a/MobileGL/MG_Test/Wire/CMakeLists.txt b/MobileGL/MG_Test/Wire/CMakeLists.txt index 222c39fa..ce8d6c6f 100644 --- a/MobileGL/MG_Test/Wire/CMakeLists.txt +++ b/MobileGL/MG_Test/Wire/CMakeLists.txt @@ -121,8 +121,10 @@ foreach (test IN LISTS MOBILEGL_WIRE_TESTS) endif () if (test STREQUAL "ServerProbeTest") - # It calls the glvnd vendor entry point the way libEGL does. + # It calls the glvnd vendor entry point the way libEGL does, and stands in for the GBM + # loader's backend-name query with a symbol of its own, which must be visible to dlsym. target_include_directories(${test} PRIVATE ${MGL_ROOT}/3rdparty/glvnd/include) + set_target_properties(${test} PROPERTIES ENABLE_EXPORTS ON) endif () if (test STREQUAL "ServerSpawnTest" OR test STREQUAL "PeerLatchTest" OR test STREQUAL "InProcessServerTest" OR diff --git a/MobileGL/MG_Test/Wire/ServerProbeTest.cpp b/MobileGL/MG_Test/Wire/ServerProbeTest.cpp index 259c71da..4ddc19a9 100644 --- a/MobileGL/MG_Test/Wire/ServerProbeTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerProbeTest.cpp @@ -37,6 +37,15 @@ extern "C" int __glx_Main(uint32_t version, const void* exports, void* vendor, v using namespace MobileGL; +// A stand-in for the GBM loader's query, found by the implementation through the process's global +// symbols (the target exports them): a "device" here is just the backend name it reports. +struct FakeGbmDevice { + const char* backend; +}; +extern "C" __attribute__((visibility("default"))) const char* gbm_device_get_backend_name(void* device) { + return static_cast(device)->backend; +} + namespace { namespace EGL = MobileGL::MG_Impl::EGLImpl; @@ -248,6 +257,51 @@ namespace { std::filesystem::remove(Debug::RoleLogPath(logBase.c_str(), Debug::LogRole::Client), ec); } + // A GBM device another backend created is declined before MobileGL brings anything up (so a + // loader with a second vendor gives it to its owner); one of MobileGL's own is served. + TEST_F(ServerProbeTest, AForeignGbmDeviceIsDeclinedAndOneOfOursIsServed) { + P12::ServerProcess server; + if (!P12::LaunchSupervisor("probe-gbm", &server)) GTEST_SKIP() << "no `--serve` supervisor on this host"; + const std::string logBase = "/tmp/" + UniqueName("gbm") + ".log"; + int fds[2] = {-1, -1}; + ASSERT_EQ(::pipe(fds), 0); + std::fflush(nullptr); + const pid_t pid = ::fork(); + if (pid == 0) { + ::close(fds[0]); + ::setenv("MOBILEGL_LOG_FILE_PATH", logBase.c_str(), 1); + ::setenv("MOBILEGL_TRANSPORT", "spawn", 1); + ::setenv("MOBILEGL_IPC_CONTROL", server.endpoint.c_str(), 1); + ::setenv("MOBILEGL_BACKEND_TYPE", "DirectGLES", 1); + constexpr EGLenum kPlatformGbm = 0x31D7; + FakeGbmDevice foreign{"drm"}; + FakeGbmDevice ours{"mobilegl"}; + Int32 result[3] = {-1, -1, -1}; + result[0] = EGL::GetPlatformDisplay(kPlatformGbm, &foreign, nullptr) != EGL_NO_DISPLAY; + result[1] = MobileGL::MG_Remote::Client::ClientSession::Active() != nullptr; // nothing brought up for it + result[2] = EGL::GetPlatformDisplay(kPlatformGbm, &ours, nullptr) != EGL_NO_DISPLAY; + const ssize_t wrote = ::write(fds[1], result, sizeof(result)); + (void)wrote; + std::fflush(nullptr); + ::_exit(0); + } + ::close(fds[1]); + Int32 result[3] = {-1, -1, -1}; + pollfd pfd{fds[0], POLLIN, 0}; + if (::poll(&pfd, 1, 60000) > 0) { + const ssize_t got = ::read(fds[0], result, sizeof(result)); + (void)got; + } + ::close(fds[0]); + ::kill(pid, SIGKILL); + ::waitpid(pid, nullptr, 0); + EXPECT_EQ(result[0], 0) << "a device from another GBM backend must be declined"; + EXPECT_EQ(result[1], 0) << "and declined before the session was brought up"; + EXPECT_EQ(result[2], 1) << "a device of MobileGL's own GBM backend is served"; + std::error_code ec; + std::filesystem::remove(Debug::RoleLogPath(logBase.c_str(), Debug::LogRole::Client), ec); + } + TEST_F(ServerProbeTest, WithAServerListeningADisplayIsHandedOut) { P12::ServerProcess server; if (!P12::LaunchSupervisor("probe", &server)) GTEST_SKIP() << "no `--serve` supervisor on this host";