[Fix] (EGL): decline another backend's GBM device and ask X for a visual only on X displays

This commit is contained in:
2026-10-02 19:49:32 -04:00
parent 6c742eba8a
commit 552dd3d39b
4 changed files with 100 additions and 12 deletions
+30 -10
View File
@@ -25,8 +25,12 @@
// file must not name ServerLoop's forwarders directly.
#include <MG_Remote/Client/WireTables.h>
#endif
#include <cstring>
#include <mutex>
#include <sstream>
#if defined(__linux__) && !defined(__ANDROID__)
#include <dlfcn.h>
#endif
#include <type_traits>
#include <unordered_set>
@@ -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<Uint64>(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<BackendNameFn>(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<Uint64>(native_display), EGL_NONE)) {
platform = Wayland::kPlatformWayland;
}
#endif
return state->GetPlatformDisplay(platform, native_display);
}
+13 -1
View File
@@ -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;
+3 -1
View File
@@ -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
+54
View File
@@ -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<FakeGbmDevice*>(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";