翻译:Mainline Explicit Fencing

译者注

dma-fence 作为 kernel 中 buffer 共享同步机制,已经成为 DRM 驱动框架必不可少的基础组件。了解 dma-fence 的背景知识,有助于后期学习 DRM 中 fence 相关的驱动开发。

本文翻译自 Gustavo Padovan 于 2016 年 9 月发表在 Collabora 官网的三篇文章,Gustavo 本人也是内核中 dma-fence 的提交作者。通过本文,我们可以了解到 dma-fence 最初是如何演变而来的,以及它是如何在 graphics pipeline 中起作用的。

Mainline Explicit Fencing - Part 1

当我们谈到 Linux kernel 中的 buffer 共享同步机制时,通常有两种:Implicit(隐式) FenceExplicit(显式) Fence。它们的主要区别在于:kernel 是否会将同步信息共享给用户空间。因此它要么是隐式的(不提供任何 fence 信息给用户空间);要么是显式的(提供所有 fence 信息给用户空间)。

fence 同步机制可以确保 buffer 在共享过程中,驱动程序或用户空间不会对一个正在被写入的 buffer 进行读操作,或对一个仍在被其他模块使用的 buffer 进行写操作。fence 确保了这些操作能够有序进行,即只会在 buffer 不被使用时才进行读写操作。例如,当一个 GPU 的 job 被塞进队列时,该 job 中的 buffer 会被关联上一个 fence,其他驱动程序可以借助该 fence 来进行同步。在收到该 fence 的信号(signal)之前,这些驱动程序不会对该 buffer 进行任何操作,fence signal 表明该 buffer 现在可以被正常使用了。同样地,为了让 GPU 驱动能等待 buffer 从显示屏中切换出来,我们可以给显示驱动采用相同的 fence 机制,以便 GPU 能再次使用这些 buffer 进行渲染。

fence 是该机制的核心元素,每当向 kernel 发送 buffer 相关的请求时,该 buffer 都会附带一个 fence。用户空间或其他驱动程序可以使用 fence 来等待硬件工作完成。因此,一旦工作完成,该 fence 就会被 signal,等待该 fence 的模块也就可以继续对该 buffer 进行它们想要的操作了。

虽然 Implicit Fence 在 buffer 同步方面起了很大作用,但在某些情况下,整个桌面的合成可能会被卡住。想象一下下面的合成过程:有 A、B、C 三个 buffer 需要处理,A 和 B 被 GPU 拿去同时做渲染,而 C 将作为 A 与 B 合成的结果拿去显示。但只有在 A、B 这两个 buffer 都渲染完成时才会通知 compositor 做合成,因此如果 B 渲染的时间太长,将导致整个桌面的合成因为需要等待 B 而被 block 住,于是 C 就无法及时的显示出来。

在这里插入图片描述

图 1:Compositor 采用 Implicit Fence 机制同时处理两块 buffer,如果 buffer B 渲染耗时太长,则会导致桌面被冻结

而如果采用 Explicit Fence 机制,compositor 会为每个 buffer 关联上一个 fence,这样就可以在每个 buffer 渲染完成时收到通知。因此,如果 A 渲染的很快而 B 需要很长时间才能渲染完成,那么 compositor 可以做出决定不等待 B,而是继续使用旧的 buffer B 与 A 一起做合成,接着显示 C。因此 compositor 可以根据 fence 信息来做出更加明智的决定,以此来避免屏幕冻结的发生。

到目前为止,Linux 内核只有 Implicit Fence 的通用 API,虽然有些驱动程序已经实现了 Explicit Fence,但它们的 API 都是与设备强相关的。Android 目前已经有了一套自己的同步实现机制,即 Android Sync Framework —— 我会在下一篇文章中对它进行介绍。

Explicit Fence 是一种 消费者-生产者 模型,在一条 GPU 渲染 + 扫描上屏显示的 pipeline 中,它将在 kernel 驱动程序之间进行同步,因此当向 GPU(生产者)提交一个新的渲染 job 时,用户空间将获得一个与本次提交 buffer 相关联的 fence。也就是说用户空间不需要一直等待 job 完成而被阻塞在那里,当 job 完成时会自动向该 fence 发送一个信号。因为用户空间不再需要一直等在那里,且有了 fence 的加持,它就可以立即进行系统调用,告诉 Display 硬件(消费者)去扫描还未渲染完的 buffer。采用 Explicit Fence,内核空间会被告需要等到 fence signal 之后才能开始 buffer 扫描上屏的处理。

