SurfaceFlinger 和 Hardware Composer

拥有图形数据缓冲区的确不错,如果还能在设备屏幕上查看它们就更是锦上添花了。这正是 SurfaceFlinger 和 Hardware Composer HAL 的用武之地。

SurfaceFlinger

SurfaceFlinger 的作用是接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。以前,该过程通过软件到硬件帧缓冲区(例如 /dev/graphics/fb0)的位块传输来实现,但是这样的日子已经远去。

当应用进入前台时,WindowManager 服务会向 SurfaceFlinger 请求一个绘图 Surface。SurfaceFlinger 会创建一个其主要组件为 BufferQueue 的层,而 SurfaceFlinger 是其消耗方。生产方端的 Binder 对象通过 WindowManager 传递到应用,然后应用可以开始直接将帧发送到 SurfaceFlinger。

注意:尽管本部分使用 SurfaceFlinger 术语,但 WindowManager 会使用术语“窗口”(而不是“层”)…而将“层”用来表示其他内容。(有人认为 SurfaceFlinger 实际上应称为 LayerFlinger。)

大多数应用通常在屏幕上有三个层:屏幕顶部的状态栏、底部或侧面的导航栏以及应用的界面。有些应用会拥有更多或更少的层(例如,默认主屏幕应用有一个单独的壁纸层,而全屏游戏可能会隐藏状态栏)。每个层都可以单独更新。状态栏和导航栏由系统进程渲染,而应用层由应用渲染,两者之间不进行协调。

设备显示会按一定速率刷新,在手机和平板电脑上通常为每秒 60 帧。如果显示内容在刷新期间更新,则会出现撕裂现象;因此,请务必只在周期之间更新内容。在可以安全更新内容时,系统便会收到来自显示设备的信号。由于历史原因,我们将该信号称为 VSYNC 信号。

刷新率可能会随时间而变化,例如,一些移动设备的刷新率范围在 58 fps 到 62 fps 之间,具体要视当前条件而定。对于连接了 HDMI 的电视,刷新率在理论上可以下降到 24 Hz 或 48 Hz,以便与视频相匹配。由于每个刷新周期只能更新屏幕一次,因此以 200 fps 的刷新率为显示设备提交缓冲区只是在做无用功,因为大多数帧永远不会被看到。SurfaceFlinger 不会在应用提交缓冲区时执行操作,而是在显示设备准备好接收新的缓冲区时才会唤醒。

当 VSYNC 信号到达时,SurfaceFlinger 会遍历它的层列表,以寻找新的缓冲区。如果找到新的缓冲区,它会获取该缓冲区;否则,它会继续使用以前获取的缓冲区。SurfaceFlinger 总是需要可显示的内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。

SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成。

Hardware Composer

Hardware Composer HAL (HWC) 是在 Android 3.0 中推出的,并且多年来一直都在不断演进。它主要是用来确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL,其实现是特定于设备的,而且通常由显示设备硬件原始设备制造商 (OEM) 完成。

当考虑叠加平面时,很容易发现这种方法的好处。它的目的是在显示硬件(而不是 GPU)中将多个缓冲区合成在一起。例如,假设有一部处于纵向模式的普通 Android 手机,其状态栏在顶部,导航栏在底部,其他地方为应用内容。每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成:

  • 将应用内容渲染到暂存缓冲区中,然后在其上渲染状态栏,再在其上渲染导航栏,最后将暂存缓冲区传送到显示硬件。
  • 将三个缓冲区全部传送到显示硬件,并告知它从不同的缓冲区读取屏幕不同部分的数据。

后一种方法可以显著提高效率。

显示处理器功能差异很大。叠加层的数量(无论层是否可以旋转或混合)以及对定位和叠加的限制很难通过 API 表达。HWC 会尝试通过一系列决策来适应这种多样性:

  1. SurfaceFlinger 向 HWC 提供一个完整的层列表,并询问“您希望如何处理这些层?”
  2. HWC 的响应方式是将每个层标记为叠加层或 GLES 合成。
  3. SurfaceFlinger 会处理所有 GLES 合成,将输出缓冲区传送到 HWC,并让 HWC 处理其余部分。

