Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 130 additions & 11 deletions .agents/docs/2026-08-30-graphics-stack-coverage-design.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# mcpp 图形栈:从「能跑通」到「能开发」的覆盖面设计

Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.4,§11 总账 / §12 客户端侧 / §14 fork 规范 / §17 桌面栈补齐与判据修正)**
Date: 2026-08-30 · 前置:[`2026-08-30-gbm-cross-repo-closed-loop-plan.md`](2026-08-30-gbm-cross-repo-closed-loop-plan.md) §19/§20 · **状态:已实现并闭环验证(v1.5,§11 总账 / §14 fork 规范 / §17 桌面栈补齐 / §18 八角度实现后复核)**

## 0. 这份文档解决什么

Expand Down Expand Up @@ -349,16 +349,19 @@ G6 libudev 路线决策 → libseat ← 最后,单独评估

## 6. 多角度评估

| 角度 | 评估 |
|---|---|
| **架构** | G1 不新建仓,复用 mcpplibs/libglvnd 已有的 `mcpp/generated/` 与 path 依赖机制;G3 复用 mcpplibs/wayland 的 scanner。**新增仓数量 = 0** |
| **稳定性** | 四张生成表已实跑;CI 的 diff 守卫扩四行即可。风险集中在 G1b(模块导出)与 G3(体积),两者都可先验 |
| **优雅/简洁** | 只做三个无 X11 的 GL 库,不碰 GLX/GL —— 覆盖合成器的全部需要,且不把 X11 拖进任何消费者 |
| **用户体验** | 合成器作者的 `mcpp.toml` 从「缺渲染链」变成可写;`import khronos.glesv2;` 与既有命名一致 |
| **兼容性** | 全部是新增条目,不动任何已发布包。唯一的行为变更是 G2a,而它修的是「静默落到软件渲染」 |
| **跨平台** | GL 家族的 per-arch entry stub 沿用 `build.mcpp` 里已有的 `target_arch()` 选择,aarch64/ppc64 由构造成立;pixman 的 SIMD 自门控,不需要额外机制 |
| **一致性** | 模块命名(khronos.*)、生成物签进仓、path 依赖三条规则原样复用,不引入新范式 |
| **无感升级** | 全是新增包,无同版本重切 tag 的问题(那正是上一轮的教训) |
> ⚠ **这张表是设计时写的,八条里有四条已被实现推翻或需要重述。**
> 每行末尾的标记给出结论,逐条的证据在 **§18**。不要单独引用本节。

| 角度 | 评估(设计时) | |
|---|---|---|
| **架构** | G1 不新建仓,复用 mcpplibs/libglvnd 已有的 `mcpp/generated/` 与 path 依赖机制;G3 复用 mcpplibs/wayland 的 scanner。**新增仓数量 = 0** | ❌ 实际五个仓,§18.1 |
| **稳定性** | 四张生成表已实跑;CI 的 diff 守卫扩四行即可。风险集中在 G1b(模块导出)与 G3(体积),两者都可先验 | ⚠ 真正的风险不在此,§18.2 |
| **优雅/简洁** | 只做三个无 X11 的 GL 库,不碰 GLX/GL —— 覆盖合成器的全部需要,且不把 X11 拖进任何消费者 | ✅ 且被 feature 加强,§18.3 |
| **用户体验** | 合成器作者的 `mcpp.toml` 从「缺渲染链」变成可写;`import khronos.glesv2;` 与既有命名一致 | ✅ 且多一层,§18.4 |
| **兼容性** | 全部是新增条目,不动任何已发布包。唯一的行为变更是 G2a,而它修的是「静默落到软件渲染」 | ❌ 动了三个,§18.5 |
| **跨平台** | GL 家族的 per-arch entry stub 沿用 `build.mcpp` 里已有的 `target_arch()` 选择,aarch64/ppc64 由构造成立;pixman 的 SIMD 自门控,不需要额外机制 | ⚠ 咬人的是编译器不是架构,§18.6 |
| **一致性** | 模块命名(khronos.*)、生成物签进仓、path 依赖三条规则原样复用,不引入新范式 | ⚠ 第二条已推翻,§18.7 |
| **无感升级** | 全是新增包,无同版本重切 tag 的问题(那正是上一轮的教训) | ⚠ 换三种形态出现,§18.8 |

---

Expand Down Expand Up @@ -1429,3 +1432,119 @@ RESULT: PASS
**pango 是唯一量准后仍需决策的**:57k 行本身不大,但硬依赖 glib,而 glib 是
`glib/` + `gobject/` + `gio/` 三大块 300k+ 行,自己还缺 `pcre2` 和 `libmount`。
按 §17.1 的新判据,它的难点也不在行数——需要先数它的生成器再定。

---

## 18. 八个角度的实现后复核

§6 的表是**设计时**写的。实现之后逐条回看,**八条里有四条被推翻或需要重述**——
把它们留在原样比不写更糟,因为下一个人会照着一张已经不成立的表做决定。

### 18.1 架构 —— ❌ 推翻

> 设计时:「不新建仓,**新增仓数量 = 0**」

实际新建 **五个** fork 仓:`wayland-protocols`、`libevdev`、`libxkbcommon`
(前三个见 §10.2)、`libdisplay-info`、`fontconfig`、`cairo`。