当用户空间向 kernel 空间提交一个 buffer 用于 Display 显示时,用户空间同时会收到一个新创建的 fence。当该 buffer 不再需要被显示时,它所对应的 fence 就会被 signal,于是这块 buffer 就可以被另一个渲染 job 拿去重新使用了。一旦户空间拿到该 fence,它可以无需等待就向 GPU 提交一个新的渲染 job。GPU 驱动则在内核空间等待该 buffer 显示完成,一旦 fence 发出信号,buffer 的渲染工作就可以立即启动。

在这里插入图片描述
图 2:fence 总是穿梭于 userspace 和 pipeline 中下一个模块之间,黄色箭头表示用户空间里的 fence

最后不得不提的是,Explicit Fence 大大改善了图形 pipeline 的 debug 能力。在用户空间访问 fence 可以更加清楚的知道底层 pipeline 当前正在发生的事情。以前,由于 Implicit Fence 没有可访问的信息,因此很难弄清楚 pipeline 上到底发生了什么,而且每个 vendor 厂商都试图实现他们自己的 Implicit Fence 机制,调试起来难道很大。现在,有了标准的 Expicit Fence,我们就可以更容易的建立起 debug 和 trace 框架,以便能够跟踪任何系统上的显示问题。

下一篇文章将描述 Android Sync Framework,之后还将描述在 mainline 上添加 Explicit Fence 支持所作的工作。

Mainline Explicit Fencing - Part 2

在第一篇文章中,我们讨论了 Linux 内核 Explicit Fence 的基本概念。作为本系列的第二篇文章,我们将介绍 Android Sync Framework,这是 Linux 内核中首个(out-of-tree)Explicit Fence 的具体实现。

Sync Framework 是 Android 在 AOSP 中的 Explicit Fence 实现方案,它使用文件描述符 fd 在 userspace 和 kernel 之间、以及进程之间传递 fence 信息。

在 Sync Framework 中,一切都从创建一个 Sync Timeline 开始,Sync Timeline 是为每个驱动程序上下文创建的一个结构体,用来表示一个单调递增的计数器。Sync Timeline 将保证同一时间轴上不同 fence 之间的执行顺序,驱动程序上下文可以是不同的 GPU ring,或是硬件上不同的 Display。
在这里插入图片描述
图 1:Sync Timeline

然后我们需要有 Sync Point(sync_pt),Android 给 fence 起的名字,它们代表 Sync Timeline 上某个特定的值。Sync Point 刚被创建的时候会被初始化为 Active 状态,当它发出信号时(比如与它关联的 job 执行结束了),它将转换成 Signaled 状态,并通知 Sync Timeline 的计数器更新为最后一次被 signal 的 Sync Point 的值。

在这里插入图片描述
图 2:Sync Point

要想将 Sync Point 导出给用户空间,或从用户空间导入到内核,需要借助 Sync Fence 结构体。Sync Fence 本质上是一个 linux file,我们使用 Sync Fence 来存储 Sync Point 的信息。要想将它导出给用户空间,需要给 Sync Fence file 关联上一个 unused 的文件描述符(fd),然后驱动程序就可以通过该 fd 来传递 Sync Point 的信息了。

在这里插入图片描述
图 3:Sync Fence

Sync Fence 通常是在 Sync Point 被创建之后才创建的,然后经过用户空间最终在 pipeline 模块之间传递,直到驱动程序等待 Sync Fence 发出信号为止。只有当 Sync Fence 内部所有的 Sync Point 都被 signal 时,Sync Fence 自己才会被 signal。

Android Sync Framework 最重要的一个功能就是能够将两个 Sync Fence merge(合并)到一个新的 Sync Fence 中,它包含来自两个 Sync Fence 的所有 Sync Point。只要你的资源允许,它可以包含无数多个 Sync Point。merge 后的 Sync Fence 只会在其内部所有的 Sync Point 都发出信号后才会被 signal。

在这里插入图片描述
图 4:Merge 后的 Sync Fence

说起用户空间 API,Sync Framework 实现了三个 ioctl 调用。第一个是等待 sync_fence 被 signal,第二个是将两个 sync_fence merge 为第三个新的 sync_fence,最后一个 API 是用来获取 sync_fence 及其所有 sync_point 信息的。

