同步框架

同步框架明确描述了 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 运算符,以使调试输出更具可读性。
  • 实现填充 driver_data,以允许用户空间库(例如 GL 库)访问私有时间线数据(如果需要)。data_driver 允许供应商传递有关不可变的 sync_fencesync_pts 的信息,以根据这些信息构建命令行。
  • 不允许用户空间显式创建或发出栅栏信号。显式创建信号/栅栏会导致拒绝服务攻击,从而停止管道功能。
  • 不要显式访问 sync_timelinesync_ptsync_fence 元素。API 提供了所有必需的函数。

sync_pt

sync_ptsync_timeline 上的单个值或点。一个点有三种状态:活动 (active)、已发出信号 (signaled) 和错误 (error)。点开始时处于活动状态,并转换到已发出信号或错误状态。例如,当图像消费者不再需要缓冲区时,会发出 sync_pt 信号,以便图像生产者知道可以再次写入缓冲区。

sync_fence

sync_fencesync_pt 值的集合,这些值通常具有不同的 sync_timeline 父项(例如,显示控制器和 GPU 的父项)。sync_fencesync_ptsync_timeline 是驱动程序和用户空间用来传达其依赖关系的主要原语。当 fence 变为已发出信号时,保证在 fence 之前发出的所有命令都已完成,因为内核驱动程序或硬件块按顺序执行命令。

同步框架允许多个消费者或生产者在完成使用缓冲区时发出信号,从而通过一个函数参数传达依赖关系信息。Fence 由文件描述符支持,并从内核空间传递到用户空间。例如,一个 fence 可以包含两个 sync_pt 值,表示两个独立的图像消费者何时完成读取缓冲区。当 fence 发出信号时,图像生产者知道两个消费者都已完成消费。

Fence 与 sync_pt 值一样,开始时处于活动状态,并根据其点的状态更改状态。如果所有 sync_pt 值都变为已发出信号,则 sync_fence 变为已发出信号。如果一个 sync_pt 进入错误状态,则整个 sync_fence 都处于错误状态。

在创建 fence 后,sync_fence 中的成员资格是不可变的。要在一个 fence 中获得多个点,需要进行合并,将来自两个不同 fence 的点添加到第三个 fence。如果其中一个点在原始 fence 中已发出信号,而另一个点没有,则第三个 fence 也不会处于已发出信号状态。

要实现显式同步,请提供以下内容

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

案例研究:实现显示驱动程序

要使用支持同步功能的 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 函数更加复杂。在将缓冲区放到显示器上时,该缓冲区与一个 fence 关联,该 fence 指示缓冲区何时准备就绪。您可以在 fence 清除后排队并启动工作。

在 fence 清除后排队和启动工作不会阻塞任何内容。您可以立即返回您自己的 fence,以保证缓冲区何时将从显示器上移除。当您排队缓冲区时,内核使用同步框架列出依赖关系

/*
 * 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 不得关闭该文件描述符。
  • 要继续使用 fence 文件描述符,供应商驱动程序或 HAL 必须复制该描述符。

每次 fence 对象通过 BufferQueue 时都会重命名。内核 fence 支持允许 fence 具有字符串名称,因此同步框架使用正在排队的窗口名称和缓冲区索引来命名 fence,例如 SurfaceView:0。这在调试中很有用,可以识别死锁的来源,因为名称会出现在 /d/sync 的输出和错误报告中。

ANativeWindow 集成

ANativeWindow 是 fence 感知的。dequeueBufferqueueBuffercancelBuffer 具有 fence 参数。

OpenGL ES 集成

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

  • EGL_ANDROID_native_fence_sync 提供了一种在 EGLSyncKHR 对象中包装或创建本机 Android fence 文件描述符的方法。
  • 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 扩展由一个独特的本机 fence EGLSyncKHR 对象类型组成。因此,应用于现有 EGLSyncKHR 对象类型的扩展不一定适用于 EGL_ANDROID_native_fence 对象,从而避免了不必要的交互。

EGL_ANDROID_native_fence_sync 扩展采用相应的本机 fence 文件描述符属性,该属性只能在创建时设置,并且无法从现有同步对象向前直接查询。此属性可以设置为两种模式之一

  • 有效的 fence 文件描述符 将现有的本机 Android fence 文件描述符包装在 EGLSyncKHR 对象中。
  • -1EGLSyncKHR 对象创建一个本机 Android fence 文件描述符。

使用 DupNativeFenceFD() 函数调用从本机 Android fence 文件描述符中提取 EGLSyncKHR 对象。这与查询设置的属性具有相同的结果,但遵循接收者关闭 fence 的约定(因此是重复操作)。最后,销毁 EGLSyncKHR 对象会关闭内部 fence 属性。

硬件合成器集成

硬件合成器处理三种类型的同步 fence

  • 获取 fence (Acquire fences) 与输入缓冲区一起传递给 setLayerBuffersetClientTarget 调用。这些表示对缓冲区的待处理写入,并且必须在 SurfaceFlinger 或 HWC 尝试从关联的缓冲区读取以执行合成之前发出信号。
  • 释放 fence (Release fences) 在使用 getReleaseFences 调用调用 presentDisplay 后检索。这些表示从同一图层上的先前缓冲区的待处理读取。当 HWC 不再使用先前的缓冲区时,释放 fence 会发出信号,因为当前缓冲区已替换显示器上的先前缓冲区。释放 fence 与将在当前合成期间替换的先前缓冲区一起传递回应用程序。应用程序必须等待释放 fence 发出信号后,才能将新内容写入返回给它们的缓冲区中。
  • 呈现 fence (Present fences) 作为调用 presentDisplay 的一部分,每个帧返回一个。呈现 fence 表示此帧的合成已完成,或者,先前帧的合成结果不再需要。对于物理显示器,当当前帧出现在屏幕上时,presentDisplay 返回呈现 fence。返回呈现 fence 后,可以安全地再次写入 SurfaceFlinger 目标缓冲区(如果适用)。对于虚拟显示器,当可以从输出缓冲区读取时,将返回呈现 fence。