waypipe + weston 全屏乱码排查:一次被现象带偏的过程

七月 08, 2026 [linux] #waypipe #weston #wayland #fontconfig #debug

问题现象

通过 waypipe 把远程主机的 Wayland 应用转发到本地显示:

waypipe ssh peter@dev weston

结果整个 Weston 窗口(包括标题栏和内部终端)所有文字都是整齐的方块(tofu),连 ASCII 字符都无法显示。

关键对照:

这两个对照信息看似缩小了范围,实际上把排查带偏了好几次。本文如实记录整个过程,重点是每一步用什么工具、看什么输出、为什么某个结论是错的

排查前的基础信息收集

第一步永远是确认版本和环境,不要凭记忆。

# 本地
waypipe --version    # -> waypipe 0.8.6

# 两台服务器
ssh dev    "waypipe --version; weston --version"   # -> 0.9.2 / weston 14.0.2
ssh douhao "waypipe --version; weston --version"   # -> 0.9.2 / weston 14.0.2

误判一:以为是字体缺失

最初的直觉:方块 = 缺字体。于是去数远程的字体:

ssh dev "fc-list | wc -l"     # -> 168
ssh dev "fc-list | grep -iE 'mono|dejavu|noto'"
# /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf: DejaVu Sans Mono ...
# /usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf ...

fc-list 列出系统所有已注册字体。dev 上有 168 个字体,DejaVu Sans Mono、Noto 一应俱全。

结论:字体文件不缺。「缺字体」被排除。

这里其实已经埋下伏笔——fc-list 能列出字体,不代表字体查询系统本身是健康的。但当时没意识到。

误判二:以为是 GPU DMABUF 传输问题

既然字体不缺,转去看 dev 和 douhao 的硬件差异:

ssh dev    "lspci -nn | grep -iE 'vga|3d'; ls -l /dev/dri"
ssh douhao "lspci -nn | grep -iE 'vga|3d'; ls -l /dev/dri"

发现一个「重大差异」:

GPU缓冲区类型
dev(乱码)真实 Intel 显卡 (i915/iris, renderD128)硬件 tiled/带 modifier 的 DMABUF
douhao(正常)virtio-gpu(虚拟机)线性 (linear) 缓冲区

推理链看起来很完美:Weston 在 dev 上用 GL 渲染器输出 Intel 平铺格式的显存缓冲区,waypipe 通过网络传这些 GPU buffer 时 tiling modifier 没正确还原 → 乱码;douhao 是虚拟 GPU、线性缓冲,所以正常。

于是给出三个方案(对应 waypipe --help 里的选项):

waypipe ssh peter@dev weston --renderer=pixman   # 让 weston 用 CPU 软件渲染,输出线性缓冲
waypipe --no-gpu ssh peter@dev weston            # 让 waypipe 不转发 GPU dmabuf
waypipe --allow-tiled ssh peter@dev weston       # 允许带 modifier 的 tiled 缓冲

结果三个全部还是乱码。

--no-gpu 走的是纯 SHM 内存缓冲路径,理论上完全绕开 GPU。它都乱码,说明根因不在 GPU / DMABUF 传输。GPU 差异理论被推翻。

教训:一个「显著差异」不等于「根因」。dev 和 douhao 有很多不同点,GPU 只是最扎眼的那个。

误判三:以为是 waypipe 版本不匹配

--debug 抓真实日志,这是转折点:

waypipe --debug ssh peter@dev "weston --renderer=pixman" 2>&1 | head -60

日志里跳出版本号:

c2951318: ... version: 0.8.6      <- 本地 client
s747338:  ... version: 0.9.2      <- 远程 server

waypipe 0.8.x 和 0.9.x 之间线协议(wire protocol)确实不保证兼容,看起来是铁证。

但对照 douhao 时立刻自相矛盾:

waypipe --version                       # 本地 0.8.6
ssh dev    "waypipe --version"          # 0.9.2
ssh douhao "waypipe --version"          # 0.9.2
ssh dev    "ls -l \$(command -v waypipe)"    # 236056 字节
ssh douhao "ls -l \$(command -v waypipe)"    # 236056 字节,完全一致
连接本地 client远程 server结果
dev0.8.60.9.2乱码
douhao0.8.60.9.2正常

两条连接是完全相同的 0.8.6 ↔ 0.9.2 组合。如果纯粹是版本协议不兼容,douhao 也该乱码。版本不匹配被推翻。

顺手也排除了 CPU/SIMD 差异(waypipe 用手写 AVX2/AVX512 内核做缓冲区 diff):

grep -m1 flags /proc/cpuinfo | grep -oE 'avx512[a-z]*|avx2'   # 三方都查

结果本地是 i7-11700T(有 avx512),两台服务器都是 i7-14700K(相同,avx2、无 avx512)。两台服务器 CPU 完全一样,SIMD 路径不是差异点。

至此,dev 和 douhao 在版本、CPU、Mesa、字体文件上全都相同,硬件 GPU 差异又已被 --no-gpu 排除。必须换思路。

定位根因:对比 fontconfig 匹配结果

回到最朴素的观察:乱码是整齐的方块(tofu),不是彩色雪花噪点。

