[Feature] (EGL): Wayland window surfaces, presented through wl_shm

A Wayland client's window is its own wl_surface, and what the compositor shows is the wl_buffer
attached to it - but the pixels of a MobileGL frame are drawn where the backend is, on a split
build in another process on another OS.  So far a wl_egl_window was taken for an X11 window and
its client-side address sent to the Android server (EGL_BAD_NATIVE_WINDOW at best), and under
glvnd a Wayland display never reached this vendor at all.

A window on a Wayland display - asked for as EGL_PLATFORM_WAYLAND, or a wl_display recognised by
its first word being wl_display_interface - is now a window surface in the EGL state at the
wl_egl_window's size with a pbuffer of that size behind it in the backend, whatever
MOBILEGL_IPC_SURFACE says.  At every swap the frame is read back out of the default framebuffer
through this library's own glReadPixels (the application's read framebuffer, pack buffer and pack
state put back as they were), its rows flipped and swizzled into a wl_shm buffer, and attached,
damaged and committed on the application's wl_surface.  Three buffers rotate on their release
events, dispatched on an event queue of the surface's own.  libwayland-client is loaded at run
time, so neither the build nor the library depends on it.  EGL_KHR_platform_wayland and
EGL_EXT_platform_wayland are announced on Linux.

Measured: glmark2-wayland runs its build scene at 13 FPS (400x300) and the window shows the frame
under kwin.  A window resize is not followed yet; the surface keeps its creation size.
This commit is contained in:
BZLZHH
2026-10-01 10:59:07 -04:00
parent 353daa265e
commit 7fd7f71d6c
7 changed files with 588 additions and 1 deletions
+1
View File
@@ -406,6 +406,7 @@ set(SOURCE_FILES
MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp
MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp
MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp
# @INSERTION_POINT:SOURCE_FILE_GLIMPL@ # # @INSERTION_POINT:SOURCE_FILE_GLIMPL@ #
MobileGL/MG_Impl/GLImpl/Sampler/Validators.cpp MobileGL/MG_Impl/GLImpl/Sampler/Validators.cpp
+96
View File
@@ -8,6 +8,7 @@
#include "EGLImpl.h" #include "EGLImpl.h"
#include "EGLPlatformExtensions.h" #include "EGLPlatformExtensions.h"
#include "WaylandWindow.h"
#include "../GetProcAddress.h" #include "../GetProcAddress.h"
#include <Init.h> #include <Init.h>
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
@@ -147,6 +148,73 @@ namespace MobileGL::MG_Impl::EGLImpl {
return reinterpret_cast<void*>(static_cast<SizeT>(nativeHandle)); return reinterpret_cast<void*>(static_cast<SizeT>(nativeHandle));
} }
} }
#if MOBILEGL_WAYLAND_WINDOWS
// Every Wayland window surface's presentation, by surface. Guarded by EGLOperationMutex,
// which every entry point that reaches it holds.
UnorderedMap<EGLSurface, UniquePtr<Wayland::WindowSurface>>& WaylandSurfaces() {
static auto* surfaces = new UnorderedMap<EGLSurface, UniquePtr<Wayland::WindowSurface>>();
return *surfaces;
}
// A WINDOW ON A WAYLAND DISPLAY. Whatever MOBILEGL_IPC_SURFACE says, the window is this
// application's own wl_surface, so neither the server's window nor the display host's frame
// is what it shows: the backend draws into a pbuffer of the window's size, and each swap
// 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.
template <typename State>
Bool CreateWaylandWindowSurface(State* state, EGLDisplay dpy, EGLConfig config, void* window,
Bool platformWindow, EGLSurface* out) {
Uint64 nativeDisplay = 0;
EGLenum platform = EGL_NONE;
if (!state->GetDisplayNative(dpy, &nativeDisplay, &platform) ||
!Wayland::IsWaylandDisplay(nativeDisplay, platform)) {
return false;
}
*out = EGL_NO_SURFACE;
if (!state->IsDisplayInitialized(dpy)) {
state->SetError(EGL_NOT_INITIALIZED);
return true;
}
if (!state->ValidateConfigOnDisplay(dpy, config)) {
state->SetError(EGL_BAD_CONFIG);
return true;
}
EGLint width = 0;
EGLint height = 0;
if (!Wayland::WindowSize(window, &width, &height)) {
state->SetError(EGL_BAD_NATIVE_WINDOW);
return true;
}
EGLint alpha = 0;
(void)state->GetConfigAttrib(dpy, config, EGL_ALPHA_SIZE, &alpha);
auto presentation =
Wayland::WindowSurface::Create(reinterpret_cast<void*>(static_cast<uintptr_t>(nativeDisplay)), window,
width, height, alpha > 0);
if (!presentation) {
state->SetError(EGL_BAD_NATIVE_WINDOW);
return true;
}
const EGLSurface surface =
platformWindow ? state->CreatePlatformWindowSurface(dpy, config, window, nullptr)
: state->CreateWindowSurface(
dpy, config, (NativeWindowType)(reinterpret_cast<uintptr_t>(window)), nullptr);
if (surface == EGL_NO_SURFACE) return true;
(void)state->ResizeSurface(dpy, surface, width, height);
auto* backendObject = GetBackendObject(state);
if (!backendObject || !backendObject->CreateEGLPbufferSurface(surface, width, height)) {
MGLOG_E("eglCreateWindowSurface: the backend refused the %dx%d drawable behind a Wayland window",
width, height);
state->DestroySurface(dpy, surface);
state->SetError(EGL_BAD_ALLOC);
return true;
}
WaylandSurfaces()[surface] = std::move(presentation);
*out = surface;
return true;
}
#endif
} // namespace } // namespace
EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window, EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window,
@@ -155,6 +223,16 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state) { if (!state) {
return EGL_NO_SURFACE; return EGL_NO_SURFACE;
} }
#if MOBILEGL_WAYLAND_WINDOWS
{
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
EGLSurface waylandSurface = EGL_NO_SURFACE;
if (CreateWaylandWindowSurface(state, dpy, config, ToVoidHandle(window), /*platformWindow=*/false,
&waylandSurface)) {
return waylandSurface;
}
}
#endif
if (!state->IsDisplayInitialized(dpy)) { if (!state->IsDisplayInitialized(dpy)) {
state->SetError(EGL_NOT_INITIALIZED); state->SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE; return EGL_NO_SURFACE;
@@ -264,6 +342,11 @@ namespace MobileGL::MG_Impl::EGLImpl {
MGLOG_E_ONCE("activeBackendObject not initialized!"); MGLOG_E_ONCE("activeBackendObject not initialized!");
return EGL_FALSE; return EGL_FALSE;
} }
#if MOBILEGL_WAYLAND_WINDOWS
// A Wayland window shows what is attached to it: the frame is read back and attached
// BEFORE the swap, while it is still the drawable's content.
if (auto it = WaylandSurfaces().find(draw); it != WaylandSurfaces().end()) (void)it->second->Present();
#endif
if (!backendObject->SwapEGLBuffers(dpy, draw)) { if (!backendObject->SwapEGLBuffers(dpy, draw)) {
MGLOG_E_ONCE("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, draw); MGLOG_E_ONCE("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, draw);
state->SetError(EGL_BAD_SURFACE); state->SetError(EGL_BAD_SURFACE);
@@ -502,6 +585,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state->DestroySurface(dpy, surface)) { if (!state->DestroySurface(dpy, surface)) {
return EGL_FALSE; return EGL_FALSE;
} }
#if MOBILEGL_WAYLAND_WINDOWS
WaylandSurfaces().erase(surface);
#endif
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) { if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
backendObject->ReleaseEGLSurface(surface); backendObject->ReleaseEGLSurface(surface);
} }
@@ -881,6 +967,16 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state) { if (!state) {
return EGL_NO_SURFACE; return EGL_NO_SURFACE;
} }
#if MOBILEGL_WAYLAND_WINDOWS
{
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
EGLSurface waylandSurface = EGL_NO_SURFACE;
if (CreateWaylandWindowSurface(state, dpy, config, native_window, /*platformWindow=*/true,
&waylandSurface)) {
return waylandSurface;
}
}
#endif
#if MOBILEGL_BUILD_DISAGGREGATED #if MOBILEGL_BUILD_DISAGGREGATED
// P12 (on-screen server window), D1: as CreateWindowSurface - with MOBILEGL_IPC_SURFACE=server // P12 (on-screen server window), D1: as CreateWindowSurface - with MOBILEGL_IPC_SURFACE=server
// the window is the server's, and a headless client's NULL is accepted. // the window is the server's, and a headless client's NULL is accepted.
@@ -38,6 +38,13 @@ namespace MobileGL::MG_Impl::EGLImpl {
// where the extension IS present) called it: measured as signal 11, fault_addr=(nil), pc=0x0, // where the extension IS present) called it: measured as signal 11, fault_addr=(nil), pc=0x0,
// from KWin::EglDisplay::determineRenderNode() through the GOT slot whose relocation is // from KWin::EglDisplay::determineRenderNode() through the GOT slot whose relocation is
// R_AARCH64_GLOB_DAT epoxy_eglQueryDisplayAttribEXT. // R_AARCH64_GLOB_DAT epoxy_eglQueryDisplayAttribEXT.
// Wayland where a Wayland client can exist (WaylandWindow.h: window surfaces presented through
// wl_shm). Without it glvnd never hands this vendor a Wayland display at all.
#if defined(__linux__) && !defined(__ANDROID__)
#define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS " EGL_KHR_platform_wayland EGL_EXT_platform_wayland"
#else
#define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS ""
#endif
inline constexpr const char* kPlatformExtensionString = inline constexpr const char* kPlatformExtensionString =
"EGL_EXT_client_extensions " "EGL_EXT_client_extensions "
"EGL_EXT_platform_base " "EGL_EXT_platform_base "
@@ -46,7 +53,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
"EGL_EXT_platform_gbm " "EGL_EXT_platform_gbm "
"EGL_MESA_platform_gbm " "EGL_MESA_platform_gbm "
"EGL_EXT_platform_device " "EGL_EXT_platform_device "
"EGL_MESA_platform_surfaceless"; "EGL_MESA_platform_surfaceless" MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS;
// What a display answers for EGL_EXTENSIONS: the extensions this implementation provides that // What a display answers for EGL_EXTENSIONS: the extensions this implementation provides that
// are not platform ones, then the platform list above. // are not platform ones, then the platform list above.
+398
View File
@@ -0,0 +1,398 @@
// MobileGL - MobileGL/MG_Impl/EGLImpl/WaylandWindow.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 "WaylandWindow.h"
#if MOBILEGL_WAYLAND_WINDOWS
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
#include <MG_Util/Debug/Log.h>
#include <cerrno>
#include <cstdarg>
#include <cstdint>
#include <cstring>
#include <dlfcn.h>
#include <fcntl.h>
#include <mutex>
#include <sys/mman.h>
#include <unistd.h>
namespace MobileGL::MG_Impl::EGLImpl::Wayland {
namespace {
// libwayland-client's types are opaque here: everything goes through wl_proxy_* and the
// exported interface descriptions, and nothing reads a field of either.
struct wl_proxy;
struct wl_interface;
struct wl_event_queue;
// wayland-egl-backend.h, WL_EGL_WINDOW_VERSION 3: the struct libwayland-egl allocates for
// wl_egl_window_create and the driver reads. The ABI is versioned by its first word.
struct EglWindow {
const intptr_t version;
int width;
int height;
int dx;
int dy;
int attached_width;
int attached_height;
void* driver_private;
void (*resize_callback)(EglWindow*, void*);
void (*destroy_window_callback)(void*);
wl_proxy* surface;
};
constexpr intptr_t kEglWindowVersion = 3;
// Request opcodes (wayland.xml, the order the requests are declared in).
constexpr uint32_t kDisplayGetRegistry = 1;
constexpr uint32_t kRegistryBind = 0;
constexpr uint32_t kShmCreatePool = 0;
constexpr uint32_t kShmPoolCreateBuffer = 0;
constexpr uint32_t kShmPoolDestroy = 1;
constexpr uint32_t kBufferDestroy = 0;
constexpr uint32_t kSurfaceAttach = 1;
constexpr uint32_t kSurfaceDamage = 2;
constexpr uint32_t kSurfaceCommit = 6;
constexpr uint32_t kSurfaceDamageBuffer = 9; // since wl_surface version 4
constexpr uint32_t kMarshalFlagDestroy = 1u << 0;
// wl_shm formats. The two every compositor must support; byte order in memory is
// B, G, R, A for both (little-endian 0xAARRGGBB).
constexpr uint32_t kShmFormatArgb8888 = 0;
constexpr uint32_t kShmFormatXrgb8888 = 1;
struct Api {
Bool loaded = false;
const wl_interface* displayInterface = nullptr;
const wl_interface* registryInterface = nullptr;
const wl_interface* shmInterface = nullptr;
const wl_interface* shmPoolInterface = nullptr;
const wl_interface* bufferInterface = nullptr;
wl_proxy* (*marshalFlags)(wl_proxy*, uint32_t, const wl_interface*, uint32_t, uint32_t, ...) = nullptr;
int (*addListener)(wl_proxy*, void (**)(void), void*) = nullptr;
void (*destroy)(wl_proxy*) = nullptr;
uint32_t (*getVersion)(wl_proxy*) = nullptr;
void* (*createWrapper)(void*) = nullptr;
void (*wrapperDestroy)(void*) = nullptr;
void (*setQueue)(wl_proxy*, wl_event_queue*) = nullptr;
wl_event_queue* (*createQueue)(void*) = nullptr;
void (*queueDestroy)(wl_event_queue*) = nullptr;
int (*roundtripQueue)(void*, wl_event_queue*) = nullptr;
int (*dispatchQueue)(void*, wl_event_queue*) = nullptr;
int (*dispatchQueuePending)(void*, wl_event_queue*) = nullptr;
int (*flush)(void*) = nullptr;
};
// Loaded once. RTLD_NOLOAD first: a Wayland client has the library already, and the copy
// it is using is the one whose wl_display this must talk to.
const Api& WaylandApi() {
static Api api;
static std::once_flag once;
std::call_once(once, [] {
void* lib = dlopen("libwayland-client.so.0", RTLD_NOW | RTLD_NOLOAD);
if (lib == nullptr) lib = dlopen("libwayland-client.so.0", RTLD_NOW);
if (lib == nullptr) return;
const auto sym = [lib](const char* name) { return dlsym(lib, name); };
api.displayInterface = static_cast<const wl_interface*>(sym("wl_display_interface"));
api.registryInterface = static_cast<const wl_interface*>(sym("wl_registry_interface"));
api.shmInterface = static_cast<const wl_interface*>(sym("wl_shm_interface"));
api.shmPoolInterface = static_cast<const wl_interface*>(sym("wl_shm_pool_interface"));
api.bufferInterface = static_cast<const wl_interface*>(sym("wl_buffer_interface"));
api.marshalFlags = reinterpret_cast<decltype(api.marshalFlags)>(sym("wl_proxy_marshal_flags"));
api.addListener = reinterpret_cast<decltype(api.addListener)>(sym("wl_proxy_add_listener"));
api.destroy = reinterpret_cast<decltype(api.destroy)>(sym("wl_proxy_destroy"));
api.getVersion = reinterpret_cast<decltype(api.getVersion)>(sym("wl_proxy_get_version"));
api.createWrapper = reinterpret_cast<decltype(api.createWrapper)>(sym("wl_proxy_create_wrapper"));
api.wrapperDestroy = reinterpret_cast<decltype(api.wrapperDestroy)>(sym("wl_proxy_wrapper_destroy"));
api.setQueue = reinterpret_cast<decltype(api.setQueue)>(sym("wl_proxy_set_queue"));
api.createQueue = reinterpret_cast<decltype(api.createQueue)>(sym("wl_display_create_queue"));
api.queueDestroy = reinterpret_cast<decltype(api.queueDestroy)>(sym("wl_event_queue_destroy"));
api.roundtripQueue = reinterpret_cast<decltype(api.roundtripQueue)>(sym("wl_display_roundtrip_queue"));
api.dispatchQueue = reinterpret_cast<decltype(api.dispatchQueue)>(sym("wl_display_dispatch_queue"));
api.dispatchQueuePending =
reinterpret_cast<decltype(api.dispatchQueuePending)>(sym("wl_display_dispatch_queue_pending"));
api.flush = reinterpret_cast<decltype(api.flush)>(sym("wl_display_flush"));
api.loaded = api.displayInterface && api.registryInterface && api.shmInterface &&
api.shmPoolInterface && api.bufferInterface && api.marshalFlags && api.addListener && api.destroy &&
api.getVersion && api.createWrapper && api.wrapperDestroy && api.setQueue &&
api.createQueue && api.queueDestroy && api.roundtripQueue && api.dispatchQueue &&
api.dispatchQueuePending && api.flush;
if (!api.loaded) MGLOG_E("Wayland: libwayland-client.so.0 lacks an entry point this library uses");
});
return api;
}
struct ShmBuffer {
wl_proxy* buffer = nullptr;
void* map = nullptr;
SizeT size = 0;
EGLint width = 0;
EGLint height = 0;
EGLint stride = 0;
Bool busy = false;
};
// The compositor is done with a buffer: it may be drawn into again.
void OnBufferRelease(void* data, wl_proxy*) { static_cast<ShmBuffer*>(data)->busy = false; }
void (*const kBufferListener[])(void) = {reinterpret_cast<void (*)(void)>(&OnBufferRelease)};
} // namespace
struct WindowSurface::Impl {
void* display = nullptr;
EglWindow* window = nullptr;
wl_event_queue* queue = nullptr;
void* displayWrapper = nullptr;
wl_proxy* registry = nullptr;
wl_proxy* shm = nullptr;
uint32_t format = kShmFormatXrgb8888;
EGLint width = 0;
EGLint height = 0;
// Three: one on the glass, one the compositor may still be reading, one to draw into.
ShmBuffer buffers[3];
Vector<Uint8> scratch;
~Impl() {
const Api& api = WaylandApi();
for (auto& buffer : buffers) Release(buffer);
// Neither wl_shm (version 1) nor wl_registry has a destroy request: the proxies go
// on this side only.
if (shm) api.destroy(shm);
if (registry) api.destroy(registry);
if (displayWrapper) api.wrapperDestroy(displayWrapper);
if (queue) api.queueDestroy(queue);
if (window && window->driver_private == this) {
window->driver_private = nullptr;
window->destroy_window_callback = nullptr;
}
}
void Release(ShmBuffer& buffer) {
const Api& api = WaylandApi();
if (buffer.buffer) api.marshalFlags(buffer.buffer, kBufferDestroy, nullptr, api.getVersion(buffer.buffer),
kMarshalFlagDestroy);
if (buffer.map) munmap(buffer.map, buffer.size);
buffer = ShmBuffer{};
}
Bool Allocate(ShmBuffer& buffer) {
const Api& api = WaylandApi();
Release(buffer);
const EGLint stride = width * 4;
const SizeT size = static_cast<SizeT>(stride) * static_cast<SizeT>(height);
const int fd = memfd_create("mobilegl-wayland", MFD_CLOEXEC);
if (fd < 0) {
MGLOG_E("Wayland: memfd_create for a %dx%d buffer failed: %s", width, height, std::strerror(errno));
return false;
}
if (ftruncate(fd, static_cast<off_t>(size)) != 0) {
MGLOG_E("Wayland: sizing a %dx%d buffer failed: %s", width, height, std::strerror(errno));
close(fd);
return false;
}
void* map = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
MGLOG_E("Wayland: mapping a %dx%d buffer failed: %s", width, height, std::strerror(errno));
close(fd);
return false;
}
// One pool per buffer, destroyed at once: the buffer keeps the memory alive.
wl_proxy* pool = api.marshalFlags(shm, kShmCreatePool, api.shmPoolInterface, api.getVersion(shm), 0, nullptr,
fd, static_cast<int32_t>(size));
close(fd);
if (pool == nullptr) {
munmap(map, size);
return false;
}
wl_proxy* wlBuffer = api.marshalFlags(pool, kShmPoolCreateBuffer, api.bufferInterface, api.getVersion(pool),
0, nullptr, 0, width, height, stride, format);
api.marshalFlags(pool, kShmPoolDestroy, nullptr, api.getVersion(pool), kMarshalFlagDestroy);
if (wlBuffer == nullptr) {
munmap(map, size);
return false;
}
buffer.buffer = wlBuffer;
buffer.map = map;
buffer.size = size;
buffer.width = width;
buffer.height = height;
buffer.stride = stride;
buffer.busy = false;
api.addListener(wlBuffer, const_cast<void (**)(void)>(kBufferListener), &buffer);
return true;
}
// A buffer the compositor is not reading: the events already received first, then (all
// three still held) a blocking wait for the next release.
ShmBuffer* NextFree() {
const Api& api = WaylandApi();
for (;;) {
if (api.dispatchQueuePending(display, queue) < 0) return nullptr;
for (auto& buffer : buffers) {
if (buffer.buffer == nullptr || buffer.busy) continue;
if (buffer.width != width || buffer.height != height) {
if (!Allocate(buffer)) return nullptr;
}
return &buffer;
}
for (auto& buffer : buffers) {
if (buffer.buffer == nullptr) return Allocate(buffer) ? &buffer : nullptr;
}
if (api.dispatchQueue(display, queue) < 0) return nullptr;
}
}
};
namespace {
void OnRegistryGlobal(void* data, wl_proxy* registry, uint32_t name, const char* interface, uint32_t) {
auto* impl = static_cast<WindowSurface::Impl*>(data);
if (impl->shm != nullptr || std::strcmp(interface, "wl_shm") != 0) return;
const Api& api = WaylandApi();
impl->shm = api.marshalFlags(registry, kRegistryBind, api.shmInterface, 1, 0, name, "wl_shm", 1u, nullptr);
}
void OnRegistryGlobalRemove(void*, wl_proxy*, uint32_t) {}
void (*const kRegistryListener[])(void) = {reinterpret_cast<void (*)(void)>(&OnRegistryGlobal),
reinterpret_cast<void (*)(void)>(&OnRegistryGlobalRemove)};
// The application destroyed its wl_egl_window first: nothing may be read from it after this.
void OnWindowDestroyed(void* data) { static_cast<WindowSurface::Impl*>(data)->window = nullptr; }
} // 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;
}
Bool IsWaylandWindow(const void* window) {
if (window == nullptr) return false;
const auto* eglWindow = static_cast<const EglWindow*>(window);
return eglWindow->version >= kEglWindowVersion && eglWindow->surface != nullptr;
}
Bool WindowSize(const void* window, EGLint* width, EGLint* height) {
if (!IsWaylandWindow(window)) return false;
const auto* eglWindow = static_cast<const EglWindow*>(window);
*width = eglWindow->width;
*height = eglWindow->height;
return eglWindow->width > 0 && eglWindow->height > 0;
}
WindowSurface::WindowSurface(UniquePtr<Impl> impl) : m_impl(std::move(impl)) {}
WindowSurface::~WindowSurface() = default;
UniquePtr<WindowSurface> WindowSurface::Create(void* wlDisplay, void* wlEglWindow, EGLint width, EGLint height,
Bool hasAlpha) {
const Api& api = WaylandApi();
if (!api.loaded) {
MGLOG_E("Wayland: a window surface needs libwayland-client.so.0, and it could not be used");
return nullptr;
}
if (wlDisplay == nullptr || !IsWaylandWindow(wlEglWindow) || width <= 0 || height <= 0) return nullptr;
auto impl = MakeUnique<Impl>();
impl->display = wlDisplay;
impl->window = static_cast<EglWindow*>(wlEglWindow);
impl->width = width;
impl->height = height;
impl->format = hasAlpha ? kShmFormatArgb8888 : kShmFormatXrgb8888;
// A queue of this surface's own, so the application's dispatching never sees these events
// and this never dispatches the application's.
impl->queue = api.createQueue(wlDisplay);
impl->displayWrapper = api.createWrapper(wlDisplay);
if (impl->queue == nullptr || impl->displayWrapper == nullptr) return nullptr;
api.setQueue(static_cast<wl_proxy*>(impl->displayWrapper), impl->queue);
auto* wrapper = static_cast<wl_proxy*>(impl->displayWrapper);
impl->registry = api.marshalFlags(wrapper, kDisplayGetRegistry, api.registryInterface, api.getVersion(wrapper),
0, nullptr);
if (impl->registry == nullptr) return nullptr;
api.addListener(impl->registry, const_cast<void (**)(void)>(kRegistryListener), impl.get());
if (api.roundtripQueue(wlDisplay, impl->queue) < 0 || impl->shm == nullptr) {
MGLOG_E("Wayland: the compositor offers no wl_shm; a window surface cannot be presented");
return nullptr;
}
impl->window->driver_private = impl.get();
impl->window->destroy_window_callback = &OnWindowDestroyed;
MGLOG_I("Wayland: window surface %dx%d presented through wl_shm (%s)", width, height,
hasAlpha ? "ARGB8888" : "XRGB8888");
return UniquePtr<WindowSurface>(new WindowSurface(std::move(impl)));
}
Bool WindowSurface::Present() {
Impl& impl = *m_impl;
if (impl.window == nullptr) return false;
const Api& api = WaylandApi();
ShmBuffer* buffer = impl.NextFree();
if (buffer == nullptr) {
MGLOG_E_ONCE("Wayland: no wl_shm buffer could be taken for the frame; it is not shown");
return false;
}
// The frame, out of the default framebuffer, through this library's own entry points: the
// application's read framebuffer, pack buffer and pack state are saved and put back, so
// the readback is invisible to it.
namespace GL = MG_Impl::GLImpl;
GLint readFramebuffer = 0, packBuffer = 0, alignment = 4, rowLength = 0, skipRows = 0, skipPixels = 0;
GL::GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFramebuffer);
GL::GetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &packBuffer);
GL::GetIntegerv(GL_PACK_ALIGNMENT, &alignment);
GL::GetIntegerv(GL_PACK_ROW_LENGTH, &rowLength);
GL::GetIntegerv(GL_PACK_SKIP_ROWS, &skipRows);
GL::GetIntegerv(GL_PACK_SKIP_PIXELS, &skipPixels);
if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, 0);
if (packBuffer != 0) GL::BindBuffer(GL_PIXEL_PACK_BUFFER, 0);
GL::PixelStorei(GL_PACK_ALIGNMENT, 4);
GL::PixelStorei(GL_PACK_ROW_LENGTH, 0);
GL::PixelStorei(GL_PACK_SKIP_ROWS, 0);
GL::PixelStorei(GL_PACK_SKIP_PIXELS, 0);
const SizeT rowBytes = static_cast<SizeT>(impl.width) * 4;
impl.scratch.resize(rowBytes * static_cast<SizeT>(impl.height));
GL::ReadPixels(0, 0, impl.width, impl.height, GL_RGBA, GL_UNSIGNED_BYTE, impl.scratch.data());
GL::PixelStorei(GL_PACK_ALIGNMENT, alignment);
GL::PixelStorei(GL_PACK_ROW_LENGTH, rowLength);
GL::PixelStorei(GL_PACK_SKIP_ROWS, skipRows);
GL::PixelStorei(GL_PACK_SKIP_PIXELS, skipPixels);
if (packBuffer != 0) GL::BindBuffer(GL_PIXEL_PACK_BUFFER, static_cast<GLuint>(packBuffer));
if (readFramebuffer != 0) GL::BindFramebuffer(GL_READ_FRAMEBUFFER, static_cast<GLuint>(readFramebuffer));
// GL's rows run bottom-up and RGBA; a wl_shm buffer's run top-down and B, G, R, A.
auto* dst = static_cast<Uint8*>(buffer->map);
for (EGLint y = 0; y < impl.height; ++y) {
const Uint8* src = impl.scratch.data() + static_cast<SizeT>(impl.height - 1 - y) * rowBytes;
Uint8* row = dst + static_cast<SizeT>(y) * static_cast<SizeT>(buffer->stride);
for (EGLint x = 0; x < impl.width; ++x) {
row[x * 4 + 0] = src[x * 4 + 2];
row[x * 4 + 1] = src[x * 4 + 1];
row[x * 4 + 2] = src[x * 4 + 0];
row[x * 4 + 3] = src[x * 4 + 3];
}
}
wl_proxy* surface = impl.window->surface;
const uint32_t surfaceVersion = api.getVersion(surface);
api.marshalFlags(surface, kSurfaceAttach, nullptr, surfaceVersion, 0, buffer->buffer, impl.window->dx,
impl.window->dy);
if (surfaceVersion >= 4) {
api.marshalFlags(surface, kSurfaceDamageBuffer, nullptr, surfaceVersion, 0, 0, 0, impl.width,
impl.height);
} else {
api.marshalFlags(surface, kSurfaceDamage, nullptr, surfaceVersion, 0, 0, 0, impl.width, impl.height);
}
api.marshalFlags(surface, kSurfaceCommit, nullptr, surfaceVersion, 0);
buffer->busy = true;
impl.window->attached_width = impl.width;
impl.window->attached_height = impl.height;
impl.window->dx = 0;
impl.window->dy = 0;
api.flush(impl.display);
return true;
}
} // namespace MobileGL::MG_Impl::EGLImpl::Wayland
#endif
+72
View File
@@ -0,0 +1,72 @@
// MobileGL - MobileGL/MG_Impl/EGLImpl/WaylandWindow.h
// 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
#pragma once
// WAYLAND WINDOW SURFACES. A Wayland client hands eglCreateWindowSurface a wl_egl_window, and what
// the compositor shows is whatever wl_buffer the client attaches to that window's wl_surface. The
// pixels of a MobileGL frame are drawn where the backend is - on a split build, in another process
// on another OS - so a window surface here is a pbuffer the backend draws into, plus this: at every
// swap the frame is read back and attached to the wl_surface as a wl_shm buffer.
//
// libwayland-client is loaded at run time (it is already in every Wayland client's process), so the
// library neither links it nor needs its headers to build.
#include <Includes.h>
#if defined(__linux__) && !defined(__ANDROID__)
#define MOBILEGL_WAYLAND_WINDOWS 1
#else
#define MOBILEGL_WAYLAND_WINDOWS 0
#endif
#if MOBILEGL_WAYLAND_WINDOWS
#include <EGL/egl.h>
namespace MobileGL::MG_Impl::EGLImpl::Wayland {
// EGL_PLATFORM_WAYLAND_KHR and EGL_PLATFORM_WAYLAND_EXT are the same enum.
inline constexpr EGLenum kPlatformWayland = 0x31D8;
// 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.
Bool IsWaylandDisplay(Uint64 nativeDisplay, EGLenum platform);
// Whether `window` looks like a wl_egl_window this library can present to.
Bool IsWaylandWindow(const void* window);
// The size the application last gave its wl_egl_window.
Bool WindowSize(const void* window, EGLint* width, EGLint* height);
// One window surface's presentation: the shm buffers, the event queue they are dispatched on,
// and the readback scratch. Created for a wl_egl_window on a wl_display; null with the reason
// logged when libwayland-client or the compositor's wl_shm is not there.
class WindowSurface {
public:
static UniquePtr<WindowSurface> Create(void* wlDisplay, void* wlEglWindow, EGLint width, EGLint height,
Bool hasAlpha);
~WindowSurface();
WindowSurface(const WindowSurface&) = delete;
WindowSurface& operator=(const WindowSurface&) = delete;
// Reads the frame back out of the current context's default framebuffer and puts it on
// the window: attach, damage, commit. The application's read-framebuffer and pack state
// are put back exactly as they were. False (logged) when the frame did not reach the
// window; the swap itself goes on regardless.
Bool Present();
struct Impl;
private:
explicit WindowSurface(UniquePtr<Impl> impl);
UniquePtr<Impl> m_impl;
};
} // namespace MobileGL::MG_Impl::EGLImpl::Wayland
#endif
+10
View File
@@ -394,6 +394,16 @@ namespace MobileGL {
return displayObject && displayObject->Initialized; return displayObject && displayObject->Initialized;
} }
Bool EGLContext::GetDisplayNative(EGLDisplayHandle display, Uint64* nativeDisplayKey,
EGLenum* platform) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* displayObject = TryGetDisplay(display);
if (!displayObject) return false;
*nativeDisplayKey = displayObject->NativeDisplayKey;
*platform = displayObject->Platform;
return true;
}
Bool EGLContext::InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor) { Bool EGLContext::InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex); const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto* displayObject = TryGetDisplay(display); auto* displayObject = TryGetDisplay(display);
+3
View File
@@ -43,6 +43,9 @@ namespace MobileGL {
EGLDisplayHandle GetPlatformDisplay(EGLenum platform, void* nativeDisplay); EGLDisplayHandle GetPlatformDisplay(EGLenum platform, void* nativeDisplay);
Bool ValidateDisplay(EGLDisplayHandle display) const; Bool ValidateDisplay(EGLDisplayHandle display) const;
Bool IsDisplayInitialized(EGLDisplayHandle display) const; Bool IsDisplayInitialized(EGLDisplayHandle display) const;
// The native display a display was made for and the platform it was asked for on
// (EGL_NONE for eglGetDisplay): what decides how its window surfaces are presented.
Bool GetDisplayNative(EGLDisplayHandle display, Uint64* nativeDisplayKey, EGLenum* platform) const;
Bool InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor); Bool InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor);
Bool TerminateDisplay(EGLDisplayHandle display); Bool TerminateDisplay(EGLDisplayHandle display);
// Whole-library idle checks used by EGLImpl::Terminate to decide // Whole-library idle checks used by EGLImpl::Terminate to decide