当我们通过系统调用,请求 kernel 对一块 buffer 进行渲染或显示时,Sync Fence 的 fd 将通过该系统调用传递给 kernel,或从 kernel 返回给用户空间。

本篇的主要目的为了简要概括一下 Sync Framework,因为我们将在下一篇文章中看到这些概念。同时我将会在下一篇讲述为了让 mainline kernel 支持 Explicit Fence,我们都做了哪些工作。如果你想了解更多关于 Sync Framework 的详细信息,请点击 这里这里 查看。

Mainline Explicit Fencing - Part 3

在前两篇文章中,我们讨论了 Explicit Fence 对于 graphics pipeline 大体上都起了什么作用,以及为了将 Android Sync Framework upstream 我们都做了哪些事情。现在,作为在本系列的最后一篇文章,我将介绍在 DRM 和图形栈的其他方面应该如何来实现 Explicit Fence。

DRM 实现 Explicit Fence 需要建立在两个 kernel 基础框架之上:struct dma_fence —— 该结构体表示 fence,和 struct sync_file —— 用来提供与用户空间共享的文件描述符(如前几篇文章中所讨论的)。在使用 fence 时,Display 底层驱动需要等待该 fence 发出信号后,才能在屏幕上显示该 buffer。在 Explicit Fence 的实现中,fence 从用户空间发送给内核空间,Display 底层驱动同样会给用户空间返回一个新的 fence,该 fence 被封装在 sync_file 结构体中。当该 buffer 被显示到屏幕上时,它所对应的 fence 就会被 signal。同样的,在渲染侧也采用相同的等待流程。

必须使用 Atomic Modesetting,我们不打算支持 legacy API。DRM 要等待的 fence 需要通过每个 DRM Plane 的 IN_FENCE_FD property 来传递,也就是说每个 Plane 将收到一个 sync_file fd,每个 fd 将包含一个或多个 dma_fence。请记住,在 DRM 中一个 Plane 直接对应一个 framebuffer,因此也可以说一个 framebuffer 对应一个 sync_file。

另一方面,对于 kernel 创建的 fence,需要借助于 OUT_FENCE PTR 这个 property 来返回给用户空间。这是一个 DRM CRTC 的 property,因为 CRTC 上的所有 buffer 都是在同一时刻被扫描出去的,所以我们只为每个 CRTC 创建一个 dma_fence。kernel 通过将 fd 的值写入 OUT_FENCE PTR property 所指向的用户空间内存里,这样就能将这个 fence 返回给用户空间了。请注意,与 Android 不同的是,Mainline 中的 fence 在被 signal 时,意味着上一个 buffer(即已经从屏幕上移除的 buffer)可以被再次使用了。而在 Android 上,当信号被触发时,意味着当前正在显示的 buffer 被释放了。不过 Android 的开发人员已经重新修改了 SurfaceFlinger,以支持 Mainline 上 Explicit Fence 的使用规则!

不过,以上仅仅只是一方面,要想让 Explicit Fence 机制在整个 graphics pipeline 中运行起来,我们还需要在渲染侧对它添加支持。由于每个渲染驱动程序都有自己的 userspace API,因此我们需要为每个渲染驱动添加 Explicit Fence 支持。freedreno 驱动程序已经可以支持 mainline 上的 Explicit Fence,而 i915 和 virtio_gpu 驱动也正在添加对该 fence 的支持。

在用户空间这一端,Mesa 已经添加了对 EGL_ANDROID_native_fence_sync 扩展接口的支持,这样就可以在 Android 平台上使用 Explicit Fence 了。libdrm 的头文件也已经合入了对 sync_file 的 ioctl 封装。在 Android 平台上,libsync 现在既可以支持旧的 Android Sync API,又可以支持新的 Mainline Sync File API。最后,在 drm_hwcomposer 上,使用 Atomic Modesetting 和 Explicit Fence 的 patch 也都已经有了,只不过它们暂时还没有被 upstream。

IGT(intel-gpu-tools) 中也已经添加对 Sync File 和 fence 的 Atomic API 测试 case。

全文完。

原文连接

  1. Mainline Explicit Fencing - Part 1
  2. Mainline Explicit Fencing - Part 2
  3. Mainline Explicit Fencing - Part 3

扩展阅读:

  1. Mainline Explicit Fencing - X.Org
  2. Bringing Android explicit fencing to the mainline
  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值