现象一直在说「字体」,只是我前面把「字体文件存在」错当成了「字体系统正常」。直接对比两台的字体匹配行为(而不是字体列表):

for h in dev douhao; do
  echo "===== $h ====="
  ssh $h "fc-match monospace; fc-match sans-serif; fc-cache -v 2>&1 | grep -iE 'invalid|成功'"
done

fc-match <类别> 才是关键工具——它模拟应用程序实际拿字体的过程,回答「monospace 这个别名最终解析到哪个字体文件」。输出对比:

fc-match monospacefc-cache 状态
devDroid Sans Fallback⚠️ invalid cache file: 21a99156...-le64.cache-9
douhaoNoto Sans Mono CJK SC正常

两个问题同时暴露:

  1. dev 的 fontconfig 缓存损坏invalid cache file,douhao 没有这行。
  2. dev 默认 monospace 解析到了 CJK 覆盖不全的 Droid Sans Fallback,douhao 是完整的 Noto CJK

修复

# 在 dev 上执行
rm -rf ~/.cache/fontconfig            # 删除损坏的缓存
sudo apt install fonts-noto-cjk fonts-noto-mono   # 补齐和 douhao 一致的字体
fc-cache -fv                          # 重建缓存
fc-match monospace                    # 验证 -> Noto Sans Mono CJK SC

验证:

ssh dev "fc-match monospace; fc-cache -v 2>&1 | grep -iE 'invalid|成功'"
# NotoSansCJK-Regular.ttc: "Noto Sans Mono CJK SC" "Regular"
# fc-cache: 缓存生成成功          <- 不再有 invalid

之后裸命令 waypipe ssh peter@dev weston 恢复正常,不需要任何 --no-gpu / --renderer 参数。

根因解释:为什么连 ASCII 都变方块?

这是最反直觉的一点,也是最值得记住的:

ASCII 字形任何西文字体都有,「monospace 匹配到覆盖不全的字体」根本不该让 ASCII 也变方块。

真正让 ASCII 也乱码的是另一半原因:fontconfig 缓存损坏。渲染链路是:

weston-terminal  ->  Cairo / Pango  ->  fontconfig  ->  FreeType
                                          (查询)        (取字形)

所以现象才是「连 ASCII 都是方块」:不是字体文件缺失,是字体查询系统整体挂了。

两个原因各自的作用要分清:

原因造成的现象修复手段
fontconfig 缓存损坏连 ASCII 都变方块(根因)rm -rf ~/.cache/fontconfig && fc-cache -fv
默认字体是 Droid Sans Fallback中文提示符覆盖不全安装 fonts-noto-cjk

真正治好 ASCII 方块的是删缓存重建那一步;装 Noto CJK 只是顺带把中文显示也补全了。

为什么直接 ssh dev 就没事?

因为渲染位置不同:

推论:本地直连 dev 跑 GUI 也会乱码吗?

会。判断标准始终是**「谁在画字、用哪台机器的 fontconfig」**,跟 waypipe / 网络无关。

如果直接坐在 dev 前(或本地登录 dev)用它的显示器跑 GUI 程序(weston、weston-terminal 等),字形渲染就在 dev 上、走 dev 的 fontconfig 完成,同样会命中那份损坏的缓存 → 一样的方块。

有一个前提要注意:损坏的是用户 peter~/.cache/fontconfig(用户级缓存)。所以精确说法是:

这也反过来印证了前一节:ssh dev 用本地终端不乱码,是因为渲染在本机、用本机 fontconfig,dev 的坏缓存根本没参与。

经验总结

  1. 让现象说话,别让「显著差异」抢戏。 整齐方块(tofu)= 字形缺失,彩色噪点 = 缓冲区损坏。一开始就该抓住这个区别,而不是先盯着 GPU。
  2. 对照组是双刃剑。 「douhao 正常」帮我推翻了版本、CPU、GPU 理论,但也一度让我忽略「两台都有字体文件、差异只在匹配结果」这个更细的层面。用对照时要对比到行为fc-match),而不只是存在性fc-list)。
  3. --debug 尽早上。 waypipe/weston 的调试日志一句话就暴露了版本号、dmabuf 权限(renderD128: Permission denied,说明 dmabuf 早已被禁用)等关键事实,省去大量猜测。
  4. 区分「文件存在」和「系统可用」。 fc-list 能列出字体 ≠ fontconfig 能正常查询。缓存损坏是「文件都在但系统坏了」的典型。

工具速查

工具用途本次关键输出
waypipe --version确认两端版本排除/确认版本不匹配
waypipe --debug ...打印协议级日志暴露版本号、dmabuf 权限被拒
waypipe --help--no-gpu / --allow-tiled / --renderer尝试绕过 GPU
fc-list列出已安装字体(存在性)168 个,文件不缺
fc-match <类别>模拟应用取字体(匹配行为)定位到 Droid Sans Fallback / 缓存 invalid
fc-cache -fv重建字体缓存修复根因
lspci -nn / ls /dev/dri查 GPU 类型发现 Intel vs virtio(最终无关)
/proc/cpuinfo flags查 SIMD 指令集排除 AVX 差异