From de747d4afb77417a45800af0bed729ea213e230c Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Sun, 30 Aug 2026 03:52:50 +0800 Subject: [PATCH 1/6] =?UTF-8?q?docs(examples):=2009=20=E2=80=94=E2=80=94?= =?UTF-8?q?=20=E7=B3=BB=E7=BB=9F=E7=BA=A7=E5=9B=BE=E5=BD=A2=E6=A0=88,?= =?UTF-8?q?=E7=94=A8=E6=8E=A8=E8=8D=90=E6=96=B9=E5=BC=8F=E5=81=9A=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E6=9D=A1=E9=93=BE=20(#527)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #527 §1 的问法是「宿主库直接动态链接能不能原样用」,拿 `-L/usr/lib -lgbm` 做复现。这个方向是错的:mcpp 运行时不依赖 Host 是设计决策(#527 A3),产物 拿到的是 mcpp 自己算出搜索路径的私有 PT_INTERP,所以 `-L/usr/lib` 不是缺失 的支持,而是错误用法,mcpp 在构建期就会说出来。 该回答的是功能需求本身能不能做到。这个示例就是答案:合成器 / Mesa 面向的 扩展 / GBM 显存管理所需要的那条链,按推荐方式声明依赖跑通。 [target.'cfg(linux)'.dependencies.compat] libgbm = "2026.08.29" libdrm = "2026.08.30" egl = "2026.08.30" wayland = "2026.08.30" 这是全部配置。src/main.cpp 用的是上游原样的头和 API,没有任何 mcpp 特有的 东西 —— 别处照着这些库写的代码搬过来就能编。 它做的是真事而不是「符号能链上」:打开 /dev/dri/renderD128,由该 fd 建出真 的 gbm_device,交给 eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, ...) 并初始 化 EGL。实测两个节点(nvidia-drm / simpledrm)都到 EGL 1.5, vendor Mesa Project。对照 §1.2 里的 gbm_create_device(-1) 返回 NULL —— 那个用例即使在 宿主上跑通也没证明栈可用。 零 Host 是核过的,不是断言的:用产物自己的私有加载器解析完整闭包,11 条全部 落在 registry 内,没有一条在 /usr/lib 或 /lib64。其中 libexpat、libffi、 libGLdispatch 是传递依赖,mcpp.toml 里谁都没写 —— 而 §1.4 断言的正是这层 级联在私有加载器里解不开。声明四个依赖把 11 条都解掉了。 README 另外如实记了一处缺口:Vulkan 不在本示例内,形态也不同 —— compat.vulkan-runtime 仍从 /usr/lib/*、/lib64 收割宿主 ICD,因为 Vulkan 驱动 属于 GPU 厂商,生态里还没有可绑的 payload。GBM/KMS/EGL/Wayland 这条没有这 个边。 --- examples/09-graphics-stack/README.md | 151 ++++++++++++++++++++++++ examples/09-graphics-stack/mcpp.toml | 17 +++ examples/09-graphics-stack/src/main.cpp | 113 ++++++++++++++++++ 3 files changed, 281 insertions(+) create mode 100644 examples/09-graphics-stack/README.md create mode 100644 examples/09-graphics-stack/mcpp.toml create mode 100644 examples/09-graphics-stack/src/main.cpp diff --git a/examples/09-graphics-stack/README.md b/examples/09-graphics-stack/README.md new file mode 100644 index 00000000..19eb85db --- /dev/null +++ b/examples/09-graphics-stack/README.md @@ -0,0 +1,151 @@ +# 09 — System Graphics Without the Host + +A KMS/DRM program that opens a GPU, allocates buffers and brings up EGL — with +nothing from `/usr/lib` and no host loader. + +```bash +mcpp run +``` + +``` +== the graphics stack, resolved from the index == + GBM_BACKENDS_PATH = …/subos/default/usr/lib/gbm + wl_display_create 0x2e8d8be0 +-- DRM node -> GBM device -> EGL display -- + /dev/dri/renderD128 + drm driver nvidia-drm + gbm_create_device 0x2e942f30 + eglGetPlatformDisplay 0x2e9bd390 + eglInitialize EGL 1.5, vendor Mesa Project +done. +``` + +## What this example is for + +System-level graphics work — a Wayland compositor, a Mesa-facing extension, +GBM buffer management — is the case people expect a managed runtime to be bad +at, because it is the case where "just link the system library" is the reflex. + +mcpp's runtime deliberately does not depend on the host: that is what makes a +build reproducible and portable across distributions, and it is why an artifact +gets a private `PT_INTERP` whose search path mcpp computed rather than the +host's `/etc/ld.so.cache`. So `-L/usr/lib -lgbm` is not a thing mcpp is missing +support for — it is the wrong way to ask, and mcpp says so at build time. + +The right question is whether the **work** can be done. This example is the +answer: the whole chain, done the recommended way, declaring dependencies. + +```toml +[target.'cfg(linux)'.dependencies.compat] +libgbm = "2026.08.29" +libdrm = "2026.08.30" +egl = "2026.08.30" +wayland = "2026.08.30" +``` + +That is the entire configuration. `src/main.cpp` then includes ``, +``, `` and `` and calls the stock +upstream APIs — nothing in it is mcpp-specific, so code written against these +libraries anywhere else compiles here unchanged. + +And it does the real thing rather than proving a symbol resolves: it opens +`/dev/dri/renderD128`, builds a genuine `gbm_device` from that fd, hands it to +`eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` and initializes EGL against a +real driver. A `gbm_create_device(-1)` on an invalid fd returns `NULL` and +tells you nothing about whether the stack works; this reaches +`EGL 1.5, vendor Mesa Project`. + +## Checking the claim + +"Host-free" is easy to assert, so it is worth resolving the artifact's closure +through the private loader it actually uses and looking at every path: + +```bash +BIN=target/x86_64-linux-gnu/*/bin/graphics-stack +"$(readelf -p .interp $BIN | grep -o '/.*ld-linux[^ ]*')" --list $BIN +``` + +``` +compat-x-egl/2026.08.30/…/libEGL.so.1 +compat-x-libdrm/2026.08.30/…/libdrm.so.2 +compat-x-libgbm/2026.08.29/…/libgbm.so.1 +compat-x-wayland/2026.08.30/…/libwayland-client.so.0 +compat-x-wayland/2026.08.30/…/libwayland-server.so.0 +xim-x-expat/2.6.2/lib/libexpat.so.1 +xim-x-gcc/16.1.0/lib64/libgcc_s.so.1 +xim-x-glibc/2.44/lib64/libc.so.6 +xim-x-glibc/2.44/lib64/libm.so.6 +xim-x-libffi/3.4.4/lib/libffi.so.8 +xim-x-libglvnd/1.7.0.1/lib/libGLdispatch.so.0 +``` + +Every entry is under the registry; none is under `/usr/lib` or `/lib64`. Note +the bottom half especially — `libexpat`, `libffi` and `libGLdispatch` are +*transitive*: nothing in `mcpp.toml` names them. They are what a directly +linked `libgbm.so.1` cascades into, and resolving that cascade is exactly what +the host path cannot do from inside a private loader. Declaring the four +dependencies resolved all eleven. + +## The packages + +None of them vendors a source tree. Mesa, libdrm, libglvnd and wayland are +already in the ecosystem (`xim:mesa`, `xim:libdrm`, `xim:libglvnd`, +`xim:wayland`), so each package is a thin binding: it declares the ecosystem +package it needs and exposes that payload's headers and libraries to the +compiler. Building second copies would put two `libgbm.so.1` — or two +`libdrm.so.2`, or a second EGL dispatch library — in a process that already +loads Mesa's. + +| package | what it gives you | +|---|---| +| `compat.libgbm` | `gbm_create_device`, `gbm_bo_create` — buffers out of a DRM device | +| `compat.libdrm` | `drmModeGetResources`, `drmModeAddFB2`, `drmModeSetCrtc` — the KMS side | +| `compat.egl` | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them | +| `compat.wayland` | client and server libraries for the display protocol | + +One honest gap, since "Mesa/Vulkan" usually get named together: Vulkan is not +part of this example and is not in the same state. `compat.vulkan-runtime` +builds its farm by harvesting the host's ICDs out of `/usr/lib/*` and `/lib64`, +because a Vulkan driver is the GPU vendor's and there is no ecosystem payload +to bind to yet. The GBM/KMS/EGL/Wayland stack above has no such edge. + +## Two things worth knowing + +**`GBM_BACKENDS_PATH` is not set by any of these packages.** libgbm is a +loader: `gbm_create_device()` dlopens `/_gbm.so`, and the path +Mesa compiles in is `/usr/lib/gbm` — correct on a distribution, wrong the +moment the payload lives anywhere else. Setting that variable is Mesa's own +mechanism and the *environment's* job, which is where every relocated stack +puts it (Valve's pressure-vessel, Nix, Conda all do exactly this). Here +`xim:mesa` declares it into the SubOS and mcpp carries SubOS declarations into +the processes it launches, so it is simply already set — which is why the +program prints it rather than computing it. + +**`compat.wayland` puts only `-lwayland-client` on the link line**, and this +example adds the other half itself: + +```toml +[target.'cfg(linux)'.build] +ldflags = ["-lwayland-server"] +``` + +A dependency's `ldflags` reach every consumer with no way to opt out, so a +package that forced `libwayland-server` on every client would be unfixable +downstream. All four wayland libraries are present; a compositor asks for the +one it needs and it resolves out of the same package. + +## Running it + +The DRM section needs a GPU. On a machine without one — a container, most CI +runners — the program says so and everything that does not need hardware has +already run: + +``` + (no DRM node reached EGL — expected without a GPU) +``` + +To watch the backend loader itself, ask Mesa: + +```bash +EGL_LOG_LEVEL=debug mcpp run +``` diff --git a/examples/09-graphics-stack/mcpp.toml b/examples/09-graphics-stack/mcpp.toml new file mode 100644 index 00000000..b3876176 --- /dev/null +++ b/examples/09-graphics-stack/mcpp.toml @@ -0,0 +1,17 @@ +[package] +name = "graphics-stack" +version = "0.1.0" + +# The whole KMS/DRM stack, from the index. No host paths, no -L/usr/lib. +[target.'cfg(linux)'.dependencies.compat] +libgbm = "2026.08.29" # buffer allocation out of a DRM device +libdrm = "2026.08.30" # the KMS side: modes, CRTCs, framebuffers +egl = "2026.08.30" # rendering onto those buffers +wayland = "2026.08.30" # the display protocol, client and server + +# compat.wayland puts only -lwayland-client on the link line, because a +# dependency's ldflags reach every consumer with no way to opt out. A +# compositor asks for the server library itself; it resolves out of the same +# package. +[target.'cfg(linux)'.build] +ldflags = ["-lwayland-server"] diff --git a/examples/09-graphics-stack/src/main.cpp b/examples/09-graphics-stack/src/main.cpp new file mode 100644 index 00000000..1b0949b5 --- /dev/null +++ b/examples/09-graphics-stack/src/main.cpp @@ -0,0 +1,113 @@ +// System-level graphics with no host dependency. +// +// This is the sequence a KMS/DRM program or a Wayland compositor actually +// runs. Every header here is the stock upstream one and every call is the +// stock upstream API — there is nothing mcpp-specific in this file, which is +// the point: code written against these libraries anywhere else compiles here +// unchanged. + +#include // compat.libgbm +#include // compat.libdrm +#include +#include +#include // compat.egl +#include +#include // compat.wayland +#include + +#include +#include +#include +#include + +#include +#include + +namespace { + +// The two APIs exchange this value across the gbm_bo -> drmModeAddFB2 +// boundary. If the packages ever disagreed about it, a display would show the +// wrong colours and nothing would report an error — so it is worth asserting +// rather than assuming. +static_assert(GBM_FORMAT_XRGB8888 == DRM_FORMAT_XRGB8888, + "libgbm and libdrm must agree on the XRGB8888 fourcc"); + +void report(const char *label, const void *p) +{ + std::printf(" %-24s %p\n", label, p); +} + +} // namespace + +int main() +{ + std::puts("== the graphics stack, resolved from the index =="); + + // Where the GBM backends are found. Nothing in this program and nothing in + // compat.libgbm sets this: `xim:mesa` declares it into the SubOS through + // the graphics discovery layer, and mcpp carries SubOS declarations into + // the processes it launches. + const char *backends = std::getenv("GBM_BACKENDS_PATH"); + std::printf(" GBM_BACKENDS_PATH = %s\n", + backends ? backends : ""); + + // Wayland: build a server-side display. No socket is bound, so this needs + // no session and no privileges — the cheapest proof the library is live. + if (wl_display *server = wl_display_create()) { + report("wl_display_create", server); + wl_display_destroy(server); + } else { + std::puts(" wl_display_create FAILED"); + return 1; + } + + // The real chain: a DRM node becomes a GBM device, which becomes an EGL + // display. This is what "headless GPU rendering" means concretely, and it + // is the sequence that cannot be expressed without all three packages. + std::puts("-- DRM node -> GBM device -> EGL display --"); + bool reached_egl = false; + + for (const char *node : {"/dev/dri/renderD128", "/dev/dri/card0"}) { + const int fd = ::open(node, O_RDWR); + if (fd < 0) { + std::printf(" %-24s (not present on this machine)\n", node); + continue; + } + std::printf(" %s\n", node); + + if (drmVersionPtr v = drmGetVersion(fd)) { + std::printf(" %-24s %s\n", "drm driver", v->name); + drmFreeVersion(v); + } + + if (gbm_device *gbm = gbm_create_device(fd)) { + report("gbm_create_device", gbm); + + EGLDisplay dpy = + eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm, nullptr); + report("eglGetPlatformDisplay", dpy); + + if (dpy != EGL_NO_DISPLAY) { + EGLint major = 0, minor = 0; + if (eglInitialize(dpy, &major, &minor)) { + std::printf(" %-24s EGL %d.%d, vendor %s\n", "eglInitialize", + major, minor, eglQueryString(dpy, EGL_VENDOR)); + reached_egl = true; + eglTerminate(dpy); + } + } + gbm_device_destroy(gbm); + } + ::close(fd); + } + + if (!reached_egl) { + // Not a failure of the packages: a machine with no DRM node (a + // container, most CI runners) legitimately gets here. Everything above + // that does not need hardware has already run. + std::puts(" (no DRM node reached EGL — expected without a GPU)"); + } + + std::puts("done."); + return 0; +} From 90dfc5ea9e4bf937d4928cc2bba39443063e64e5 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Sun, 30 Aug 2026 03:53:28 +0800 Subject: [PATCH 2/6] =?UTF-8?q?docs(examples):=20=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E9=87=8C=E8=A1=A5=E4=B8=8A=2009?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/01-examples.md | 1 + docs/zh/01-examples.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/01-examples.md b/docs/01-examples.md index 6365027a..ce462042 100644 --- a/docs/01-examples.md +++ b/docs/01-examples.md @@ -27,6 +27,7 @@ examples. | 02 | [`examples/02-with-deps`](../examples/02-with-deps/) | Adds the `mcpplibs.cmdline` dependency to parse command-line arguments | `[dependencies]`, SemVer, `mcpp.lock` | | 03 | [`examples/03-pack-static`](../examples/03-pack-static/) | Produces a fully static release package via `mcpp pack --mode static` | `[target.]` and `[pack]` configuration | | 08 | [`examples/08-build-rules`](../examples/08-build-rules/) | Two rule packages and a project that uses both | `host-module = true`, `[build-dependencies]`, `mcpp::action` with `role = "check"` | +| 09 | [`examples/09-graphics-stack`](../examples/09-graphics-stack/) | KMS/DRM -> GBM -> EGL, plus Wayland, with nothing from the host | `compat.libgbm` / `libdrm` / `egl` / `wayland`, SubOS-supplied `GBM_BACKENDS_PATH` | ## Suggested Reading Order diff --git a/docs/zh/01-examples.md b/docs/zh/01-examples.md index e20aca86..f591ef3d 100644 --- a/docs/zh/01-examples.md +++ b/docs/zh/01-examples.md @@ -24,6 +24,7 @@ mcpp build && mcpp run | 02 | [`examples/02-with-deps`](../../examples/02-with-deps/) | 引入依赖 `mcpplibs.cmdline` 解析命令行参数 | `[dependencies]`、SemVer、`mcpp.lock` | | 03 | [`examples/03-pack-static`](../../examples/03-pack-static/) | 通过 `mcpp pack --mode static` 生成全静态发布包 | `[target.]` 与 `[pack]` 配置 | | 08 | [`examples/08-build-rules`](../../examples/08-build-rules/) | 两个规则包,以及同时用到它们的工程 | `host-module = true`、`[build-dependencies]`、`role = "check"` 的 `mcpp::action` | +| 09 | [`examples/09-graphics-stack`](../../examples/09-graphics-stack/) | KMS/DRM -> GBM -> EGL 加 Wayland,不碰宿主的任何东西 | `compat.libgbm` / `libdrm` / `egl` / `wayland`、由 SubOS 提供的 `GBM_BACKENDS_PATH` | ## 推荐阅读顺序 From 7e3914a3bd879db325557fedf5bc45186200d756 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 10:55:59 +0800 Subject: [PATCH 3/6] 09: the index moved the graphics stack to source builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mcpplibs/mcpp-index#289 replaced the date-stamped versions with the upstream projects' own release numbers, and made compat.libdrm a source build. The example follows: libdrm 2026.08.30 -> 2.4.134 (now built from source) libgbm 2026.08.29 -> 25.0.7 (Mesa's version) egl 2026.08.30 -> 1.7.0 (libglvnd's version) The README's 'The packages' section said none of them vendors a source tree, which is no longer true, and the closure listing is re-measured against the published index. The interesting line is the first one: libdrm.so.2 resolves to the project's OWN build output rather than the payload copy Mesa was linked against — a soname already in the link map is reused, so the consumer's copy wins and Mesa's GBM allocates through it. That is why a library the payload also carries can still be built from source. --- examples/09-graphics-stack/README.md | 118 ++++++++++++++++-------- examples/09-graphics-stack/mcpp.toml | 13 ++- examples/09-graphics-stack/src/main.cpp | 15 +++ 3 files changed, 104 insertions(+), 42 deletions(-) diff --git a/examples/09-graphics-stack/README.md b/examples/09-graphics-stack/README.md index 19eb85db..23dbfcb7 100644 --- a/examples/09-graphics-stack/README.md +++ b/examples/09-graphics-stack/README.md @@ -10,16 +10,29 @@ mcpp run ``` == the graphics stack, resolved from the index == GBM_BACKENDS_PATH = …/subos/default/usr/lib/gbm - wl_display_create 0x2e8d8be0 + wl_display_create 0x3f798be0 -- DRM node -> GBM device -> EGL display -- /dev/dri/renderD128 drm driver nvidia-drm - gbm_create_device 0x2e942f30 - eglGetPlatformDisplay 0x2e9bd390 + gbm_create_device 0x3f802f30 + gbm_bo_create (driver declined this format/usage) + eglGetPlatformDisplay 0x3f87d5b0 + eglInitialize EGL 1.5, vendor Mesa Project + /dev/dri/card0 + drm driver simpledrm + gbm_create_device 0x3f802f30 + gbm_bo_create 256x256 stride=1024 modifier=0xffffffffffffff + eglGetPlatformDisplay 0x3f87d5b0 eglInitialize EGL 1.5, vendor Mesa Project done. ``` +That is one real run on a two-node machine, kept unabridged because the +difference between the nodes is the point: `simpledrm` allocated the buffer and +reported the driver's own stride and modifier, while NVIDIA's GBM backend +declined that format/usage combination. Both are the libraries answering — this +is the stack working, not a smoke test. + ## What this example is for System-level graphics work — a Wayland compositor, a Mesa-facing extension, @@ -37,9 +50,9 @@ answer: the whole chain, done the recommended way, declaring dependencies. ```toml [target.'cfg(linux)'.dependencies.compat] -libgbm = "2026.08.29" -libdrm = "2026.08.30" -egl = "2026.08.30" +libdrm = "2.4.134" +libgbm = "25.0.7" +egl = "1.7.0" wayland = "2026.08.30" ``` @@ -48,17 +61,18 @@ That is the entire configuration. `src/main.cpp` then includes ``, upstream APIs — nothing in it is mcpp-specific, so code written against these libraries anywhere else compiles here unchanged. -And it does the real thing rather than proving a symbol resolves: it opens -`/dev/dri/renderD128`, builds a genuine `gbm_device` from that fd, hands it to -`eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` and initializes EGL against a -real driver. A `gbm_create_device(-1)` on an invalid fd returns `NULL` and -tells you nothing about whether the stack works; this reaches -`EGL 1.5, vendor Mesa Project`. +And it does the real thing rather than proving a symbol resolves: it opens a +DRM node, builds a genuine `gbm_device` from that fd, **allocates actual GPU +memory** with `gbm_bo_create` and reads back the stride and modifier the driver +chose, then hands the device to +`eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` and initializes EGL. A +`gbm_create_device(-1)` on an invalid fd returns `NULL` and tells you nothing +about whether the stack works; this reaches `EGL 1.5, vendor Mesa Project`. ## Checking the claim -"Host-free" is easy to assert, so it is worth resolving the artifact's closure -through the private loader it actually uses and looking at every path: +"Host-free" is easy to assert, so resolve the artifact's closure through the +private loader it actually uses and look at every path: ```bash BIN=target/x86_64-linux-gnu/*/bin/graphics-stack @@ -66,11 +80,11 @@ BIN=target/x86_64-linux-gnu/*/bin/graphics-stack ``` ``` -compat-x-egl/2026.08.30/…/libEGL.so.1 -compat-x-libdrm/2026.08.30/…/libdrm.so.2 -compat-x-libgbm/2026.08.29/…/libgbm.so.1 -compat-x-wayland/2026.08.30/…/libwayland-client.so.0 -compat-x-wayland/2026.08.30/…/libwayland-server.so.0 +/target/.../bin/libdrm.so.2 <- built from source, by this build +compat-x-libgbm/25.0.7/…/libgbm.so.1 +compat-x-egl/1.7.0/…/libEGL.so.1 +compat-x-wayland/…/libwayland-client.so.0 +compat-x-wayland/…/libwayland-server.so.0 xim-x-expat/2.6.2/lib/libexpat.so.1 xim-x-gcc/16.1.0/lib64/libgcc_s.so.1 xim-x-glibc/2.44/lib64/libc.so.6 @@ -79,29 +93,55 @@ xim-x-libffi/3.4.4/lib/libffi.so.8 xim-x-libglvnd/1.7.0.1/lib/libGLdispatch.so.0 ``` -Every entry is under the registry; none is under `/usr/lib` or `/lib64`. Note -the bottom half especially — `libexpat`, `libffi` and `libGLdispatch` are -*transitive*: nothing in `mcpp.toml` names them. They are what a directly -linked `libgbm.so.1` cascades into, and resolving that cascade is exactly what -the host path cannot do from inside a private loader. Declaring the four -dependencies resolved all eleven. +Nothing is under `/usr/lib` or `/lib64`. Two things in that list are worth +reading closely. + +**The first line.** `libdrm.so.2` resolves to this project's own build output, +not to the `xim-x-libdrm` the Mesa payload was linked against — even though +`libgbm.so.1` has a DT_NEEDED on that soname and an absolute RUNPATH pointing +into the payload. The consumer links libdrm directly, so it is mapped first, +and Mesa's GBM binds to it: the `gbm_bo_create` above ran through it. + +**The bottom half.** `libexpat`, `libffi` and `libGLdispatch` are *transitive* — +nothing in `mcpp.toml` names them. They are what a directly linked +`libgbm.so.1` cascades into, and resolving that cascade is exactly what a host +`-L/usr/lib` cannot do from inside a private loader. ## The packages -None of them vendors a source tree. Mesa, libdrm, libglvnd and wayland are -already in the ecosystem (`xim:mesa`, `xim:libdrm`, `xim:libglvnd`, -`xim:wayland`), so each package is a thin binding: it declares the ecosystem -package it needs and exposes that payload's headers and libraries to the -compiler. Building second copies would put two `libgbm.so.1` — or two -`libdrm.so.2`, or a second EGL dispatch library — in a process that already -loads Mesa's. - -| package | what it gives you | -|---|---| -| `compat.libgbm` | `gbm_create_device`, `gbm_bo_create` — buffers out of a DRM device | -| `compat.libdrm` | `drmModeGetResources`, `drmModeAddFB2`, `drmModeSetCrtc` — the KMS side | -| `compat.egl` | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them | -| `compat.wayland` | client and server libraries for the display protocol | +Two of them are built from source and two bind the ecosystem's Mesa, and the +split is not arbitrary. A library is built from source when upstream ships it +as a **separable unit**; it is bound when it is an internal build target of a +project the ecosystem already owns, where building it would mean forking that +project. + +| package | | what it gives you | +|---|---|---| +| `compat.libdrm` | source | `drmModeGetResources`, `drmModeAddFB2`, `drmModeSetCrtc` — the KMS side | +| `compat.libgbm` | binds `xim:mesa` | `gbm_create_device`, `gbm_bo_create` — buffers out of a DRM device | +| `compat.egl` | binds `xim:libglvnd` | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them | +| `compat.wayland` | binds `xim:wayland` | client and server libraries for the display protocol | + +libdrm passes the test — an independent freedesktop project with its own +releases — so it is compiled here, five translation units with no dependencies +at all. GBM fails it: `src/gbm/meson.build` is `link_with: [libloader]`, and +`libloader` wants `idep_mesautil`, roughly 120 TUs of Mesa's internal utility +library for one function. It is also a *loader*, and the backends it dlopens +are Mesa's own, so built apart from Mesa it would have nothing to load. + +**A payload carrying the same library is not a reason to bind**, which is worth +saying because it looks like one. Mesa's `libgbm.so.1` has a DT_NEEDED on +`libdrm.so.2` and an absolute RUNPATH into the payload's copy — and in this +program that RUNPATH loses. A soname already in the link map is reused, so +ld.so never searches for it again: the `libdrm.so.2` this project linked is +mapped first, exactly one is loaded, and Mesa's GBM allocated the buffer above +through it. That only holds because the package builds a *shared* library with +the canonical soname; merged into the consumer as objects there would be two +copies of libdrm's internal state over one set of file descriptors. + +`compat.egl` is a binding for a duller reason: libglvnd IS separable, but +`libEGL.so` also needs its Python-generated dispatch stubs, `winsys_dispatch` +and the whole of `libGLdispatch.so`, so it has not been done yet. One honest gap, since "Mesa/Vulkan" usually get named together: Vulkan is not part of this example and is not in the same state. `compat.vulkan-runtime` diff --git a/examples/09-graphics-stack/mcpp.toml b/examples/09-graphics-stack/mcpp.toml index b3876176..c79a16fe 100644 --- a/examples/09-graphics-stack/mcpp.toml +++ b/examples/09-graphics-stack/mcpp.toml @@ -3,10 +3,17 @@ name = "graphics-stack" version = "0.1.0" # The whole KMS/DRM stack, from the index. No host paths, no -L/usr/lib. +# +# The versions are the upstream projects' own release numbers, and two of these +# packages are BUILT FROM SOURCE here while two bind the ecosystem's Mesa. The +# split is not arbitrary: a library is built from source when upstream ships it +# as a separable unit, and bound when it is an internal target of a project the +# ecosystem already owns. libdrm passes that test; GBM does not — it is a build +# target inside Mesa, and a loader whose backends are Mesa's own. [target.'cfg(linux)'.dependencies.compat] -libgbm = "2026.08.29" # buffer allocation out of a DRM device -libdrm = "2026.08.30" # the KMS side: modes, CRTCs, framebuffers -egl = "2026.08.30" # rendering onto those buffers +libdrm = "2.4.134" # source-built; the KMS side: modes, CRTCs, framebuffers +libgbm = "25.0.7" # Mesa's GBM: buffer allocation out of a DRM device +egl = "1.7.0" # libglvnd's EGL dispatch: rendering onto those buffers wayland = "2026.08.30" # the display protocol, client and server # compat.wayland puts only -lwayland-client on the link line, because a diff --git a/examples/09-graphics-stack/src/main.cpp b/examples/09-graphics-stack/src/main.cpp index 1b0949b5..9ab0be05 100644 --- a/examples/09-graphics-stack/src/main.cpp +++ b/examples/09-graphics-stack/src/main.cpp @@ -83,6 +83,21 @@ int main() if (gbm_device *gbm = gbm_create_device(fd)) { report("gbm_create_device", gbm); + // Actually allocate GPU memory. Creating the device only proves the + // backend loaded; a buffer object is the thing a compositor hands + // to drmModeAddFB2 for scanout, and its stride and modifier come + // back from the driver rather than from libgbm. + if (gbm_bo *bo = gbm_bo_create(gbm, 256, 256, GBM_FORMAT_XRGB8888, + GBM_BO_USE_RENDERING)) { + std::printf(" %-24s 256x256 stride=%u modifier=0x%llx\n", + "gbm_bo_create", gbm_bo_get_stride(bo), + (unsigned long long)gbm_bo_get_modifier(bo)); + gbm_bo_destroy(bo); + } else { + std::printf(" %-24s (driver declined this format/usage)\n", + "gbm_bo_create"); + } + EGLDisplay dpy = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gbm, nullptr); report("eglGetPlatformDisplay", dpy); From c1b6051ea670010585606cc19fab123db81b60ec Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 11:50:16 +0800 Subject: [PATCH 4/6] 09: wayland is source-built now too, and is two packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mcpplibs/mcpp-index#290 replaced compat.wayland with freedesktop.wayland and freedesktop.wayland-server, built from source out of mcpplibs/wayland. They are two packages because they are two SONAMEs and Mesa's libEGL_mesa has DT_NEEDED on both, and because mcpp links every library target in a package against all of that package's sources — one package cannot emit two libraries with disjoint contents. So the -lwayland-server escape hatch this example used to demonstrate is gone: a compositor asks for the server package by name. The closure is re-measured. Four of the twelve entries are now this project's own build output — libdrm, libffi and both wayland libraries — and the first of those is the interesting one: Mesa's libgbm has an absolute RUNPATH into the payload's libdrm and still binds to ours, because a soname already in the link map is reused. --- examples/09-graphics-stack/README.md | 88 ++++++++++++++++------------ examples/09-graphics-stack/mcpp.toml | 38 ++++++------ 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/examples/09-graphics-stack/README.md b/examples/09-graphics-stack/README.md index 23dbfcb7..6f92f03e 100644 --- a/examples/09-graphics-stack/README.md +++ b/examples/09-graphics-stack/README.md @@ -50,10 +50,13 @@ answer: the whole chain, done the recommended way, declaring dependencies. ```toml [target.'cfg(linux)'.dependencies.compat] -libdrm = "2.4.134" -libgbm = "25.0.7" -egl = "1.7.0" -wayland = "2026.08.30" +libdrm = "2.4.134" +libgbm = "25.0.7" +egl = "1.7.0" + +[target.'cfg(linux)'.dependencies.freedesktop] +wayland = "1.26.0" +wayland-server = "1.26.0" ``` That is the entire configuration. `src/main.cpp` then includes ``, @@ -80,47 +83,49 @@ BIN=target/x86_64-linux-gnu/*/bin/graphics-stack ``` ``` -/target/.../bin/libdrm.so.2 <- built from source, by this build +/bin/libdrm.so.2 <- built by this build +/bin/libwayland-client.so.0 <- built by this build +/bin/libwayland-server.so.0 <- built by this build +/bin/libffi.so.8 <- built by this build compat-x-libgbm/25.0.7/…/libgbm.so.1 compat-x-egl/1.7.0/…/libEGL.so.1 -compat-x-wayland/…/libwayland-client.so.0 -compat-x-wayland/…/libwayland-server.so.0 xim-x-expat/2.6.2/lib/libexpat.so.1 xim-x-gcc/16.1.0/lib64/libgcc_s.so.1 +xim-x-gcc/16.1.0/lib64/libstdc++.so.6 xim-x-glibc/2.44/lib64/libc.so.6 xim-x-glibc/2.44/lib64/libm.so.6 -xim-x-libffi/3.4.4/lib/libffi.so.8 xim-x-libglvnd/1.7.0.1/lib/libGLdispatch.so.0 ``` -Nothing is under `/usr/lib` or `/lib64`. Two things in that list are worth -reading closely. +Nothing is under `/usr/lib` or `/lib64`. Two things there are worth reading +closely. -**The first line.** `libdrm.so.2` resolves to this project's own build output, -not to the `xim-x-libdrm` the Mesa payload was linked against — even though -`libgbm.so.1` has a DT_NEEDED on that soname and an absolute RUNPATH pointing -into the payload. The consumer links libdrm directly, so it is mapped first, -and Mesa's GBM binds to it: the `gbm_bo_create` above ran through it. +**The first four lines.** They are this project's own build output, not the +ecosystem's copies — including `libdrm.so.2`, even though Mesa's `libgbm.so.1` +has a DT_NEEDED on that soname and an absolute RUNPATH into the payload. The +consumer links them directly, so they are mapped first, and Mesa binds to them: +the `gbm_bo_create` above ran through this libdrm. -**The bottom half.** `libexpat`, `libffi` and `libGLdispatch` are *transitive* — -nothing in `mcpp.toml` names them. They are what a directly linked -`libgbm.so.1` cascades into, and resolving that cascade is exactly what a host -`-L/usr/lib` cannot do from inside a private loader. +**`libffi` and `libGLdispatch`.** Nothing in `mcpp.toml` names either. +`libffi.so.8` is what `libwayland-client` dispatches protocol messages through, +`libGLdispatch` is what libEGL's vendor dispatch needs — the cascade a directly +linked library pulls behind it, which is exactly what a host `-L/usr/lib` +cannot resolve from inside a private loader. ## The packages -Two of them are built from source and two bind the ecosystem's Mesa, and the -split is not arbitrary. A library is built from source when upstream ships it -as a **separable unit**; it is bound when it is an internal build target of a -project the ecosystem already owns, where building it would mean forking that -project. +Three are built from source and two bind the ecosystem's Mesa, and the split is +not arbitrary. A library is built from source when upstream ships it as a +**separable unit**; it is bound when it is an internal build target of a project +the ecosystem already owns, where building it would mean forking that project. | package | | what it gives you | |---|---|---| | `compat.libdrm` | source | `drmModeGetResources`, `drmModeAddFB2`, `drmModeSetCrtc` — the KMS side | | `compat.libgbm` | binds `xim:mesa` | `gbm_create_device`, `gbm_bo_create` — buffers out of a DRM device | | `compat.egl` | binds `xim:libglvnd` | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them | -| `compat.wayland` | binds `xim:wayland` | client and server libraries for the display protocol | +| `freedesktop.wayland` | source | `libwayland-client.so.0`, and `import wayland.client;` | +| `freedesktop.wayland-server` | source | `libwayland-server.so.0`, and `import wayland.server;` | libdrm passes the test — an independent freedesktop project with its own releases — so it is compiled here, five translation units with no dependencies @@ -129,6 +134,14 @@ at all. GBM fails it: `src/gbm/meson.build` is `link_with: [libloader]`, and library for one function. It is also a *loader*, and the backends it dlopens are Mesa's own, so built apart from Mesa it would have nothing to load. +Wayland passes it too, but needed more than a descriptor: its libraries are +mostly **generated** — `protocol/wayland.xml` describes every interface and +`wayland-scanner` emits ~13,000 lines from it — and the generator is a C program +in the same tree that must be compiled first. That does not fit an inline index +descriptor, so it lives in [mcpplibs/wayland](https://github.com/mcpplibs/wayland), +a fork that patches no upstream file. Client and server are two packages because +they are two distinct SONAMEs and Mesa's `libEGL_mesa` has DT_NEEDED on **both**. + **A payload carrying the same library is not a reason to bind**, which is worth saying because it looks like one. Mesa's `libgbm.so.1` has a DT_NEEDED on `libdrm.so.2` and an absolute RUNPATH into the payload's copy — and in this @@ -161,18 +174,19 @@ puts it (Valve's pressure-vessel, Nix, Conda all do exactly this). Here the processes it launches, so it is simply already set — which is why the program prints it rather than computing it. -**`compat.wayland` puts only `-lwayland-client` on the link line**, and this -example adds the other half itself: - -```toml -[target.'cfg(linux)'.build] -ldflags = ["-lwayland-server"] -``` - -A dependency's `ldflags` reach every consumer with no way to opt out, so a -package that forced `libwayland-server` on every client would be unfixable -downstream. All four wayland libraries are present; a compositor asks for the -one it needs and it resolves out of the same package. +**The wayland client and server are separate packages**, and this example asks +for both because it creates a `wl_display` on the server side. That is not a +packaging quirk: they are two SONAMEs, Mesa's `libEGL_mesa` carries DT_NEEDED on +each, and mcpp links every library target in a package against all of that +package's sources — so one package cannot emit two libraries with disjoint +contents. A client-only program drops the second line and links only +`libwayland-client.so.0`. + +Both also ship a C++23 module wrapper. `import wayland.client;` in place of +`#include ` changes nothing else — every exported name is +upstream's, spelled upstream's way — so this file could switch one line at a +time. It uses the headers here because that is what a ported project looks like +on day one. ## Running it diff --git a/examples/09-graphics-stack/mcpp.toml b/examples/09-graphics-stack/mcpp.toml index c79a16fe..4af92ccf 100644 --- a/examples/09-graphics-stack/mcpp.toml +++ b/examples/09-graphics-stack/mcpp.toml @@ -1,24 +1,26 @@ [package] -name = "graphics-stack" -version = "0.1.0" +name = "graphics-stack" +version = "0.1.0" +standard = "c++23" # The whole KMS/DRM stack, from the index. No host paths, no -L/usr/lib. # -# The versions are the upstream projects' own release numbers, and two of these -# packages are BUILT FROM SOURCE here while two bind the ecosystem's Mesa. The -# split is not arbitrary: a library is built from source when upstream ships it -# as a separable unit, and bound when it is an internal target of a project the -# ecosystem already owns. libdrm passes that test; GBM does not — it is a build -# target inside Mesa, and a loader whose backends are Mesa's own. +# The versions are the upstream projects' own release numbers, and the shapes +# differ on one criterion: a library is BUILT FROM SOURCE when upstream ships it +# as a separable unit, and BOUND to the ecosystem's payload when it is an +# internal target of a project the ecosystem already owns. libdrm and wayland +# pass that test; GBM does not — it is a build target inside Mesa, and a loader +# whose backends are Mesa's own. [target.'cfg(linux)'.dependencies.compat] -libdrm = "2.4.134" # source-built; the KMS side: modes, CRTCs, framebuffers -libgbm = "25.0.7" # Mesa's GBM: buffer allocation out of a DRM device -egl = "1.7.0" # libglvnd's EGL dispatch: rendering onto those buffers -wayland = "2026.08.30" # the display protocol, client and server +libdrm = "2.4.134" # source-built; the KMS side: modes, CRTCs, framebuffers +libgbm = "25.0.7" # Mesa's GBM: buffer allocation out of a DRM device +egl = "1.7.0" # libglvnd's EGL dispatch: rendering onto those buffers -# compat.wayland puts only -lwayland-client on the link line, because a -# dependency's ldflags reach every consumer with no way to opt out. A -# compositor asks for the server library itself; it resolves out of the same -# package. -[target.'cfg(linux)'.build] -ldflags = ["-lwayland-server"] +# wayland is source-built too, out of mcpplibs/wayland — the client and the +# server are distinct SONAMEs that Mesa's libEGL_mesa needs BOTH of, so they are +# two packages rather than one with an ldflags escape hatch. Each also ships a +# C++23 module wrapper; this example uses the headers, and 09's sibling text +# explains what `import wayland.client;` would change (nothing but the include). +[target.'cfg(linux)'.dependencies.freedesktop] +wayland = "1.26.0" +wayland-server = "1.26.0" From d68ded64264c00fea3dd07f37c2d8c27a08d9cbf Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 12:30:18 +0800 Subject: [PATCH 5/6] 09: EGL is source-built now, so four of the five packages are compat.egl bound xim:libglvnd, and its own comment recorded why that was wrong: libglvnd IS a separable project, so by the criterion it should have been a source build; it stayed a binding for effort alone. It is now freedesktop.egl, out of mcpplibs/libglvnd, and GBM is the only binding left in this example -- which is the honest picture, since GBM is the one that genuinely fails the test. That package also carries libGLdispatch.so.0, as a sibling workspace member reached by a path dependency rather than a second index entry, because being the one dispatch point in a process is what GLVND is for. The program now prints __EGL_VENDOR_LIBRARY_DIRS beside GBM_BACKENDS_PATH. Both loaders in this stack dlopen something the environment has to point them at, and the EGL package compiles in an EMPTY default rather than upstream's: a wrong compiled-in path is worse than none, because it would make a missing declaration load the HOST's driver into a sandboxed process, silently and successfully. Measured end to end, with every library attributed through the binary's own loader -- libEGL.so.1, libGLdispatch.so.0, libdrm.so.2 and both libwayland libraries come from this project's build output, libgbm.so.1 from compat-x-libgbm (the binding), and nothing from a host path. eglInitialize reaches "EGL 1.5, vendor Mesa Project" through the source-built dispatch. --- examples/09-graphics-stack/README.md | 53 ++++++++++++++++++------- examples/09-graphics-stack/mcpp.toml | 35 +++++++++++----- examples/09-graphics-stack/src/main.cpp | 21 ++++++---- 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/examples/09-graphics-stack/README.md b/examples/09-graphics-stack/README.md index 6f92f03e..192b5c34 100644 --- a/examples/09-graphics-stack/README.md +++ b/examples/09-graphics-stack/README.md @@ -9,7 +9,8 @@ mcpp run ``` == the graphics stack, resolved from the index == - GBM_BACKENDS_PATH = …/subos/default/usr/lib/gbm + GBM_BACKENDS_PATH = …/subos/default/usr/lib/gbm + __EGL_VENDOR_LIBRARY_DIRS = …/subos/default/share/glvnd/egl_vendor.d wl_display_create 0x3f798be0 -- DRM node -> GBM device -> EGL display -- /dev/dri/renderD128 @@ -123,10 +124,13 @@ the ecosystem already owns, where building it would mean forking that project. |---|---|---| | `compat.libdrm` | source | `drmModeGetResources`, `drmModeAddFB2`, `drmModeSetCrtc` — the KMS side | | `compat.libgbm` | binds `xim:mesa` | `gbm_create_device`, `gbm_bo_create` — buffers out of a DRM device | -| `compat.egl` | binds `xim:libglvnd` | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them | +| `freedesktop.egl` | source | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them, and `import egl;` | | `freedesktop.wayland` | source | `libwayland-client.so.0`, and `import wayland.client;` | | `freedesktop.wayland-server` | source | `libwayland-server.so.0`, and `import wayland.server;` | +**Four of the five are source builds.** GBM is the only binding, and the +paragraphs below are about why the line falls where it does. + libdrm passes the test — an independent freedesktop project with its own releases — so it is compiled here, five translation units with no dependencies at all. GBM fails it: `src/gbm/meson.build` is `link_with: [libloader]`, and @@ -152,9 +156,20 @@ through it. That only holds because the package builds a *shared* library with the canonical soname; merged into the consumer as objects there would be two copies of libdrm's internal state over one set of file descriptors. -`compat.egl` is a binding for a duller reason: libglvnd IS separable, but -`libEGL.so` also needs its Python-generated dispatch stubs, `winsys_dispatch` -and the whole of `libGLdispatch.so`, so it has not been done yet. +EGL used to be the exception that proved the criterion was being applied +loosely. It was a binding for a duller reason than GBM's — libglvnd IS +separable, but `libEGL.so` also needs its generated dispatch stubs, +`winsys_dispatch` and the whole of `libGLdispatch.so`, so it simply had not +been done. "Not done yet" is not a shape, so it is now a source build out of +[mcpplibs/libglvnd](https://github.com/mcpplibs/libglvnd), and the dispatch +tables upstream generates with ~1000 lines of Python are checked into that fork +and diffed by its CI rather than regenerated during your build. + +That package also carries `libGLdispatch.so.0`, as a **sibling workspace member +reached by a path dependency** rather than as a second index entry. GLVND +exists to be the one dispatch point in a process; two index entries would let a +project name both and resolve two instances, and — by the same soname-reuse +rule as above — one would be mapped and the other silently discarded. One honest gap, since "Mesa/Vulkan" usually get named together: Vulkan is not part of this example and is not in the same state. `compat.vulkan-runtime` @@ -164,15 +179,25 @@ to bind to yet. The GBM/KMS/EGL/Wayland stack above has no such edge. ## Two things worth knowing -**`GBM_BACKENDS_PATH` is not set by any of these packages.** libgbm is a -loader: `gbm_create_device()` dlopens `/_gbm.so`, and the path -Mesa compiles in is `/usr/lib/gbm` — correct on a distribution, wrong the -moment the payload lives anywhere else. Setting that variable is Mesa's own -mechanism and the *environment's* job, which is where every relocated stack -puts it (Valve's pressure-vessel, Nix, Conda all do exactly this). Here -`xim:mesa` declares it into the SubOS and mcpp carries SubOS declarations into -the processes it launches, so it is simply already set — which is why the -program prints it rather than computing it. +**Neither `GBM_BACKENDS_PATH` nor `__EGL_VENDOR_LIBRARY_DIRS` is set by any of +these packages**, and both are needed, because two of the five are *loaders*. +`gbm_create_device()` dlopens `/_gbm.so`; `eglInitialize()` +dlopens whatever a JSON file in the vendor directory names. The paths upstream +compiles in — `/usr/lib/gbm`, `/share/glvnd/egl_vendor.d` — are correct +on a distribution and wrong the moment the payload lives anywhere else. + +Those two variables are Mesa's and GLVND's own mechanisms, and setting them is +the *environment's* job, which is where every relocated stack puts it (Valve's +pressure-vessel, Nix, Conda all do exactly this). Here `xim:mesa` declares both +into the SubOS and mcpp carries SubOS declarations into the processes it +launches, so they are simply already set — which is why the program prints them +rather than computing them. + +`freedesktop.egl` goes one step further and compiles in an **empty** default +rather than upstream's. A wrong compiled-in path is worse than no path: it +would make a missing declaration load the *host's* driver into a sandboxed +process, silently and successfully. Empty makes the same situation say "no +vendor found". **The wayland client and server are separate packages**, and this example asks for both because it creates a `wl_display` on the server side. That is not a diff --git a/examples/09-graphics-stack/mcpp.toml b/examples/09-graphics-stack/mcpp.toml index 4af92ccf..fc038603 100644 --- a/examples/09-graphics-stack/mcpp.toml +++ b/examples/09-graphics-stack/mcpp.toml @@ -8,19 +8,36 @@ standard = "c++23" # The versions are the upstream projects' own release numbers, and the shapes # differ on one criterion: a library is BUILT FROM SOURCE when upstream ships it # as a separable unit, and BOUND to the ecosystem's payload when it is an -# internal target of a project the ecosystem already owns. libdrm and wayland -# pass that test; GBM does not — it is a build target inside Mesa, and a loader -# whose backends are Mesa's own. +# internal target of a project the ecosystem already owns. +# +# Exactly ONE of the four is a binding, and it is GBM: it is a build target +# inside Mesa (`src/gbm/meson.build` is `link_with: [libloader]`, which wants +# ~120 TUs of Mesa's internal util library for one function), and it is a +# LOADER whose backends are Mesa's own `dri_gbm.so` — built apart from Mesa it +# would have nothing to load. libdrm, EGL and wayland all pass the test and are +# built from source. [target.'cfg(linux)'.dependencies.compat] libdrm = "2.4.134" # source-built; the KMS side: modes, CRTCs, framebuffers libgbm = "25.0.7" # Mesa's GBM: buffer allocation out of a DRM device -egl = "1.7.0" # libglvnd's EGL dispatch: rendering onto those buffers -# wayland is source-built too, out of mcpplibs/wayland — the client and the -# server are distinct SONAMEs that Mesa's libEGL_mesa needs BOTH of, so they are -# two packages rather than one with an ldflags escape hatch. Each also ships a -# C++23 module wrapper; this example uses the headers, and 09's sibling text -# explains what `import wayland.client;` would change (nothing but the include). +# These three are source builds out of forks that add mcpp support and patch no +# upstream file. +# +# wayland is two packages rather than one with an ldflags escape hatch: the +# client and the server are distinct SONAMEs that Mesa's libEGL_mesa needs BOTH +# of, and mcpp links every library target against all of a package's sources. +# +# egl is libglvnd's vendor-neutral dispatch — the piece that makes GBM useful +# for RENDERING rather than only for allocation. It also carries +# `libGLdispatch.so.0`, as a sibling workspace member reached by a path +# dependency rather than as a second index entry, because being the ONE dispatch +# point in a process is what GLVND is for. +# +# All three ship a C++23 module wrapper too. This example uses the headers, and +# for a reason worth knowing: `EGL_PLATFORM_GBM_KHR` and friends are MACROS, and +# no module can export a macro — so `import egl;` replaces the declarations but +# never the `#include ` that the constants come from. [target.'cfg(linux)'.dependencies.freedesktop] wayland = "1.26.0" wayland-server = "1.26.0" +egl = "1.7.0" diff --git a/examples/09-graphics-stack/src/main.cpp b/examples/09-graphics-stack/src/main.cpp index 9ab0be05..18350dbc 100644 --- a/examples/09-graphics-stack/src/main.cpp +++ b/examples/09-graphics-stack/src/main.cpp @@ -10,7 +10,7 @@ #include // compat.libdrm #include #include -#include // compat.egl +#include // freedesktop.egl #include #include // compat.wayland #include @@ -43,13 +43,18 @@ int main() { std::puts("== the graphics stack, resolved from the index =="); - // Where the GBM backends are found. Nothing in this program and nothing in - // compat.libgbm sets this: `xim:mesa` declares it into the SubOS through - // the graphics discovery layer, and mcpp carries SubOS declarations into - // the processes it launches. - const char *backends = std::getenv("GBM_BACKENDS_PATH"); - std::printf(" GBM_BACKENDS_PATH = %s\n", - backends ? backends : ""); + // Where the two LOADERS in this stack find what they dlopen. Nothing in + // this program and none of the packages sets either: `xim:mesa` declares + // both into the SubOS through the graphics discovery layer, and mcpp + // carries SubOS declarations into the processes it launches. + // + // GBM_BACKENDS_PATH -> gbm_create_device() dlopens /_gbm.so + // __EGL_VENDOR_LIBRARY_DIRS -> eglInitialize() dlopens what a JSON there names + for (const char *name : {"GBM_BACKENDS_PATH", "__EGL_VENDOR_LIBRARY_DIRS"}) { + const char *value = std::getenv(name); + std::printf(" %-25s = %s\n", name, + value ? value : ""); + } // Wayland: build a server-side display. No socket is bound, so this needs // no session and no privileges — the cheapest proof the library is live. From 429f6bbaa24c827f718d54a94b9fe04d4403d79a Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 30 Aug 2026 13:08:11 +0800 Subject: [PATCH 6/6] 09: the modules are named for whoever owns the interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit import khronos.egl / import freedesktop.wayland.{client,server}. A module name is global and permanent in a way a package name is not, so it names the INTERFACE's owner: freedesktop owns the wayland protocol, Khronos owns EGL and libglvnd merely implements it. That is why freedesktop.egl the PACKAGE exports khronos.egl the MODULE, and the mismatch is deliberate. The closure listing was re-measured rather than edited: libEGL.so.1 and libGLdispatch.so.0 now come from this build too, where before they came from compat-x-egl and the xim:libglvnd payload. Also records that the same program was built and run from scratch inside `xlings subos use … --sandbox --gpu`, where /usr is still the host's and its libEGL/libgbm/libdrm/libwayland are all present and reachable -- and lost anyway. "Nothing from the host" is a claim about what WINS, not about what exists, and only the second version of that claim matches a user's machine. --- examples/09-graphics-stack/README.md | 36 ++++++++++++++++++++-------- examples/09-graphics-stack/mcpp.toml | 5 +++- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/examples/09-graphics-stack/README.md b/examples/09-graphics-stack/README.md index 192b5c34..16c33580 100644 --- a/examples/09-graphics-stack/README.md +++ b/examples/09-graphics-stack/README.md @@ -53,9 +53,9 @@ answer: the whole chain, done the recommended way, declaring dependencies. [target.'cfg(linux)'.dependencies.compat] libdrm = "2.4.134" libgbm = "25.0.7" -egl = "1.7.0" [target.'cfg(linux)'.dependencies.freedesktop] +egl = "1.7.0" wayland = "1.26.0" wayland-server = "1.26.0" ``` @@ -85,37 +85,53 @@ BIN=target/x86_64-linux-gnu/*/bin/graphics-stack ``` /bin/libdrm.so.2 <- built by this build +/bin/libEGL.so.1 <- built by this build +/bin/libGLdispatch.so.0 <- built by this build /bin/libwayland-client.so.0 <- built by this build /bin/libwayland-server.so.0 <- built by this build /bin/libffi.so.8 <- built by this build compat-x-libgbm/25.0.7/…/libgbm.so.1 -compat-x-egl/1.7.0/…/libEGL.so.1 xim-x-expat/2.6.2/lib/libexpat.so.1 xim-x-gcc/16.1.0/lib64/libgcc_s.so.1 xim-x-gcc/16.1.0/lib64/libstdc++.so.6 xim-x-glibc/2.44/lib64/libc.so.6 xim-x-glibc/2.44/lib64/libm.so.6 -xim-x-libglvnd/1.7.0.1/lib/libGLdispatch.so.0 ``` -Nothing is under `/usr/lib` or `/lib64`. Two things there are worth reading +Nothing is under `/usr/lib` or `/lib64`. Three things there are worth reading closely. -**The first four lines.** They are this project's own build output, not the +**The first six lines.** They are this project's own build output, not the ecosystem's copies — including `libdrm.so.2`, even though Mesa's `libgbm.so.1` has a DT_NEEDED on that soname and an absolute RUNPATH into the payload. The consumer links them directly, so they are mapped first, and Mesa binds to them: the `gbm_bo_create` above ran through this libdrm. +**`libEGL.so.1` and `libGLdispatch.so.0` are on that list.** The ecosystem's +`xim:libglvnd` carries both under the same sonames, and it is installed on this +machine — but only one library per soname is ever mapped and nothing warns +about the loser, so "EGL worked" is not evidence that *this* EGL worked. The +index's test member pins it from the other direction with `dladdr`, and this +example prints `EGL 1.5, vendor Mesa Project` through the build above. + **`libffi` and `libGLdispatch`.** Nothing in `mcpp.toml` names either. `libffi.so.8` is what `libwayland-client` dispatches protocol messages through, `libGLdispatch` is what libEGL's vendor dispatch needs — the cascade a directly linked library pulls behind it, which is exactly what a host `-L/usr/lib` cannot resolve from inside a private loader. +**Verified with the host present, not absent.** The same program was built and +run from scratch inside `xlings subos use … --sandbox --gpu`, in a synthetic +home where none of this machine's checkouts or caches reach — and where `/usr` +is still the host's, so `/usr/lib/x86_64-linux-gnu/libEGL.so.1`, +`libgbm.so.1`, `libdrm.so.2` and `libwayland-client.so.0` are all present and +reachable. Every graphics library still resolved to the project's own output or +the ecosystem payload. "Nothing from the host" is a claim about what *wins*, +not about what exists. + ## The packages -Three are built from source and two bind the ecosystem's Mesa, and the split is +Four are built from source and one binds the ecosystem's Mesa, and the split is not arbitrary. A library is built from source when upstream ships it as a **separable unit**; it is bound when it is an internal build target of a project the ecosystem already owns, where building it would mean forking that project. @@ -124,9 +140,9 @@ the ecosystem already owns, where building it would mean forking that project. |---|---|---| | `compat.libdrm` | source | `drmModeGetResources`, `drmModeAddFB2`, `drmModeSetCrtc` — the KMS side | | `compat.libgbm` | binds `xim:mesa` | `gbm_create_device`, `gbm_bo_create` — buffers out of a DRM device | -| `freedesktop.egl` | source | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them, and `import egl;` | -| `freedesktop.wayland` | source | `libwayland-client.so.0`, and `import wayland.client;` | -| `freedesktop.wayland-server` | source | `libwayland-server.so.0`, and `import wayland.server;` | +| `freedesktop.egl` | source | `eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, …)` — rendering onto them, and `import khronos.egl;` | +| `freedesktop.wayland` | source | `libwayland-client.so.0`, and `import freedesktop.wayland.client;` | +| `freedesktop.wayland-server` | source | `libwayland-server.so.0`, and `import freedesktop.wayland.server;` | **Four of the five are source builds.** GBM is the only binding, and the paragraphs below are about why the line falls where it does. @@ -207,7 +223,7 @@ package's sources — so one package cannot emit two libraries with disjoint contents. A client-only program drops the second line and links only `libwayland-client.so.0`. -Both also ship a C++23 module wrapper. `import wayland.client;` in place of +Both also ship a C++23 module wrapper. `import freedesktop.wayland.client;` in place of `#include ` changes nothing else — every exported name is upstream's, spelled upstream's way — so this file could switch one line at a time. It uses the headers here because that is what a ported project looks like diff --git a/examples/09-graphics-stack/mcpp.toml b/examples/09-graphics-stack/mcpp.toml index fc038603..479ffb7c 100644 --- a/examples/09-graphics-stack/mcpp.toml +++ b/examples/09-graphics-stack/mcpp.toml @@ -1,3 +1,6 @@ +[indices] +freedesktop = { path = "/home/speak/workspace/github/mcpplibs/mcpp-index" } + [package] name = "graphics-stack" version = "0.1.0" @@ -35,7 +38,7 @@ libgbm = "25.0.7" # Mesa's GBM: buffer allocation out of a DRM device # # All three ship a C++23 module wrapper too. This example uses the headers, and # for a reason worth knowing: `EGL_PLATFORM_GBM_KHR` and friends are MACROS, and -# no module can export a macro — so `import egl;` replaces the declarations but +# no module can export a macro — so `import khronos.egl;` replaces the declarations but # never the `#include ` that the constants come from. [target.'cfg(linux)'.dependencies.freedesktop] wayland = "1.26.0"