同步框架

同步框架明确描述了 Android 图形系统中不同异步操作之间的依赖关系。该框架提供了一个 API,可让组件在缓冲区被释放时进行提示。该框架还允许在驱动程序之间(从内核驱动程序到用户空间驱动程序)以及在用户空间进程本身之间传递同步基元。

例如,应用可以将要在 GPU 中执行的工作加入队列。然后,GPU 开始绘制该图像。尽管图像尚未被绘制到内存中,但系统仍会将缓冲区指针与指示 GPU 将何时完成工作的栅栏一起传递给窗口合成器。窗口合成器会提前开始处理,然后将工作移交给屏幕控制器。通过类似的方式,CPU 可以提前完成工作。GPU 完成处理后,屏幕控制器会立即显示图像。

同步框架还允许实现方在自己的硬件组件中利用同步资源。最后,该框架还可让实现方查看图形管道,以帮助他们进行调试。

显式同步

显式同步使图形缓冲区的生产方和消费方能够在结束使用缓冲区时发出信号。显式同步在内核空间中实现。

显式同步的优势包括:

  • 在不同设备上的行为差异小
  • 可提供更好的调试支持
  • 测试指标更完善

同步框架具有三种对象类型:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline 是一个单调递增的时间轴,供应商应为每个驱动程序实例(如 GL 上下文、屏幕控制器或 2D 位块传送器)实现该时间轴。sync_timeline 会记录针对特定硬件提交给内核的作业数量。sync_timeline 可保证相关操作按正确顺序进行,并支持特定于硬件的实现。

实现 sync_timeline 时,请遵循以下准则:

  • 为所有驱动程序、时间轴和栅栏提供有助于简化调试的名称。
  • 在时间轴中实现 timeline_value_strpt_value_str 运算符,以使调试输出更易于理解。
  • 如果您想允许用户空间库(如 GL 库)访问时间轴的私有数据,请实现填充 driver_data 运算符。data_driver 能让供应商传递有关不可变 sync_fencesync_pts 的信息,以便基于它们构建命令行。
  • 不允许用户空间明确创建栅栏或信号。明确创建信号/栅栏可招致拒绝服务攻击,该攻击会使管道功能停止运行。
  • 请勿明确访问 sync_timelinesync_ptsync_fence 元素。该 API 提供了所有必需的函数。

sync_pt

sync_ptsync_timeline 上的单个值(即点)。点具有三种状态:活动、有信号和错误。点最初处于活动状态,然后转变为有信号状态或错误状态。例如,当图像消费方不再需要缓冲区时,sync_pt 会变为有信号状态,以便图像生产方知道可以再次写入缓冲区。

sync_fence

sync_fencesync_pt 值的集合,这些值通常具有不同的 sync_timeline 父项(例如,对应于屏幕控制器的和对应于 GPU 的)。sync_fencesync_ptsync_timeline 是驱动程序和用户空间用来传达其依赖关系的主要基元。当某个栅栏变为有信号状态时,在该栅栏之前发出的所有命令肯定均已完成,因为内核驱动程序或硬件块是按顺序执行命令的。

同步框架允许多个消费方或生产方在结束使用缓冲区时发出信号,并通过一个函数参数来传达依赖关系信息。栅栏由一个文件描述符提供支持,并且会从内核空间传递到用户空间。例如,一个栅栏可以包含两个 sync_pt 值,它们分别指示两个独立的图像消费方完成缓冲区读取的时间点。当该栅栏变为有信号状态时,图像生产方便可知道两个消费方均已完成消费。

栅栏(如 sync_pt 值)最初处于活动状态,并会根据其所在点的状态来改变状态。如果所有 sync_pt 值均变为有信号状态,sync_fence 就会变为有信号状态。如果有一个 sync_pt 变为错误状态,那么整个 sync_fence 都会变为错误状态。

创建 sync_fence 后,该栅栏中的成员是不可变的。为了让一个栅栏中有多个点,需要进行合并操作,也就是将两个不同栅栏中的点添加到第三个栅栏中。如果其中一个点在原始栅栏中处于有信号状态,而另一个点并非处于有信号状态,那么第三个栅栏也不会处于有信号状态。

如需实现显式同步,请提供以下内容:

  • 为特定硬件驱动程序实现同步框架的内核空间子系统。需要感知栅栏的驱动程序通常是访问 Hardware Composer 或与其通信的任何程序。关键文件包括:
    • 核心实现:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • 位于 kernel/common/Documentation/sync.txt 的文档
    • platform/system/core/libsync 中的内核空间进行通信的库
  • 供应商必须将适当的同步栅栏以参数的形式提供给 HAL 中的 validateDisplay()presentDisplay() 函数。
  • 图形驱动程序中的两个与栅栏相关的 GL 扩展(EGL_ANDROID_native_fence_syncEGL_ANDROID_wait_sync)以及栅栏支持。

案例研究:实现屏幕驱动程序

为了使用支持同步函数的 API,您需要开发具有屏幕缓冲区函数的屏幕驱动程序。在同步框架尚不存在时,此函数接收 dma-buf 对象,将这些缓冲区放在屏幕上,并在缓冲区可见时执行屏蔽。例如:

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

