Skip to content

Commit 3d1757a

Browse files
authored
feat(egl): 第五个成员 libwayland-egl——客户端做 GPU 渲染的唯一入口 (#1)
一个 Wayland 客户端要用 GPU 画东西,需要三样:libwayland-client 给的 wl_surface、libEGL 给的 EGLDisplay,以及把两者接起来的东西。那个东西是 wl_egl_window_create(surface, w, h)——Wayland 上拿到 EGLNativeWindowType 的 唯一办法,只在这里有。没有它, wl_surface -> wl_egl_window -> eglCreateWindowSurface -> draw 第二步就不存在,「Wayland 客户端 + EGL」根本不可表达。 漏掉的原因是这个 fork 建了**合成器**需要的四个成员就停了——合成器渲染进 GBM 缓冲,不走这条路。上游把它放在同一个 tarball 里(upstream/egl/,118 行), 所以这是一个从没写过的成员,不是一个决定。 ## 形态 独立成员而非并进 client,理由和 client/server 分开是同一条: libwayland-egl.so.1 是自己的 SONAME,而 mcpp 把一个包的全部对象链进它的每个 库目标——一个包出不了两个内容不相交的库。Mesa 的 libEGL 按名字对 libwayland-egl.so.1 有 DT_NEEDED,所以它必须是独立文件。 依赖声明 freedesktop.wayland 是**头**依赖,对应上游 .pc 的 `Requires: wayland-client`;不是链接依赖(上游 Libs: 只有 -lwayland-egl,而 wayland-egl.c 不调任何 client 函数——实测该文件里的 wl_*( 只有它自己那四个 定义)。仍然声明,因为拿着 wl_egl_window 的消费者必然从 libwayland-client 拿到了 wl_surface,这条边是真的,只是链接器看不见。 模块手写而非走 genmod.py:那个生成器是为 client/server 各约两百个名字写的, 一次版本升级可能悄悄丢掉一个。这里公共面是四个函数加一个不透明结构,一眼能 读完,而且 CI 逐个点名。也不需要 export inline 转发——wayland-egl-core.h 里 没有 static inline,四个都是普通外部函数,using ::name 既合法又精确。 WL_EGL_PLATFORM 不在模块里也不可能在:它是宏,而 export 命名的是实体。这条 有实际后果——Mesa 的 <EGL/eglplatform.h> 靠它选 Wayland 平台 typedef,所以 消费者要自己 #define,和用 C 头时一样。注释里写明了。 ## CI - SONAME 循环加了 egl 一行,并把 ${want%.0} 改成 ${want%.*}:前者只是碰巧 能用,因为那两个 soname 都以 .0 结尾;libwayland-egl 的以 .1 结尾。 - 新增一步逐个点名四个函数(四个条目用表不用计数——计数在「一个函数被另一个 替换」时仍会通过),并断言它没有混入 client API。 本地实测:SONAME = libwayland-egl.so.1,四个符号全导出,未混入 wl_display_connect。
1 parent a53ff64 commit 3d1757a

4 files changed

Lines changed: 176 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,18 @@ jobs:
9898
head -1 /tmp/wayland-protocol.c
9999
grep -q 'Generated by wayland-scanner 1.26.0' /tmp/wayland-protocol.c
100100
101-
- name: both libraries carry the canonical SONAMEs
101+
- name: every library carries its canonical SONAME
102102
run: |
103-
for pair in "client:libwayland-client.so.0" "server:libwayland-server.so.0"; do
103+
# `${want%.*}` strips ONE trailing version component, whatever it is:
104+
# the built file is the unversioned `lib*.so` and the SONAME is what
105+
# `readelf -d` reports. The earlier `${want%.0}` form only worked
106+
# because both sonames happened to end in `.0` — libwayland-egl's
107+
# ends in `.1` and would have been searched for by its full name.
108+
for pair in "client:libwayland-client.so.0" \
109+
"server:libwayland-server.so.0" \
110+
"egl:libwayland-egl.so.1"; do
104111
member=${pair%%:*}; want=${pair#*:}
105-
so=$(find . -name "${want%.0}" -type f | head -1)
112+
so=$(find . -name "${want%.*}" -type f | head -1)
106113
test -n "$so" || { echo "$member: no library built"; exit 1; }
107114
got=$(readelf -d "$so" | sed -n 's/.*SONAME.*\[\(.*\)\]/\1/p')
108115
echo "$member -> $got"
@@ -134,6 +141,34 @@ jobs:
134141
fi
135142
echo "client and server APIs are disjoint"
136143
144+
- name: libwayland-egl carries its four functions and nothing else's
145+
run: |
146+
# The whole public surface, named one by one. It is four entries, so
147+
# a table beats a count: a count would still pass if one function
148+
# were replaced by another.
149+
#
150+
# Mesa's libEGL dlopens nothing here — it carries DT_NEEDED on
151+
# `libwayland-egl.so.1` by name — so a missing symbol surfaces as a
152+
# link failure in a client, far from this repo.
153+
e=$(find . -name libwayland-egl.so -type f | head -1)
154+
test -n "$e" || { echo "::error::libwayland-egl was not built"; exit 1; }
155+
156+
has() { readelf --dyn-syms -W "$1" | grep -q " $2$"; }
157+
158+
for f in wl_egl_window_create wl_egl_window_destroy \
159+
wl_egl_window_resize wl_egl_window_get_attached_size; do
160+
has "$e" "$f" || { echo "::error::libwayland-egl lost $f"; exit 1; }
161+
done
162+
163+
# It must NOT carry the client's API. wayland-egl.c calls no client
164+
# function, so if `wl_display_connect` shows up here the member has
165+
# started linking libwayland-client's objects in — the same
166+
# overlap the check above forbids between client and server.
167+
if has "$e" wl_display_connect; then
168+
echo "::error::libwayland-egl exports the CLIENT api (wl_display_connect)"; exit 1
169+
fi
170+
echo "libwayland-egl exports exactly its own four functions"
171+
137172
- name: mcpp/generated/ matches what the scanner produces
138173
run: |
139174
# The generated protocol code is checked in because it is the

mcpp.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,11 @@ members = [
3333
"mcpp/util",
3434
"mcpp/client",
3535
"mcpp/server",
36+
# The client's other half. A Wayland program that draws with the GPU needs
37+
# `wl_egl_window_create` to turn a wl_surface into something EGL can render
38+
# into, and it exists nowhere else — so without this member "Wayland client
39+
# + EGL" is not expressible at all. It went missing because the first four
40+
# cover what a COMPOSITOR needs, and a compositor renders into GBM buffers
41+
# instead.
42+
"mcpp/egl",
3643
]

mcpp/egl/mcpp.toml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# libwayland-egl — the client-side window handle EGL renders into.
2+
#
3+
# WHAT IT IS, AND WHY ITS ABSENCE BREAKS A WHOLE CLASS OF PROGRAM
4+
#
5+
# A Wayland client that draws with the GPU needs three things: a `wl_surface`
6+
# from libwayland-client, an `EGLDisplay` from libEGL, and something to join
7+
# them. That something is `wl_egl_window_create(surface, w, h)` — the only way
8+
# to get an `EGLNativeWindowType` on Wayland, and it lives here and nowhere
9+
# else. Without this library the sequence
10+
#
11+
# wl_surface -> wl_egl_window -> eglCreateWindowSurface -> draw
12+
#
13+
# has no second step, so "Wayland client + EGL" is simply not expressible.
14+
# Compositors do not need it (they render into GBM buffers); clients cannot do
15+
# without it.
16+
#
17+
# It was missing because the fork built the four members the SERVER side needs
18+
# and stopped. Upstream ships it in this same tarball — `upstream/egl/`, 118
19+
# lines — so this is a member that was never written rather than a decision.
20+
#
21+
# ─────────────────────────────────────────────────────────────────────────
22+
# WHY A SEPARATE MEMBER RATHER THAN PART OF THE CLIENT
23+
#
24+
# The same reason client and server are separate: `libwayland-egl.so.1` is its
25+
# own SONAME, and mcpp links every library target in a package against all of
26+
# that package's objects — one package cannot emit two libraries with disjoint
27+
# contents. Mesa's libEGL carries DT_NEEDED on `libwayland-egl.so.1` by that
28+
# name, so it has to be its own file.
29+
[package]
30+
namespace = "freedesktop"
31+
name = "wayland-egl"
32+
version = "1.26.0"
33+
standard = "c++23"
34+
description = "libwayland-egl 1.26.0 — wl_egl_window, the bridge from a wl_surface to an EGL window surface"
35+
license = "MIT"
36+
repo = "https://github.com/mcpplibs/wayland"
37+
38+
[build]
39+
# One upstream file. `wayland-egl-abi-check.c` is deliberately absent: it is
40+
# upstream's own test that the struct layout has not changed, and it has a
41+
# `main()` — see the note in ../../mcpp/scanner about why a member that
42+
# provides `main` cannot be linked into a consumer.
43+
sources = [
44+
"../../upstream/egl/wayland-egl.c",
45+
"src/wayland-egl.cppm",
46+
]
47+
48+
# `../generated` is here for a TRANSITIVE include, not a direct one:
49+
# `wayland-egl.h` includes `<wayland-client.h>`, which includes the header
50+
# wayland-scanner emits. The .c file itself names only wayland-egl.h,
51+
# wayland-egl-backend.h and wayland-util.h.
52+
include_dirs = [
53+
"../../upstream/egl",
54+
"../../upstream/src",
55+
"../generated",
56+
]
57+
58+
# -fvisibility=hidden matches upstream's compiler_flags; the public surface is
59+
# exactly what WL_EXPORT marks, which here is the four functions in
60+
# wayland-egl-core.h. No -D_GNU_SOURCE and no config.h: this file uses nothing
61+
# outside C99 plus <stdlib.h>/<string.h>, which is why upstream's meson entry
62+
# for it carries no dependencies either.
63+
cflags = [
64+
"-fPIC",
65+
"-fvisibility=hidden",
66+
]
67+
68+
[lib]
69+
path = "src/wayland-egl.cppm"
70+
71+
[targets.wayland-egl]
72+
kind = "shared"
73+
soname = "libwayland-egl.so.1"
74+
75+
[dependencies]
76+
# `Requires: wayland-client` in upstream's wayland-egl.pc, and it means the
77+
# HEADERS: `wayland-egl.h` includes <wayland-client.h>. It is NOT a link
78+
# dependency — upstream's `Libs:` is `-lwayland-egl` alone, and wayland-egl.c
79+
# calls no client function (verified: the only `wl_*(` in it are its own four
80+
# definitions). Declared anyway, because a consumer holding a `wl_egl_window`
81+
# necessarily got its `wl_surface` from libwayland-client, so the edge is real
82+
# even where the linker does not see it.
83+
freedesktop.wayland = { path = "../client" }

mcpp/egl/src/wayland-egl.cppm

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// freedesktop.wayland.egl — libwayland-egl, as a C++23 module.
2+
//
3+
// A module wrapper and nothing more: every name below is upstream's, spelled
4+
// upstream's way, with upstream's signature. `import freedesktop.wayland.egl;`
5+
// replaces the #include and changes nothing else.
6+
//
7+
// HAND-WRITTEN, unlike the client and server wrappers, and the reason is scale
8+
// rather than taste. `mcpp/tools/genmod.py` exists because those two re-export
9+
// roughly two hundred names each and a version bump could quietly drop one.
10+
// This header's entire public surface is FOUR functions and one opaque struct
11+
// — small enough to read in one glance and to check by other means, which
12+
// `tests/wayland-egl.cpp` does by taking the address of each.
13+
//
14+
// NO `export inline` FORWARDERS ARE NEEDED HERE. The client and server modules
15+
// need them because wayland-scanner emits its convenience wrappers
16+
// `static inline`, and C++ forbids exporting an entity with internal linkage.
17+
// Nothing in `wayland-egl-core.h` is static: all four are ordinary external
18+
// functions marked WL_EXPORT in the .c file, so a plain `using ::name;`
19+
// re-export is both legal and exact.
20+
//
21+
// WL_EGL_PLATFORM is NOT here and cannot be — it is a macro, and `export`
22+
// names entities. It matters: Mesa's `<EGL/eglplatform.h>` selects the Wayland
23+
// platform typedefs when it is defined, so a consumer that needs it must
24+
// `#define WL_EGL_PLATFORM 1` itself before including EGL's headers, exactly
25+
// as it would with the C header. Same arrangement as the wayland macros, which
26+
// live in `freedesktop.wayland.util` as entities.
27+
module;
28+
29+
#include <wayland-egl-core.h>
30+
31+
export module freedesktop.wayland.egl;
32+
33+
export {
34+
35+
// -- types --
36+
//
37+
// `wl_egl_window` is opaque by design: its definition lives in
38+
// wayland-egl-backend.h, which is the contract between this library and an EGL
39+
// implementation, not part of the client-facing API.
40+
using ::wl_egl_window;
41+
42+
// -- functions --
43+
using ::wl_egl_window_create;
44+
using ::wl_egl_window_destroy;
45+
using ::wl_egl_window_resize;
46+
using ::wl_egl_window_get_attached_size;
47+
48+
} // export

0 commit comments

Comments
 (0)