首页
/ Godot 的 Wayland Linux DMA-BUF Feedback 协议详解:主设备与分档(Tranche)机制如何协商最优缓冲分配

Godot 的 Wayland Linux DMA-BUF Feedback 协议详解:主设备与分档(Tranche)机制如何协商最优缓冲分配

2026-09-04 14:29:27作者:牧宁李

本文围绕 Godot 引擎仓库中的 linux-dmabuf feedback 文档 展开,系统讲解 Wayland 合成器(compositor)与客户端之间协商 DMA-BUF 缓冲分配参数的机制:主设备(main device)、分档(tranche)、format_table 编码与 dev_t 编码,并结合仓库内协议定义 linux-dmabuf-v1.xml 和 Godot 的构建/内嵌代码说明该协议在引擎中的落地位置。读完本文,你将能够:理解 feedback 事件流的完整结构与顺序约束、掌握客户端/合成器各自的推荐实现策略、看懂 dev_tformat_table 的二进制编码方式,并知道 Godot 在哪里从 XML 生成并注册了这套协议。

1. 协议定位:为什么需要 linux-dmabuf feedback

在 Linux 平台上,Wayland 客户端把 GPU 缓冲区交给合成器显示时,最常见的路径是 Linux DMA-BUF 协议(zwp_linux_dmabuf_v1)。在协议 v4 之前,合成器只会通过 format/modifier 事件广播一次“支持的格式列表”,客户端拿到列表后自行猜测在哪块设备、用哪种 format/modifier 分配缓冲——在多 GPU(渲染设备与 KMS 扫描输出设备分离)场景下,这种猜测往往次优:缓冲可能分配在私有显存中导致跨设备拷贝,或者分配出无法直接扫描输出的 layout,白白增加合成开销。

feedback 机制(协议 v4 引入)让合成器与客户端动态协商最优的缓冲分配参数。文档开头明确了适用前提:假设合成器使用 OpenGL 或 Vulkan 这类渲染 API 做合成、使用 KMS 做呈现——这不是协议的硬性限制,但这是最典型的场景。

在 Godot 仓库中,该协议定义文件就是 linux-dmabuf-v1.xml,其中 zwp_linux_dmabuf_feedback_v1 接口版本号为 5;Linux/BSD 平台的 Wayland 构建脚本 SCsub 通过 generate_from_xml("linux_dmabuf_v1", ...) 从该 XML 生成 linux_dmabuf_v1.gen.h 代码,wayland_embedder.h 则包含该生成头文件,并在 get_wayland_interfaces() 列表中同时注册了 zwp_linux_dmabuf_v1_interfacezwp_linux_dmabuf_feedback_v1_interfacewayland_embedder.h),说明 Godot 作为 Wayland 内嵌平台(编辑器远程调试、运行在 Wayland 下的主机等场景)完整实现了这套 feedback 接口。

2. 两个核心概念:main device 与 tranche

文档定义了整个 feedback 机制的两个基石概念:

2.1 主设备(main device)

主设备是合成器用于执行合成的渲染设备。由于合成器必须能显示客户端提交的任何缓冲,因此主设备天然是兜底路径:当所有优化路径都不可用时,缓冲总能被主设备导入并做成纹理。客户端应当以“缓冲可被主设备导入和纹理化”为底线来分配缓冲。

协议 XML 对 main_device 事件有进一步约束(linux-dmabuf-v1.xml):

  • 恰好只有一个主设备;合成器必须至少发送一个 tranche_target_device 等于 main_device 的 tranche;
  • 主设备通常是 DRM 节点,但节点类型(primary 还是 render node)不作保证,客户端不得依赖特定节点类型;
  • 客户端不能通过比较 dev_t 值判断两个设备是否相等;
  • 如果客户端在不支持显式 modifier 的情况下,于非主设备上分配缓冲,必须强制线性(linear)布局;
  • 主设备通常会被合成器保持活跃,客户端使用它还能避免唤醒另一块设备,从而省电。

2.2 分档(tranche)