有了同步框架后,display_buffer 函数变得稍微复杂一些。将某个缓冲区放在屏幕上时,该缓冲区会与指示该缓冲区何时准备就绪的栅栏相关联。您可以将工作加入队列,并在栅栏清空后启动工作。

将工作加入队列并在栅栏清空后启动无需屏蔽任何内容。您会立即返回自己的栅栏,这可以保证缓冲区将在指定时间离开屏幕。当您将缓冲区加入队列时,内核会列出与同步框架之间的依赖关系:

/*
 * displays buffer when fence is signaled.  returns immediately with a fence
 * that signals when buffer is no longer displayed.
 */
struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence
*fence);

同步集成

本部分将介绍如何将内核空间同步框架与 Android 框架的用户空间部分以及彼此必须进行通信的驱动程序进行集成。内核空间对象在用户空间中表示为文件描述符。

集成规范

请遵循以下 Android HAL 接口规范:

  • 如果 API 提供了表示 sync_pt 的文件描述符,使用该 API 的供应商驱动程序或 HAL 必须关闭该文件描述符。
  • 如果供应商驱动程序或 HAL 将包含 sync_pt 的文件描述符传递给 API 函数,那么供应商驱动程序或 HAL 就不得关闭该文件描述符。
  • 如需继续使用栅栏文件描述符,供应商驱动程序或 HAL 必须复制该描述符。

栅栏对象每次通过 BufferQueue 时都会被重命名。由于内核栅栏支持允许栅栏使用字符串作为名称,因此同步框架使用窗口名称和正在排队的缓冲区索引来命名栅栏(例如 SurfaceView:0)。在为了找出死锁的来源而进行调试时,这种命名方式十分有帮助,因为名称会显示在 /d/sync 的输出和错误报告中。

ANativeWindow 集成

ANativeWindow 能够感知栅栏。dequeueBufferqueueBuffercancelBuffer 具有栅栏参数。

OpenGL ES 集成

OpenGL ES 同步集成依赖于两个 EGL 扩展:

  • EGL_ANDROID_native_fence_sync 提供了一种在 EGLSyncKHR 对象中封装或创建原生 Android 栅栏文件描述符的方式。
  • EGL_ANDROID_wait_sync 允许 GPU 端暂停而不是 CPU 端暂停,从而使 GPU 等待 EGLSyncKHREGL_ANDROID_wait_sync 扩展与 EGL_KHR_wait_sync 扩展相同。

如需单独使用这两个扩展,请实现 EGL_ANDROID_native_fence_sync 扩展以及关联的内核支持。接下来,在驱动程序中启用 EGL_ANDROID_wait_sync 扩展。EGL_ANDROID_native_fence_sync 扩展包含不同的原生栅栏 EGLSyncKHR 对象类型。因此,适用于现有 EGLSyncKHR 对象类型的扩展不一定适用于 EGL_ANDROID_native_fence 对象,从而可以避免不必要的交互。

EGL_ANDROID_native_fence_sync 扩展使用相应的原生栅栏文件描述符属性,该属性只能在创建时设置,不能从现有同步对象直接向前查询。该属性可以设置为以下两种模式之一:

  • 有效的栅栏文件描述符:将现有的原生 Android 栅栏文件描述符封装在 EGLSyncKHR 对象中。
  • -1:从 EGLSyncKHR 对象创建原生 Android 栅栏文件描述符。

使用 DupNativeFenceFD() 函数调用从原生 Android 栅栏文件描述符中提取 EGLSyncKHR 对象。这与查询已设置的属性的结果相同,但符合由接收者关闭栅栏(因此会执行重复操作)的规范。最后,销毁 EGLSyncKHR 对象的操作会关闭内部栅栏属性。

Hardware Composer 集成

Hardware Composer 可处理三种类型的同步栅栏:

  • 获取栅栏会与输入缓冲区一起传递给 setLayerBuffersetClientTarget 调用。这些栅栏表示正在等待写入缓冲区,并且必须在 SurfaceFlinger 或 HWC 尝试从关联缓冲区读取数据以执行合成之前变为有信号状态。
  • 释放栅栏在调用 presentDisplay 之后使用 getReleaseFences 调用进行检索。这些栅栏表示正在等待从同一个图层的上一个缓冲区读取数据。当 HWC 不再使用屏幕的上一个缓冲区时(因为当前缓冲区已经替换了上一个缓冲区),释放栅栏会变为有信号状态。系统会将释放栅栏连同将在当前合成期间被替换的上一个缓冲区一起传回给应用。应用必须等到释放栅栏变为有信号状态,才能将新内容写入返回给它们的缓冲区。
  • 当前栅栏作为 presentDisplay 调用结果的一部分返回(每帧一个)。当前栅栏表示对应帧的合成何时完成,或者何时不再需要上一帧的合成结果。对于物理屏幕,当屏幕上显示当前帧时,presentDisplay 会返回当前栅栏。返回当前栅栏后,即可安全地再次写入 SurfaceFlinger 目标缓冲区(如果适用)。对于虚拟屏幕,当可以安全地从输出缓冲区中读取数据时,便会返回当前栅栏。