GPU 表面(零拷贝)¶
在 GPU 上渲染的本地客户端,例如视频播放器或绘图画布,可通过传输 GPU 表面 t=g 与终端共享输出,无需 CPU 拷贝。载荷是表面的名称:macOS 使用 IOSurface,Linux 使用如下 dma-buf。其他表面类型及未实现此功能的平台上的 t=g 均以 EINVAL 拒绝。t=g 不允许分块(m)和压缩(o)。
macOS 的载荷为 iosurface:<id>,其中 <id> 是 IOSurfaceGetID() 返回的整数。创建表面时必须设置 kIOSurfaceIsGlobal,否则终端中的 IOSurfaceLookup() 无法找到它。表面必须为 32 位 BGRA 或 RGBA,Alpha 与 f=32 一样采用直通值(非预乘)。f、s 和 v 被忽略,尺寸和像素格式取自表面本身。
Linux 使用 dma-buf,即 GPU 驱动、Vulkan、GBM 和 udmabuf 在进程间共享的缓冲区。dma-buf 是文件描述符,没有可供其他进程查找的名称,因此载荷指定持有它的进程及该进程中的描述符::
dmabuf:<pid>:<fourcc>:<modifier>:<fd>,<offset>,<stride>[:<fd>,<offset>,<stride>...]
<pid>持有缓冲区的进程 ID,以终端所见的 PID 为准,因此拥有独立 PID 命名空间的容器客户端无法使用。
<fourcc>DRM 格式的四字符代码。
AB24``(``DRM_FORMAT_ABGR8888)在内存中的字节顺序为 R、G、B、A,与f=32相同;AR24``(``DRM_FORMAT_ARGB8888)为 B、G、R、A。XB24和XR24分别相同,但忽略第四字节。Alpha 与f=32一样为直通值。<modifier>缓冲区的 DRM 格式修饰符,以十进制或带
0x前缀的十六进制表示:线性缓冲区用0,将布局交给驱动用0x00ffffffffffffff``(``DRM_FORMAT_MOD_INVALID),也可使用分配器选择的修饰符。<fd>,<offset>,<stride>一至四个平面,每个包含持有缓冲区进程中的描述符编号、平面偏移,以及字节步长,按分配器提供的值填写。上述格式具有一个平面;修饰符可能增加更多平面。
dma-buf 不知道自身尺寸,因此必须用 s 和 v 指定像素宽高;f 被忽略。
终端像调试器一样使用 pidfd_getfd(2) 复制描述符,且仅接受窗口所运行程序会话内的进程,即该窗口的 shell 及由其启动的程序。其他进程会被拒绝,避免程序让终端显示其他窗口或非自身程序的缓冲区。内核还要求终端有权跟踪目标进程。多数发行版默认的 Yama ptrace_scope 为 0 或 1,终端启动的进程满足条件;设为 2 或 3 时均不满足,全部 dma-buf 会被拒绝。非 dma-buf 描述符也会被拒绝。缓冲区导入后,终端关闭自己的描述符副本,由驱动在图像存在期间保留缓冲区;客户端收到回复后可随时关闭自己的描述符。
使用 EGL_EXT_image_dma_buf_import 导入缓冲区;修饰符不是 0 或 0x00ffffffffffffff 时,还需 EGL_EXT_image_dma_buf_import_modifiers。因此适用于 kitty 使用 EGL 绘制的 Wayland,而不适用于使用 GLX 的 X11。所有拒绝都回复 EINVAL 并说明原因:X11 平台、载荷格式不符、缺少 s 或 v、进程不在会话内、描述符无法获取或不是 dma-buf,或驱动无法导入缓冲区。查询 a=q 会执行除实际导入以外的全部检查,其答案说明终端能否获取该缓冲区。
复制 dma-buf 时,终端依赖 dma-buf 驱动对客户端发送命令前所提交渲染的隐式同步。如果驱动不提供此同步,或客户端用 CPU 写入缓冲区,客户端必须在发送命令前完成写入。
已在 Ubuntu 26.04、无头 sway 和 Mesa 软件渲染器下测试,使用 udmabuf 从内存创建 AB24 和 XR24 缓冲区,修饰符为 0 和 0x00ffffffffffffff:可显示缓冲区内容,并在刷新后显示更新内容。尚未测试 GPU 硬件驱动的缓冲区或其他修饰符。
图像存在期间,终端保持表面引用,并在传输时及每次刷新时在 GPU 上将其复制到图像,因此图像显示最近一次复制时的表面内容。更新表面后,客户端发送刷新操作::
<ESC>_Ga=r,i=31<ESC>\
这会重新复制表面并标记图像已更改,下一帧即可显示新内容。除非用 q 抑制回复,否则终端回答 OK。对已有图像 ID,再次用 t=g、同一表面和 a=t 或 a=T 传输,等同于先刷新再执行通常的放置。删除图像会释放表面。基于表面的图像不支持动画帧(a=f)和合成(a=c),终端回复 EINVAL。可用 a=q 与 t=g 检查支持情况;不认识此介质的终端回复错误。
多路复用器或 Goblin Skiff 等将终端会话转发到另一台机器的中继,不能传送表面本身,但可以读取它。在 macOS 上,同一用户的任何进程都可用 ID 调用 IOSurfaceLookup(),再通过 IOSurfaceLock() 和 IOSurfaceGetBaseAddress() 读取像素。在 Linux 上,由中继启动的客户端可像终端一样用 pidfd_getfd(2) 获取描述符,并用 mmap(2) 读取线性缓冲区;终端自身会拒绝通过多路复用器指定的 dma-buf,因为这些客户端不在窗口的会话内。中继可以轮询表面,例如每次收到刷新操作时,将像素用普通 t=d 传输发送到远端。
基础传输、放置、删除和回复规则由 kitty 图形协议 规定。