一个 tranche 由三部分构成:目标设备(tranche_target_device)、分配标志(tranche_flags)、一组 format/modifier 对(tranche_formats)。可以把它理解为“与目标设备兼容的一组 format/modifier 对”。

tranche 可以携带 scanout 标志(tranche_flags 位域中 scanout = 1):表示目标设备是 KMS 设备,且用该 tranche 中的 format/modifier 对分配的缓冲有资格直接扫描输出(direct scanout)

tranche 按偏好降序发送,同一 tranche 内的所有 format/modifier 优先级相同。客户端应当按顺序逐个尝试:先用第一个能成功的 tranche 分配缓冲,既拿到最合适的 format/modifier,又能避免跨设备操作时误分配进私有设备内存。

3. 事件流:一次完整 feedback 的发送顺序

zwp_linux_dmabuf_feedback_v1 接口描述(linux-dmabuf-v1.xml)规定了参数何时、如何发送:

  • 参数在对象创建时发送一次,之后每次变化都全量重发(单个参数变化也会触发全部参数重发);
  • 每次参数集合结束后必定发送一个 done 事件,使多事件参数变更在客户端看来是原子的;
  • 参数序列固定为:format_tablemain_device → 若干 tranche(每个 tranche 依次是 1 个 tranche_target_device、1 个 tranche_flags、1 个或多个 tranche_formats、1 个 tranche_done)→ done

各事件的要点:

事件 载荷 说明
format_table fd + size(字节) 传入一个可内存映射的文件描述符,内容是紧凑排列的 format+modifier 对数组;客户端必须以只读私有方式映射;合成器发送后不得修改表文件内容,需变更时必须新建表文件并整体重发 feedback;表内允许重复对
main_device device(数组) 携带 dev_t 值(数组编码,见第 5 节)
tranche_target_device device(数组) 该 tranche 的目标设备;可能是扫描输出设备(合成器偏好直接扫描输出),也可能是渲染设备(合成器偏好对其做纹理)
tranche_flags flags(uint 位域) 目前只有 scanout = 1;它是提示:若客户端恰当地分配了缓冲,合成器可能在目标设备上尝试直接扫描输出
tranche_formats indices(数组) 一组 16 位无符号索引(本机字节序),指向最近一次收到的 format_table 中的 format+modifier 对
tranche_done 标志一个 tranche 结束;下一个 tranche 优先级更低
done 本轮所有参数发送完毕

两个实现上容易踩坑的细节:

  1. DRM_FORMAT_MOD_INVALID 的遗留语义tranche_formats 允许携带该 modifier(modifier_hi == 0x00ffffffmodifier_lo == 0xffffffff),表示合成器支持该格式且使用隐式 modifier——有效 modifier 从 dmabuf 自身推导。同时发送有效 modifier 和 DRM_FORMAT_MOD_INVALID 的合成器意味着它同时支持显式与隐式 modifier。
  2. 去重约束:合成器不得在同一个 tranche 内、或跨两个具有相同目标设备和 flags 的 tranche 之间,发送重复的 format+modifier 对。

此外,合成器不应无意义地重发相同参数:如果重新分配缓冲不会得到更优的配置,就不要重发;尤其要避免连续多次发送完全相同的参数。

4. 客户端实现指南

文档给出的客户端推荐流程按能力分三个层次,核心思路完全一致:按 tranche 优先级逐档尝试分配(必要时并验证导入)

4.1 基础客户端(仅支持启动时静态分配)

  1. 发送 get_default_feedback 请求获取全局 feedback;
  2. 选择 main_device 事件指定的设备作为分配设备;
  3. 逐个遍历 tranche:
    • tranche_target_device 与分配设备不一致,跳过该 tranche;
    • tranche_flags 累积分配标志;
    • tranche_formats 收到的 format/modifier 对累加进一个列表;
    • 收到 tranche_done 后,用累计的 modifier 列表与分配标志尝试分配缓冲;失败则进入下一个 tranche,成功则结束循环;
  4. 销毁 feedback 对象。

由于 tranche 按偏好降序排列,客户端应当使用第一个恰好可用的 tranche。

4.2 已预先选定设备的客户端

