[Test] (Gbm): drive mobilegl_gbm through the system loader the way Xwayland's glamor does

This commit is contained in:
2026-10-03 01:51:23 -04:00
parent 198d6feaf3
commit bd4c5b5770
4 changed files with 314 additions and 0 deletions
+2
View File
@@ -105,4 +105,6 @@ endif()
# is only registered there. Nothing under MG_Remote is compiled otherwise.
if (MOBILEGL_BUILD_DISAGGREGATED)
add_subdirectory(Wire)
# The GBM backend glamor uses (Xwayland on MobileGL), through the system's loader.
add_subdirectory(Gbm)
endif()
+25
View File
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.14)
# mobilegl_gbm.so through the system's GBM loader, the calls Xwayland's glamor makes, over a fake of
# libMobileGL's shared-image C ABI. Only where the GBM backend itself is built (gbm headers found).
find_library(MOBILEGL_TEST_GBM_LIBRARY gbm)
if (NOT TARGET mobilegl_gbm OR NOT MOBILEGL_TEST_GBM_LIBRARY)
message(STATUS "MobileGL: GbmGlamorTest not built (no mobilegl_gbm target or no libgbm)")
return()
endif()
add_library(mobilegl_fake_shared_images MODULE FakeSharedImageLibrary.cpp)
set_target_properties(mobilegl_fake_shared_images PROPERTIES PREFIX "" CXX_VISIBILITY_PRESET hidden)
target_include_directories(mobilegl_fake_shared_images PRIVATE ${MGL_ROOT}/MobileGL/MG_Impl)
add_executable(GbmGlamorTest GbmGlamorTest.cpp)
add_dependencies(GbmGlamorTest mobilegl_gbm mobilegl_fake_shared_images)
target_include_directories(GbmGlamorTest PRIVATE ${MOBILEGL_GBM_INCLUDE_DIR})
target_compile_definitions(GbmGlamorTest PRIVATE
MOBILEGL_TEST_GBM_BACKEND_DIR="$<TARGET_FILE_DIR:mobilegl_gbm>"
MOBILEGL_TEST_FAKE_SHARED_IMAGE_LIBRARY="$<TARGET_FILE:mobilegl_fake_shared_images>"
)
target_link_libraries(GbmGlamorTest PRIVATE GTest::gtest_main ${MOBILEGL_TEST_GBM_LIBRARY} ${CMAKE_DL_LIBS})
include(GoogleTest)
gtest_discover_tests(GbmGlamorTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
@@ -0,0 +1,105 @@
// MobileGL - MobileGL/MG_Test/Gbm/FakeSharedImageLibrary.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
// A stand-in for libMobileGL.so's shared-image C ABI (MG_Impl/SharedImageApi.h), loaded by
// mobilegl_gbm.so through MOBILEGL_GBM_LIBRARY in GbmGlamorTest: images are memfds, identified on
// import by inode as the server identifies its dma-bufs. fake_shared_image_* let the test look in.
#include "SharedImageApi.h"
#include <cerrno>
#include <map>
#include <mutex>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define FAKE_EXPORT extern "C" __attribute__((visibility("default")))
namespace {
struct FakeImage {
int fd = -1;
ino_t inode = 0;
uint32_t width = 0, height = 0, fourcc = 0;
int references = 0;
};
std::mutex g_mutex;
std::map<uint64_t, FakeImage> g_images;
uint64_t g_nextId = 1;
int g_imports = 0;
bool Supported(uint32_t fourcc) {
return fourcc == MOBILEGL_SHARED_IMAGE_FOURCC_ABGR8888 || fourcc == MOBILEGL_SHARED_IMAGE_FOURCC_XBGR8888 ||
fourcc == MOBILEGL_SHARED_IMAGE_FOURCC_ARGB8888 || fourcc == MOBILEGL_SHARED_IMAGE_FOURCC_XRGB8888;
}
} // namespace
FAKE_EXPORT uint32_t mobilegl_shared_image_abi_version(void) { return MOBILEGL_SHARED_IMAGE_ABI_VERSION; }
FAKE_EXPORT int mobilegl_server_available(void) { return 1; }
FAKE_EXPORT int mobilegl_shared_image_formats(uint32_t*, uint32_t) { return 4; }
FAKE_EXPORT int mobilegl_shared_image_allocate(uint32_t width, uint32_t height, uint32_t fourcc,
struct mobilegl_shared_image* out) {
if (width == 0 || height == 0 || !Supported(fourcc) || out == nullptr) return -EINVAL;
const int fd = ::memfd_create("fake-shared-image", MFD_CLOEXEC);
if (fd < 0 || ::ftruncate(fd, static_cast<off_t>(width) * height * 4) != 0) return -EIO;
struct stat st {};
::fstat(fd, &st);
const std::lock_guard<std::mutex> lock(g_mutex);
const uint64_t id = g_nextId++;
g_images[id] = FakeImage{fd, st.st_ino, width, height, fourcc, 1};
out->width = width;
out->height = height;
out->fourcc = fourcc;
out->id = id;
out->fd = ::dup(fd);
out->stride = width * 4;
out->offset = 0;
out->modifier = 0x00ffffffffffffffull;
return 0;
}
FAKE_EXPORT int mobilegl_shared_image_import(int fd, uint32_t width, uint32_t height, uint32_t fourcc,
uint64_t* out_id) {
struct stat st {};
if (fd < 0 || ::fstat(fd, &st) != 0 || !Supported(fourcc)) return -EINVAL;
const std::lock_guard<std::mutex> lock(g_mutex);
for (auto& [id, image] : g_images) {
if (image.inode != st.st_ino) continue;
if (image.width != width || image.height != height) return -EIO;
++image.references;
++g_imports;
if (out_id != nullptr) *out_id = id;
return 0;
}
return -EIO; // not one of "the server's" images
}
FAKE_EXPORT int mobilegl_shared_image_release(uint64_t id) {
const std::lock_guard<std::mutex> lock(g_mutex);
auto it = g_images.find(id);
if (it == g_images.end()) return -EINVAL;
if (--it->second.references == 0) {
::close(it->second.fd);
g_images.erase(it);
}
return 0;
}
FAKE_EXPORT int fake_shared_image_live(void) {
const std::lock_guard<std::mutex> lock(g_mutex);
return static_cast<int>(g_images.size());
}
FAKE_EXPORT int fake_shared_image_imports(void) {
const std::lock_guard<std::mutex> lock(g_mutex);
return g_imports;
}
+182
View File
@@ -0,0 +1,182 @@
// MobileGL - MobileGL/MG_Test/Gbm/GbmGlamorTest.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_gbm.so THROUGH THE SYSTEM'S GBM LOADER, the way Xwayland's glamor drives it
// (hw/xwayland/xwayland-glamor-gbm.c, 24.1): gbm_create_device on a character device with
// GBM_BACKEND=mobilegl, window pixmaps from gbm_bo_create_with_modifiers2 with the compositor's
// {INVALID, LINEAR} (else gbm_bo_create), their planes exported for EGL and linux-dmabuf, and a
// DRI3 client's buffer imported back with GBM_BO_IMPORT_FD / _FD_MODIFIER. The shared images come
// from a fake of libMobileGL's C ABI (FakeSharedImageLibrary.cpp).
#include <gbm.h>
#include <gtest/gtest.h>
#include <cstdlib>
#include <cstring>
#include <dlfcn.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#ifndef DRM_FORMAT_MOD_INVALID
#define DRM_FORMAT_MOD_INVALID 0x00ffffffffffffffULL
#endif
#ifndef DRM_FORMAT_MOD_LINEAR
#define DRM_FORMAT_MOD_LINEAR 0ULL
#endif
namespace {
using CountFn = int (*)();
class GbmGlamor : public ::testing::Test {
protected:
void SetUp() override {
::setenv("GBM_BACKEND", "mobilegl", 1);
::setenv("GBM_BACKENDS_PATH", MOBILEGL_TEST_GBM_BACKEND_DIR, 1);
::setenv("MOBILEGL_GBM_LIBRARY", MOBILEGL_TEST_FAKE_SHARED_IMAGE_LIBRARY, 1);
m_fd = ::open("/dev/null", O_RDWR | O_CLOEXEC);
ASSERT_GE(m_fd, 0);
m_gbm = gbm_create_device(m_fd);
ASSERT_NE(m_gbm, nullptr) << "libgbm did not take the mobilegl backend";
void* fake = dlopen(MOBILEGL_TEST_FAKE_SHARED_IMAGE_LIBRARY, RTLD_NOW | RTLD_NOLOAD);
ASSERT_NE(fake, nullptr) << "the backend did not load the shared-image library";
m_live = reinterpret_cast<CountFn>(dlsym(fake, "fake_shared_image_live"));
m_imports = reinterpret_cast<CountFn>(dlsym(fake, "fake_shared_image_imports"));
ASSERT_TRUE(m_live && m_imports);
m_liveAtStart = m_live();
}
void TearDown() override {
if (m_gbm != nullptr) gbm_device_destroy(m_gbm);
if (m_fd >= 0) ::close(m_fd);
}
int m_fd = -1;
gbm_device* m_gbm = nullptr;
CountFn m_live = nullptr;
CountFn m_imports = nullptr;
int m_liveAtStart = 0;
};
} // namespace
TEST_F(GbmGlamor, TheDeviceIsMobileGLsAndTakesTheFormatsGlamorAsksFor) {
// Xwayland names the screen's GLX vendor after the backend (anything but "drm").
EXPECT_STREQ(gbm_device_get_backend_name(m_gbm), "mobilegl");
// gbm_format_for_depth: 24 -> XRGB8888 (ARGB8888 under GLES glamor), 32 -> ARGB8888.
EXPECT_TRUE(gbm_device_is_format_supported(m_gbm, GBM_FORMAT_XRGB8888, GBM_BO_USE_RENDERING));
EXPECT_TRUE(gbm_device_is_format_supported(m_gbm, GBM_FORMAT_ARGB8888, GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT));
EXPECT_FALSE(gbm_device_is_format_supported(m_gbm, GBM_FORMAT_RGB565, GBM_BO_USE_RENDERING));
}
TEST_F(GbmGlamor, AWindowPixmapIsASharedImageWithOneExportablePlane) {
// The compositor's tranche for a MobileGL EGL lists INVALID and LINEAR (implicit modifiers).
const uint64_t modifiers[] = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR};
gbm_bo* bo = gbm_bo_create_with_modifiers2(m_gbm, 640, 480, GBM_FORMAT_XRGB8888, modifiers, 2,
GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT);
ASSERT_NE(bo, nullptr) << std::strerror(errno);
EXPECT_EQ(m_live(), m_liveAtStart + 1);
EXPECT_EQ(gbm_bo_get_width(bo), 640u);
EXPECT_EQ(gbm_bo_get_height(bo), 480u);
EXPECT_EQ(gbm_bo_get_format(bo), GBM_FORMAT_XRGB8888);
EXPECT_EQ(gbm_bo_get_modifier(bo), DRM_FORMAT_MOD_INVALID) << "the layout is the allocator's";
// xwl_glamor_gbm_create_pixmap_for_bo / init_buffer_params_with_modifiers:
ASSERT_EQ(gbm_bo_get_plane_count(bo), 1);
const int fd = gbm_bo_get_fd_for_plane(bo, 0);
ASSERT_GE(fd, 0);
EXPECT_NE(gbm_bo_get_handle_for_plane(bo, 0).u32, 0u);
EXPECT_GE(gbm_bo_get_stride_for_plane(bo, 0), 640u * 4u);
EXPECT_EQ(gbm_bo_get_offset(bo, 0), 0u);
// Exports are duplicates of one dma-buf: the same inode every time.
const int again = gbm_bo_get_fd(bo);
struct stat a {}, b {};
ASSERT_EQ(::fstat(fd, &a), 0);
ASSERT_EQ(::fstat(again, &b), 0);
EXPECT_EQ(a.st_ino, b.st_ino);
::close(fd);
::close(again);
gbm_bo_destroy(bo);
EXPECT_EQ(m_live(), m_liveAtStart) << "destroying the bo released the image";
}
TEST_F(GbmGlamor, AModifierListWithoutInvalidIsRefusedAndGlamorsFallbackWorks) {
const uint64_t linearOnly[] = {DRM_FORMAT_MOD_LINEAR};
EXPECT_EQ(gbm_bo_create_with_modifiers2(m_gbm, 64, 64, GBM_FORMAT_XRGB8888, linearOnly, 1, GBM_BO_USE_RENDERING),
nullptr);
// glamor's implicit-modifier fallback.
gbm_bo* bo = gbm_bo_create(m_gbm, 64, 64, GBM_FORMAT_XRGB8888, GBM_BO_USE_RENDERING);
ASSERT_NE(bo, nullptr);
EXPECT_EQ(gbm_bo_get_modifier(bo), DRM_FORMAT_MOD_INVALID);
gbm_bo_destroy(bo);
// ...which with a LINEAR-only tranche asks for a linear layout, and that no shared image promises.
EXPECT_EQ(gbm_bo_create(m_gbm, 64, 64, GBM_FORMAT_XRGB8888, GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR), nullptr);
}
TEST_F(GbmGlamor, ADri3ClientsBufferImportsBackAsTheSameImage) {
// The GLX client's side: an image it allocated (here through the same ABI) handed over as a
// dma-buf with PixmapFromBuffer(s), modifier INVALID.
gbm_bo* clientBo = gbm_bo_create(m_gbm, 320, 200, GBM_FORMAT_XRGB8888, GBM_BO_USE_RENDERING);
ASSERT_NE(clientBo, nullptr);
const int fd = gbm_bo_get_fd(clientBo);
const int importsBefore = m_imports();
// glamor_pixmap_from_fds, modifier INVALID: GBM_BO_IMPORT_FD with the depth's format.
gbm_import_fd_data plain{};
plain.fd = fd;
plain.width = 320;
plain.height = 200;
plain.stride = gbm_bo_get_stride(clientBo);
plain.format = GBM_FORMAT_XRGB8888;
gbm_bo* imported = gbm_bo_import(m_gbm, GBM_BO_IMPORT_FD, &plain, GBM_BO_USE_RENDERING);
ASSERT_NE(imported, nullptr);
EXPECT_EQ(m_imports(), importsBefore + 1);
// ...and re-exported for EGL_LINUX_DMA_BUF_EXT: still that image.
const int reexport = gbm_bo_get_fd_for_plane(imported, 0);
struct stat a {}, b {};
::fstat(fd, &a);
::fstat(reexport, &b);
EXPECT_EQ(a.st_ino, b.st_ino);
::close(reexport);
// A client that sent an explicit modifier list: GBM_BO_IMPORT_FD_MODIFIER.
gbm_import_fd_modifier_data withModifier{};
withModifier.width = 320;
withModifier.height = 200;
withModifier.format = GBM_FORMAT_XRGB8888;
withModifier.num_fds = 1;
withModifier.fds[0] = fd;
withModifier.strides[0] = static_cast<int>(gbm_bo_get_stride(clientBo));
withModifier.modifier = DRM_FORMAT_MOD_INVALID;
gbm_bo* importedModifier = gbm_bo_import(m_gbm, GBM_BO_IMPORT_FD_MODIFIER, &withModifier, GBM_BO_USE_RENDERING);
ASSERT_NE(importedModifier, nullptr);
// A size that disagrees with the image is refused.
plain.width = 321;
EXPECT_EQ(gbm_bo_import(m_gbm, GBM_BO_IMPORT_FD, &plain, GBM_BO_USE_RENDERING), nullptr);
gbm_bo_destroy(importedModifier);
gbm_bo_destroy(imported);
gbm_bo_destroy(clientBo);
::close(fd);
EXPECT_EQ(m_live(), m_liveAtStart) << "every reference was dropped";
}
TEST_F(GbmGlamor, AForeignDmaBufIsRefused) {
// A buffer the MobileGL server never allocated (another driver's client) cannot become a pixmap.
const int foreign = ::memfd_create("foreign", MFD_CLOEXEC);
ASSERT_GE(foreign, 0);
ASSERT_EQ(::ftruncate(foreign, 64 * 64 * 4), 0);
gbm_import_fd_data data{};
data.fd = foreign;
data.width = 64;
data.height = 64;
data.stride = 256;
data.format = GBM_FORMAT_XRGB8888;
EXPECT_EQ(gbm_bo_import(m_gbm, GBM_BO_IMPORT_FD, &data, GBM_BO_USE_RENDERING), nullptr);
::close(foreign);
}