**错在哪**:当时以为「进已有 fork」是可选的组织方式。它不是——**一个 fork 仓对应
一个上游、一个版本**。wayland-protocols 有自己的版本号和发布周期,塞进
mcpplibs/wayland 就等于让两个上游共用一个 tag,那才是真正的架构错误。

**留下的规则**:fork 仓的数量由**上游的数量**决定,不由「想少建几个仓」决定。

### 18.2 稳定性 —— ⚠ 重述

设计时说「风险集中在模块导出与体积,两者都可先验」。体积确实可先验(cairo 47.8MB
→ 1.8MB,§17.4)。模块导出也确实是风险,但**真正咬人的不是它**。

实现期的失败按代价排序,前三名都不在当时的风险清单上:

1. **探测答案错**(§17.5、§17.6)——编过、链过、报 SUCCESS,行为错。三次。
2. **环境陈旧**(§17.7)——沙箱/CI/store 各一次,报错全部指向别处。
3. **上游自己的 `.gitignore`**(§17.6)——fork 少了 5 个发布物文件。

**留下的规则**:风险不在「难写的代码」里,在「写完之后没人检查的断言」里。所以
每个 fork 的 CI 都要有一条**拿产物和上游对照**的检查,而不只是「能编过」。

### 18.3 优雅/简洁 —— ✅ 成立,而且被 feature 机制加强

设计时的「只做三个无 X11 的 GL 库」成立。cairo 把同一个想法做到了更好的形态:
**不是替消费者选,而是让消费者选**——`default = ["ft","fc","png"]`,X11 是
feature(§17.4)。

差别是真实的:「只做无 X11 的库」意味着要 X11 的人没得用;feature 意味着他写一行
就有。

### 18.4 用户体验 —— ✅ 成立,且比预期多一层

`import khronos.glesv2;` 那条成立。多出来的一层是**模块消掉了上游的缺陷**:
libdisplay-info 七个公共头一个 `extern "C"` 都没有,C++ 消费者 `#include` 会
mangle 到链接失败;`import freedesktop.displayinfo;` 把包装做在模块内(§14.2)。

**留下的规则**:模块层不只是「换个写法」,它是**放置适配代码的位置**。

### 18.5 兼容性 —— ❌ 推翻

> 设计时:「全部是新增条目,**不动任何已发布包**」

实际动了三个已发布包:

- `compat.freetype` 补 `ftsynth.c`(#311)——cairo 无条件调它
- `xim:mesa` 改成只声明自己填的路径(xim#733)——加一行 DISCOVERY 造成的回归
- `graphics.consumer_envs` 跳过标量行(xim#735)——同一行造成的第二处回归

**错在哪**:当时把「新增」等同于「无风险」。**新增一个条目会改变现有条目的行为**
——DISCOVERY 表加一行,`declare_subos_env(tag)` 不传 `only` 的调用点就自动继承了
它;新增一个消费者,`compat.freetype` 的源码列表缺口就暴露了。

**留下的规则**:见 §14 的三项检查清单,以及「一个 compat 包的源码列表就是它的
ABI 承诺」(§17.6)。

### 18.6 跨平台 —— ⚠ 重述

设计时只考虑了 CPU 架构(aarch64/ppc64)。实现期真正咬人的跨平台维度是
**编译器与标准库**:

- `wl_shm_create_pool` 是 `static inline`,取地址强制实例化 → GNU ld 经传递
DT_NEEDED 解析,**lld 不会**(#301)
- `<sstream>` 被 libstdc++ 经 `<fstream>` 传递包含,**libc++ 不会**(#310)

两个都只有 mcpp-index 的 **llvm 腿**抓得到——它没有 sysroot 且用 lld。

**留下的规则**:fork 里任何 `build.mcpp` 或模块包装的改动,推之前先
`mcpp toolchain default llvm` 跑一遍。cairo 的 fork CI 已经把这一步固化。

### 18.7 一致性 —— ⚠ 重述

三条规则(模块命名、生成物签进仓、path 依赖)里,**第二条被推翻**:生成物不再
签进仓,改由 `build.mcpp` 在构建期产出(§14.1)。

这不是风格变更,是消掉了一整类状态:**没有签进仓的生成物,就没有「数据与代码不
一致」这个状态**,也就不需要「重生成再 diff」的 CI 步骤。

另外两条原样成立,并新增一条:**feature 的 `sources` 逐条列,不用 glob**
(§17.4)。

### 18.8 无感升级 —— ⚠ 重述

> 设计时:「全是新增包,无同版本重切 tag 的问题」

同版本重发的问题**换了三种形态出现**:

| 形态 | 后果 |
|---|---|
| tag archive 做 URL | 重切直接打断已发布描述符的 sha256 |
| store 按 `(name, version)` 缓存 | 同版本重发**不重新解压** |
| 删 tag 再建 | release 掉成**草稿**,资产 404 |

三个都指向同一条,已成为本轮的操作规则:**tag 指向历史,资产承载分发,不要动
tag**——把新内容作为 release 资产发布,旧资产原样保留,零窗口(§12.2)。

### 18.9 一句话

八条里**两条推翻**(架构、兼容性)、**四条重述**(稳定性、跨平台、一致性、无感
升级)、**两条成立且被加强**(优雅、用户体验)。

设计时的判断有一半没扛过实现——而这正是「验证要更新到文档」的意义:留下的应该是
**被证伪之后的那一版**。