部分客户端(如 Godot 这类自己管理 GPU 设备的引擎)会事先选定渲染设备。这类客户端可以忽略 main_device 事件,并忽略所有 tranche_target_device 与选定设备不匹配的 tranche。代价是必须准备好 wp_linux_buffer_params.create 请求可能失败的兜底逻辑。

4.3 隐式 modifier 的线性布局约束

文档强调了一条硬性规则:如果客户端在不同于 main_device 的设备上分配缓冲且未指定显式 modifier,则必须强制线性布局(协议 XML 在 main_device 事件描述中同样重申了这一点)。否则缓冲可能带着分配设备私有的非线性 tiling,导入主设备时被误读为线性,产生花屏。

4.4 支持运行时重协商的客户端

支持动态更换 format/modifier 的客户端应改用 get_surface_feedback(绑定到具体 wl_surface,若 surface 先于 feedback 对象销毁,feedback 对象变为惰性),并在首次分配后保活 feedback 对象。每次收到新的一组参数(以 done 事件结尾)就重复基础客户端的步骤,并且要检测最优分配参数是否真的变化了(相同 format/modifier/flags),避免无谓地重新分配缓冲。

4.5 支持运行时切换分配设备的客户端

更强一层的客户端还可以动态切换分配设备。对每个 tranche:选择 tranche_target_device 指示的设备做分配,照旧累加标志与 format/modifier 对,收到 tranche_done 后分配缓冲,并发送 wp_linux_buffer_params.create 验证导入(这一步可能失败);重复直至某个 tranche 的“分配 + 导入”都成功。每次收到新参数时重复上述过程,同样要检测 device/format/modifier/flags 是否变化以跳过无效重分配。

5. 合成器实现指南

文档按合成器能力递增给出了五种情形:

  1. 基础合成器(仅支持经 OpenGL/Vulkan 纹理化 DMA-BUF):对 get_default_feedbackget_surface_feedback 都只回发单个 tranchemain_device 设为渲染设备,tranche 的 tranche_target_device 也设为渲染设备,携带渲染 API 支持的全部 DRM format/modifier 对;不要设置 scanout 标志。
  2. 支持全屏直接扫描输出:当某 surface 进入/离开全屏时(仅当客户端使用了 get_surface_feedback),重发参数。非全屏参数同基础合成器;全屏参数有两个 tranche——其一带 KMS plane 支持的 format/modifier 对、设置 scanout 标志、tranche_target_device 为 KMS 扫描输出设备;其二带其余“可纹理化但不可扫描输出”的 format/modifier 对、不设 scanout 标志、tranche_target_device 为渲染设备。
  3. 支持所有 surface 直接扫描输出:对成为扫描输出候选的 surface 发送两个 tranche(方式同上)。surface 退出候选时,重发仅优化纹理化路径的参数。候选如何选属于合成器策略,文档给出的可行实现是:选尽可能多的 surface 直到占满可用硬件 plane 数量,从“离眼睛更近”的 surface 开始选。
  4. 多设备同时支持:固定设备渲染 + 次级设备直接扫描输出的合成器,可以为在次级设备上显示且是扫描输出候选的 surface 单独发一个 tranche,其 tranche_target_device 是次级设备,等于 main_device
  5. 运行时切换渲染设备:允许以不同的 main_device 重发参数,但存在风险——客户端可能不支持运行时切换设备、继续沿用旧设备。因此合成器应当始终先以一个兜底渲染设备作为初始 main_device 发出,让这些旧设备客户端使用兜底设备。文档还特别警告:在不支持显式 modifier 时不要在运行时改变 main_device,否则有把隐式非线性 modifier 的缓冲按线性缓冲导入、导致缓冲内容被误读的风险。

两条红线值得单独强调:

  • 没有兜底路径就不发 feedback 参数。例如:绝不应把一个仅支持直接扫描输出、而渲染 API 无法纹理化的 format/modifier 写进 feedback 里——一旦客户端真的按它分配,合成器就无法显示。
  • 可用多个 tranche 表达纹理化路径的分级:如有快慢两种纹理化路径的格式,可以拆成两个 tranche;也可用“中间 tranche”表达介于直接扫描输出与纯纹理化之间的代码路径,例如通过 mem2mem 设备把缓冲转换后再走扫描输出路径。

