From 65a903e5be963105c420ad592cc6bf50807ca45d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sun, 30 Aug 2026 21:05:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(wayland-egl):=20=E8=A1=A5=E4=B8=8A?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=81=9A=20GPU=20=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E7=9A=84=E5=94=AF=E4=B8=80=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一个 Wayland 客户端要用 EGL 画东西,需要把 wl_surface 变成 EGLNativeWindowType,而唯一的办法是 wl_egl_window_create——它只在 libwayland-egl 里。之前索引里没有这个库,所以 wl_surface -> wl_egl_window -> eglCreateWindowSurface -> draw 第二步不存在,「Wayland 客户端 + EGL」整类程序不可表达。 漏掉的原因:fork 建了**合成器**需要的四个成员就停了(合成器渲染进 GBM 缓冲, 不走这条路),所以索引从服务端看是完整的、从客户端看是不能用的。上游把源码放 在同一个 tarball 里(upstream/egl/,118 行)。 ## tarball 换了指向,tag 没动 加第五个成员改变了归档内容,而一份归档支撑五个索引条目。原来 GLOBAL 指的是 **tag archive**(archive/refs/tags/v1.26.0.tar.gz),重切 tag 会当场打断已发布 的四个描述符的 sha256——正是「同版本重切 tag」那条教训。 所以不动 tag,把新内容作为 **release 资产**发布: wayland-1.26.0-mcpp2.tar.gz。旧 tag 归档原样还在,没有任何时间窗口里 main 是 坏的。附带两个好处:GLOBAL 与 CN 现在形态对称(都是 release 资产),以及重打包 时解引用了 upstream/git-blame-ignore-revs 这个符号链接——按「符号链接归档咬 windows」那条,顺手消掉。 两个镜像逐字节一致(sha256 比对,非可达性): GLOBAL bcf388cc1dd6617f... 1030428 bytes CN bcf388cc1dd6617f... 1030428 bytes ## 测试成员 只名 freedesktop.wayland-egl 一个包,client 走传递依赖——因为 wayland-egl 成员 内部对 client 是 **path** 依赖,而测试再声明一次 version 依赖会被直接拒绝: error: dependency 'freedesktop.wayland' is requested as both a version dep and a path dep. Pick one. 这个诊断是对的:两种拼法可能解析到两棵不同的树。 测试包含 wayland-egl-backend.h——那是这个库与 EGL 实现之间的契约(Mesa 就包含 它),否则透过公共头只能观测到「create 返回非空」。 **第一版我断言错了一处**:以为 get_attached_size 会返回创建尺寸,实际返回 0 x 0。读源码才发现 attached_width/height 是 **EGL 实现附加缓冲时才写**的字段, create 用 calloc 归零、resize 也不碰。现在断言的是真契约:没有东西渲染进去的 窗口报告「什么都没附加」。 16 项断言全过,原有四个包在新 tarball 上照常绿。 --- mcpp.toml | 1 + pkgs/f/freedesktop.wayland-egl.lua | 51 +++++++ pkgs/f/freedesktop.wayland-scanner.lua | 6 +- pkgs/f/freedesktop.wayland-server.lua | 6 +- pkgs/f/freedesktop.wayland-util.lua | 6 +- pkgs/f/freedesktop.wayland.lua | 6 +- tests/examples/wayland-egl/mcpp.toml | 31 +++++ .../wayland-egl/tests/wayland-egl.cpp | 126 ++++++++++++++++++ 8 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 pkgs/f/freedesktop.wayland-egl.lua create mode 100644 tests/examples/wayland-egl/mcpp.toml create mode 100644 tests/examples/wayland-egl/tests/wayland-egl.cpp diff --git a/mcpp.toml b/mcpp.toml index 90d8c59..5a01282 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -86,6 +86,7 @@ members = [ "tests/examples/libffi", "tests/examples/expat", "tests/examples/wayland", + "tests/examples/wayland-egl", "tests/examples/egl", "tests/examples/gl", "tests/examples/libevdev", diff --git a/pkgs/f/freedesktop.wayland-egl.lua b/pkgs/f/freedesktop.wayland-egl.lua new file mode 100644 index 0000000..6d1830c --- /dev/null +++ b/pkgs/f/freedesktop.wayland-egl.lua @@ -0,0 +1,51 @@ +-- freedesktop.wayland-egl — libwayland-egl, and the reason a Wayland client +-- could not use the GPU until now. +-- +-- A Wayland client that draws with EGL needs three things: a `wl_surface` from +-- libwayland-client, an `EGLDisplay` from libEGL, and something to join them. +-- That something is `wl_egl_window_create(surface, w, h)` — the only way to get +-- an `EGLNativeWindowType` on Wayland, and it lives in this library and nowhere +-- else. Without it the sequence +-- +-- wl_surface -> wl_egl_window -> eglCreateWindowSurface -> draw +-- +-- has no second step. +-- +-- WHY IT WAS MISSING, since the gap is instructive: the fork built the four +-- members a COMPOSITOR needs — client, server, util, scanner — and a compositor +-- renders into GBM buffers, so it never asks for this. The index therefore +-- looked complete from the server side and was unusable from the client side. +-- Upstream ships the source in the same tarball (`upstream/egl/`, 118 lines); +-- this was a member nobody had written, not a decision anyone had made. +-- +-- ───────────────────────────────────────────────────────────────────────── +-- FIFTH MEMBER OF THE SAME TARBALL +-- +-- `freedesktop.wayland`, `-server`, `-util`, `-scanner` and this one are five +-- index entries backed by ONE archive, each naming a different workspace +-- member. Adding this one changed the archive, so all five carry the new +-- sha256 in the same commit — see the note in that commit on why the tag was +-- left alone and a release ASSET was published instead. +package = { + spec = "1", + namespace = "freedesktop", + name = "wayland-egl", + description = "libwayland-egl 1.26.0 — wl_egl_window, the bridge from a wl_surface to an EGL window surface", + licenses = {"MIT"}, + repo = "https://github.com/mcpplibs/wayland", + type = "package", + + xpm = { + linux = { + ["1.26.0"] = { + url = { + GLOBAL = "https://github.com/mcpplibs/wayland/releases/download/v1.26.0/wayland-1.26.0-mcpp2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-mcpp2.tar.gz", + }, + sha256 = "bcf388cc1dd6617fdce5cb595defbe2aa1fae8db292ca5d7fd84afda2811be32", + }, + }, + }, + + mcpp = "*/mcpp/egl/mcpp.toml", +} diff --git a/pkgs/f/freedesktop.wayland-scanner.lua b/pkgs/f/freedesktop.wayland-scanner.lua index dc748c6..058d72f 100644 --- a/pkgs/f/freedesktop.wayland-scanner.lua +++ b/pkgs/f/freedesktop.wayland-scanner.lua @@ -33,10 +33,10 @@ package = { linux = { ["1.26.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + GLOBAL = "https://github.com/mcpplibs/wayland/releases/download/v1.26.0/wayland-1.26.0-mcpp2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-mcpp2.tar.gz", }, - sha256 = "961a900dafe76e713c2e46d9a064ff3a6375622f4057013472a04c44d8bcb6e8", + sha256 = "bcf388cc1dd6617fdce5cb595defbe2aa1fae8db292ca5d7fd84afda2811be32", }, }, }, diff --git a/pkgs/f/freedesktop.wayland-server.lua b/pkgs/f/freedesktop.wayland-server.lua index ea499dc..9fcda61 100644 --- a/pkgs/f/freedesktop.wayland-server.lua +++ b/pkgs/f/freedesktop.wayland-server.lua @@ -33,10 +33,10 @@ package = { linux = { ["1.26.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + GLOBAL = "https://github.com/mcpplibs/wayland/releases/download/v1.26.0/wayland-1.26.0-mcpp2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-mcpp2.tar.gz", }, - sha256 = "961a900dafe76e713c2e46d9a064ff3a6375622f4057013472a04c44d8bcb6e8", + sha256 = "bcf388cc1dd6617fdce5cb595defbe2aa1fae8db292ca5d7fd84afda2811be32", }, }, }, diff --git a/pkgs/f/freedesktop.wayland-util.lua b/pkgs/f/freedesktop.wayland-util.lua index bc12e7b..67ee923 100644 --- a/pkgs/f/freedesktop.wayland-util.lua +++ b/pkgs/f/freedesktop.wayland-util.lua @@ -33,10 +33,10 @@ package = { linux = { ["1.26.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + GLOBAL = "https://github.com/mcpplibs/wayland/releases/download/v1.26.0/wayland-1.26.0-mcpp2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-mcpp2.tar.gz", }, - sha256 = "961a900dafe76e713c2e46d9a064ff3a6375622f4057013472a04c44d8bcb6e8", + sha256 = "bcf388cc1dd6617fdce5cb595defbe2aa1fae8db292ca5d7fd84afda2811be32", }, }, }, diff --git a/pkgs/f/freedesktop.wayland.lua b/pkgs/f/freedesktop.wayland.lua index dd16dcc..b783a49 100644 --- a/pkgs/f/freedesktop.wayland.lua +++ b/pkgs/f/freedesktop.wayland.lua @@ -33,10 +33,10 @@ package = { linux = { ["1.26.0"] = { url = { - GLOBAL = "https://github.com/mcpplibs/wayland/archive/refs/tags/v1.26.0.tar.gz", - CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0.tar.gz", + GLOBAL = "https://github.com/mcpplibs/wayland/releases/download/v1.26.0/wayland-1.26.0-mcpp2.tar.gz", + CN = "https://gitcode.com/mcpp-res/wayland/releases/download/1.26.0/wayland-1.26.0-mcpp2.tar.gz", }, - sha256 = "961a900dafe76e713c2e46d9a064ff3a6375622f4057013472a04c44d8bcb6e8", + sha256 = "bcf388cc1dd6617fdce5cb595defbe2aa1fae8db292ca5d7fd84afda2811be32", }, }, }, diff --git a/tests/examples/wayland-egl/mcpp.toml b/tests/examples/wayland-egl/mcpp.toml new file mode 100644 index 0000000..4df006a --- /dev/null +++ b/tests/examples/wayland-egl/mcpp.toml @@ -0,0 +1,31 @@ +# wayland-egl test member — the client-side GPU path. +# +# Its own `[indices]`, for the reason the sibling wayland member documents: the +# workspace root maps `compat` to this checkout and lookup is BY NAMESPACE, so +# `freedesktop.*` would otherwise resolve against the published index instead of +# the packages under test. +[indices] +freedesktop = { path = "../../.." } + +[package] +name = "wayland-egl-tests" +version = "0.1.0" +standard = "c++23" + +# ONE package named, and libwayland-client arrives with it. +# +# `freedesktop.wayland-egl`'s own manifest declares `freedesktop.wayland` as a +# PATH dependency — the two are workspace members of the same fork — and naming +# it here as a version dependency as well is rejected outright: +# +# error: dependency 'freedesktop.wayland' is requested as both a version +# dep (by 'wayland-egl-tests') and a path dep (by +# 'freedesktop.wayland-egl@1.26.0'). Pick one. +# +# That is the right diagnostic: two spellings of one dependency could resolve +# to two different trees. So the client comes in transitively, which is also +# what a real consumer gets — `wl_egl_window_create` takes a `wl_surface *` +# that only libwayland-client can produce, so the edge exists whether or not +# the consumer names it. +[target.'cfg(linux)'.dependencies.freedesktop] +wayland-egl = "1.26.0" diff --git a/tests/examples/wayland-egl/tests/wayland-egl.cpp b/tests/examples/wayland-egl/tests/wayland-egl.cpp new file mode 100644 index 0000000..5671043 --- /dev/null +++ b/tests/examples/wayland-egl/tests/wayland-egl.cpp @@ -0,0 +1,126 @@ +// freedesktop.wayland-egl — the step that used to be missing. +// +// WHAT THIS CAN AND CANNOT ASSERT, decided by what the library actually is. +// +// libwayland-egl is 118 lines and holds no connection: `wl_egl_window_create` +// allocates a struct, stores the `wl_surface *` it was handed, and records a +// size. It calls no client function and talks to no compositor — an EGL +// implementation later reads and writes the struct through +// `wayland-egl-backend.h`. +// +// So a CI runner with no compositor can exercise the whole library honestly. +// What it cannot do is `eglCreateWindowSurface`, which needs a real +// `wl_display` from a real compositor — asserting that here would only assert +// that the runner has a desktop session. +// +// THE BACKEND HEADER IS INCLUDED ON PURPOSE. `wayland-egl-core.h` exposes four +// functions and an opaque type, so through it alone the only observable is +// "create returned non-null". `wayland-egl-backend.h` is the contract this +// library has with an EGL implementation — Mesa includes it — and reading the +// struct through it is what lets the test check that create and resize wrote +// what they claim. +// +// The `wl_surface *` is a null pointer ON PURPOSE: upstream stores it without +// dereferencing (wayland-egl.c:93), so this exercises the real path, and the +// test can then assert the pointer round-tripped. + +#ifdef __linux__ + +#include +#include +#include + +#include +#include + +import freedesktop.wayland.egl; + +namespace { + +int failures = 0; + +void check(bool ok, const char *what) +{ + std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED"); + if (!ok) { + ++failures; + } +} + +} // namespace + +int main() +{ + // ── 1. The module carries the API ──────────────────────────────────── + // Taking the address of each is what makes this a check rather than a + // comment: a name that vanished from the module is a compile error here, + // which is the guarantee genmod.py gives the client and server wrappers + // and that this hand-written module needs by other means. + check(&wl_egl_window_create != nullptr, "module exports wl_egl_window_create"); + check(&wl_egl_window_destroy != nullptr, "module exports wl_egl_window_destroy"); + check(&wl_egl_window_resize != nullptr, "module exports wl_egl_window_resize"); + check(&wl_egl_window_get_attached_size != nullptr, "module exports wl_egl_window_get_attached_size"); + + // ── 2. The bridge itself ───────────────────────────────────────────── + // This is the call that did not exist in this ecosystem before: the only + // way to turn a wl_surface into something EGL can render into. + wl_egl_window *w = wl_egl_window_create(nullptr, 640, 480); + check(w != nullptr, "wl_egl_window_create(surface, 640, 480)"); + if (w == nullptr) { + std::printf("\n%d check(s) failed\n", failures); + return 1; + } + + // ── 3. It wrote what it was given ──────────────────────────────────── + // Read back through the backend header, which is how Mesa reads it. + // `version` matters most of the three: an EGL implementation switches on + // it, and it is the field upstream casts away constness to set. + std::printf(" version=%ld size=%d x %d\n", + (long)w->version, w->width, w->height); + check(w->version == WL_EGL_WINDOW_VERSION, "…and stamped WL_EGL_WINDOW_VERSION"); + check(w->width == 640 && w->height == 480, "…and recorded 640 x 480"); + check(w->surface == nullptr, "…and round-tripped the wl_surface pointer"); + + // ── 4. attached size is 0 BEFORE any EGL implementation attaches ───── + // Not an oversight and not the creation size: `attached_width/height` are + // written by the EGL side when it attaches a buffer, and `create` calloc's + // them to zero. The first version of this test asserted 640 x 480 here and + // was wrong — the check now encodes the real contract, which is that a + // window nothing has rendered into reports nothing attached. + int aw = -1, ah = -1; + wl_egl_window_get_attached_size(w, &aw, &ah); + std::printf(" attached size before any EGL attach: %d x %d\n", aw, ah); + check(aw == 0 && ah == 0, "get_attached_size reports 0 x 0 until EGL attaches"); + + // ── 5. Resize is what a client calls on every configure event ──────── + wl_egl_window_resize(w, 800, 600, 0, 0); + check(w->width == 800 && w->height == 600, "wl_egl_window_resize updated the geometry"); + + // A rejected resize, which is a real branch: upstream returns early on a + // non-positive dimension rather than storing it, so the previous size must + // survive. Same guard `create` uses. + wl_egl_window_resize(w, 0, 600, 0, 0); + check(w->width == 800 && w->height == 600, "…and a width of 0 is refused, not stored"); + + wl_egl_window_destroy(w); + check(true, "wl_egl_window_destroy"); + + // ── 6. create rejects a degenerate size ────────────────────────────── + check(wl_egl_window_create(nullptr, 0, 480) == nullptr, + "wl_egl_window_create refuses a width of 0"); + + // ── 7. The two halves agree on the linkage they share ──────────────── + // `wl_display_connect` comes from libwayland-client and is NOT in + // libwayland-egl — the fork's CI asserts that separation on the library, + // and this asserts the consumer side: both are reachable from one program, + // which is the arrangement a real client is in. + check(&wl_display_connect != nullptr, + "libwayland-client is in the same program (wl_display_connect)"); + + std::printf("\n%d check(s) failed\n", failures); + return failures == 0 ? 0 : 1; +} + +#else +int main() { return 0; } +#endif