由于硬件供应商可以定制决策代码,因此可以在每台设备上实现最佳性能。

当屏幕上的内容没有变化时,叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。在此类情况下,HWC 可以选择为部分或全部层请求 GLES 合成,并保留合成的缓冲区。如果 SurfaceFlinger 返回要求合成同一组缓冲区,HWC 可以继续显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。

运行 Android 4.4 或更高版本的设备通常支持 4 个叠加平面。尝试合成多于叠加层的层会导致系统对其中一些层使用 GLES 合成,这意味着应用使用的层数会对功耗和性能产生重大影响。

虚拟显示设备

SurfaceFlinger 支持一个主要显示设备(即内置在手机或平板电脑中的显示屏)、一个外部显示设备(如通过 HDMI 连接的电视)以及一个或多个令合成的输出在系统中可用的虚拟显示设备。虚拟显示设备可用于记录屏幕或通过网络发送屏幕。

虚拟显示设备可以与主显示设备共享相同的一组层(层堆叠),也可拥有自己的一组层。虚拟显示设备没有 VSYNC,因此主显示设备的 VSYNC 用于为所有显示设备触发合成。

在旧版本的 Android 中,虚拟显示设备总是通过 GLES 合成,而 Hardware Composer 管理的合成仅用于主要显示设备。在 Android 4.4 中,Hardware Composer 能够参与虚拟显示设备合成。

正如您所预期的,为虚拟显示设备生成的帧会写入 BufferQueue。

案例研究:screenrecord

screenrecord 命令可让您将屏幕上显示的所有内容作为一个 .mp4 文件记录在磁盘上。为此,我们必须从 SurfaceFlinger 接收合成的帧,将它们写入视频编码器,然后将已编码的视频数据写入一个文件。视频编解码器由单独的进程 (mediaserver) 进行管理,因此我们必须在系统中移动大量图形缓冲区。为了使其更具挑战性,我们尝试以全分辨率录制 60 fps 的视频。高效完成这项工作的关键是 BufferQueue。

MediaCodec 类允许应用以缓冲区中的原始字节或通过 Surface 来提供数据。当 screenrecord 请求访问视频编码器时,mediaserver 会创建一个 BufferQueue,将其自身连接到消耗方端,然后将生产方端作为 Surface 传回到 screenrecord。

然后,screenrecord 命令要求 SurfaceFlinger 创建一个镜像主显示设备的虚拟显示设备(即,它具有完全相同的层),并指示它将输出发送到来自 mediaserver 的 Surface。在这种情况下,SurfaceFlinger 是缓冲区的生产方,而不是消耗方。

配置完成后,screenrecord 会等待显示编码数据。在应用绘制时,其缓冲区会前往 SurfaceFlinger,SurfaceFlinger 将它们合成为单个缓冲区,然后直接发送到 mediaserver 中的视频编码器。screenrecord 进程甚至从未看到过完整的帧。在内部,mediaserver 具有自己的移动缓冲区的方式,这种方式还通过句柄传递数据,从而最大限度地降低开销。

案例研究:模拟辅助显示设备

WindowManager 可以要求 SurfaceFlinger 创建一个可见层,将 SurfaceFlinger 作为其 BufferQueue 消耗方。也可以要求 SurfaceFlinger 创建一个虚拟显示设备,同样以 SurfaceFlinger 作为其 BufferQueue 生产方。如果连接它们并配置渲染到可见层的虚拟显示设备,会出现什么情况?

创建一个闭合循环,其中合成的屏幕显示在窗口中。该窗口现在是合成输出的一部分,因此在下一次刷新时,该窗口中的合成图像也会显示窗口内容(然后一直循环下去)。要实际查看该过程,请在设置中启用开发者选项,选择模拟辅助显示设备,然后启用一个窗口。另外有个好处是,可使用 screenrecord 捕获启用显示设备的操作,然后逐帧播放。