6. 线上编码:dev_tformat_table

6.1 dev_t 编码

协议通过数组在线上携带 dev_t 值。合成器(C 实现)编码示例:

struct stat drm_node_stat;
struct wl_array dev_array = {
    .size = sizeof(drm_node_stat.st_rdev),
    .data = &drm_node_stat.st_rdev,
};

客户端解码:

dev_t dev;
assert(dev_array->size == sizeof(dev));
memcpy(&dev, dev_array->data, sizeof(dev));

由于两个 DRM 节点可以指向同一个 DRM 设备而拥有不同的 dev_t,客户端必须使用 drmDevicesEqual 比较两个设备,绝不能直接比较 dev_t

6.2 format_table 编码

format_table 事件携带的文件描述符对应一个 format+modifier 对数组,每个对 16 字节宽,结构如下:

struct dmabuf_format_modifier {
    uint32_t format;
    uint32_t pad; /* unused */
    uint64_t modifier;
};

数组使用本机字节序、紧凑排列;tranche_formats 中的 16 位索引即指向该数组中的第几个元素。

6.3 与其他 API 的集成

文档最后列出了设备标识互相映射的三个入口:

  • libdrmdrmGetDeviceFromDevId 可以从设备 ID 得到 drmDevice
  • EGL:可用 EGL_EXT_device_drm_render_node 扩展查询某 EGL display 使用的 DRM 渲染节点;不可用时退回较旧的 EGL_EXT_device_drm
  • Vulkan:可用 VK_EXT_physical_device_drm 扩展查询某 VkPhysicalDevice 使用的 DRM 设备。

7. 在 Godot 仓库中定位这套协议

结合仓库结构可以确认 feedback 文档并非孤立资料,而是协议完整实现的一部分:

  • 协议机读定义:linux-dmabuf-v1.xmlzwp_linux_dmabuf_v1 工厂接口(version 5)在 v4 起提供 get_default_feedback / get_surface_feedback 两个请求,同时 formatmodifier 广播事件自 v4 起被标记 deprecated-since="4",文档明确要求改用 feedback 路径;zwp_linux_buffer_params_v1 定义了 add/create/create_immed 请求与 8 种错误枚举(already_usedinvalid_wl_buffer)以及 y_invert/interlaced/bottom_first 三个 flags——这正是客户端第 4 节流程里反复调用的 create 请求。
  • 构建集成:platform/linuxbsd/wayland/SCsub 在 Linux/BSD Wayland 目标编译时从 XML 生成 linux_dmabuf_v1.gen.h
  • 接口注册:wayland_embedder.h 包含生成头,接口表中同时列出 zwp_linux_dmabuf_v1_interfacezwp_linux_dmabuf_feedback_v1_interfacewayland_embedder.h)。
  • 协议维护者信息见 README

从源码结构看,Godot 侧对 feedback 的响应(缓冲重分配、format 选择)由引擎渲染服务器按平台实现,而协议代码本身完全由上述 XML 驱动生成,修改协议版本时只需更新 thirdparty 下的 wayland-protocols 快照。

8. 小结

linux-dmabuf feedback 的价值在于把“客户端猜 format”升级为“合成器按设备与呈现路径显式推荐”:一个 main_device 保证兜底可显示,若干按偏好降序的 tranche 分别描述“直接扫描输出 → 中间转换路径 → 纯纹理化”的各级代码路径,format_table 以零拷贝的 fd 传递格式表,done 事件把多事件参数变更收敛为原子更新。客户端按“逐档尝试、检测变更、强制线性布局兜底”实现即可在单/多 GPU 的 Linux 桌面上同时拿到正确性与性能;合成器则必须遵守“无兜底不发参数、重发必须带来更优配置”的约束。这套机制正是现代 Wayland 合成器在混合 GPU 环境下控制 DMA-BUF 分配质量的标准手段,也是 Godot 在 Linux/BSD Wayland 平台上随协议快照完整携带并注册的 stable 扩展。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384