Android 框架提供了各种用于 2D 和 3D 图形渲染的 API,可与制造商的图形驱动程序实现方法交互。应用开发者可通过三种方式将图像绘制到屏幕上:使用 Canvas、OpenGL ES 或 Vulkan。
一、Android 图形组件
无论开发者使用什么渲染 API,一切内容都会渲染到“Surface”。Surface 表示缓冲队列中的生产方,而缓冲队列通常会被 SurfaceFlinger 消耗。在 Android 平台上创建的每个窗口都由 Surface 提供支持。所有被渲染的可见 Surface 都被 SurfaceFlinger 合成到显示部分。
下图显示了关键组件如何协同工作:
主要组件如下所述:
图像流生产方
图像流生产方可以是生成图形缓冲区以供消耗的任何内容。例如 OpenGL ES、Canvas 2D 和 mediaserver 视频解码器。
WindowManager
WindowManager 会控制窗口(Window)对象,它们是用于容纳视图(View)对象的容器。窗口对象始终由 Surface 对象提供支持。WindowManager 会监督生命周期、输入和聚焦事件、屏幕方向、转换、动画、位置、变形、Z 轴顺序以及窗口的许多其他方面。WindowManager 会将所有窗口元数据发送到 SurfaceFlinger,以便 SurfaceFlinger 可以使用这些数据在屏幕上合成 Surface。
图像流消耗方
图像流的最常见消耗方是 SurfaceFlinger,其他 OpenGL ES 应用也可以消耗图像流,例如相机应用会消耗相机预览图像流。非 GL 应用也可以是使用方,例如 ImageReader 类。
SurfaceFlinger
该系统服务会消耗当前可见的 Surface,并使用窗口管理器中提供的信息将它们合成到显示部分。SurfaceFlinger 接受来自多个源的数据缓冲区,然后将它们进行合成并发送到显示屏。SurfaceFlinger 是可以修改所显示部分内容的唯一服务。SurfaceFlinger 使用 OpenGL 和 Hardware Composer 来合成一组 Surface。
SurfaceFlinger 可通过两种方式接受缓冲区:通过 BufferQueue 和 SurfaceControl。
SurfaceFlinger 接受缓冲区的一种方式是通过 BufferQueue 和 SurfaceControl。当应用进入前台时,它会从 WindowManager 请求缓冲区。然后,WindowManager 会从 SurfaceFlinger 请求层。层是 Surface(包含 BufferQueue)和 SurfaceControl(包含屏幕框架等层元数据)的组合。SurfaceFlinger 创建层并将其发送至 WindowManager。然后,WindowManager 将 Surface 发送至应用,但会保留 SurfaceControl 来操控应用在屏幕上的外观。
虽然应用可以随时提交缓冲区,但 SurfaceFlinger 仅能在屏幕处于两次刷新之间时唤醒,以接受缓冲区,这会因设备而异。这样可以最大限度地减少内存使用量,并避免屏幕上出现可见的撕裂现象(如果显示内容在刷新期间更新,则会出现此现象)。
在屏幕处于两次刷新之间时,屏幕会向 SurfaceFlinger 发送 VSYNC 信号。VSYNC 信号表明可对屏幕进行刷新而不会产生撕裂。当 SurfaceFlinger 接收到 VSYNC 信号后,SurfaceFlinger 会遍历其层列表,以查找新的缓冲区。如果 SurfaceFlinger 找到新的缓冲区,SurfaceFlinger 会获取缓冲区;否则,SurfaceFlinger 会继续使用上一次获取的那个缓冲区。SurfaceFlinger 必须始终显示内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。
SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问硬件混合渲染器 (HWC) 应如何进行合成。如果 HWC 将层合成类型标记为客户端合成,则 SurfaceFlinger 将合成这些层。然后,SurfaceFlinger 会将输出缓冲区传递给 HWC。
层
层是合成的最重要单元。层是 Surface 和 SurfaceControl 的组合。每个层都有一组属性,用于定义它与其他层的交互方式。层属性是:
属性 | 说明 |
---|---|
定位 | 定义层在其屏幕上的显示位置。包括层边缘的位置及其相对于其他层的 Z 顺序(指示该层在其他层之前还是之后)等信息。 |
内容 | 定义应如何在定位属性定义的边界内呈现层上显示的内容。包括诸如剪裁(用来扩展内容的一部分以填充层的边界)和转换(用来显示旋转或翻转的内容)等信息。 |
合成 | 定义层应如何与其他层合成。包括混合模式和用于 Alpha 合成的全层 Alpha 值等信息。 |
优化 | 提供对于正确合成层并非绝对必要但可由硬件混合渲染器 (HWC) 设备用来优化合成执行方式的信息。包括层的可见区域以及层的哪个部分自上一帧以来已经更新等信息。 |
硬件混合渲染器(HWC)
显示子系统的硬件抽象实现。SurfaceFlinger 可以将某些合成工作委托给 Hardware Composer,以分担 OpenGL 和 GPU 上的工作量。SurfaceFlinger 只是充当另一个 OpenGL ES 客户端。因此,在 SurfaceFlinger 将一个或两个缓冲区合成到第三个缓冲区中的过程中,它会使用 OpenGL ES。这样使合成的功耗比通过 GPU 执行所有计算更低。
Hardware Composer HAL 则进行另一半的工作,并且是所有 Android 图形渲染的核心。Hardware Composer 必须支持事件,其中之一是 VSYNC(另一个是支持即插即用 HDMI 的热插拔)。
硬件混合渲染器 (HWC) HAL 用于确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL,其实现是特定于设备的,而且通常由屏幕硬件原始设备制造商 (OEM) 完成。
当您考虑使用叠加平面时,很容易发现这种方法的好处,它会在显示硬件(而不是 GPU)中合成多个缓冲区。例如,假设有一部普通 Android 手机,其屏幕方向为纵向,状态栏在顶部,导航栏在底部,其他区域显示应用内容。每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成:
- 将应用内容渲染到暂存缓冲区中,然后在其上渲染状态栏,再在其上渲染导航栏,最后将暂存缓冲区传送到显示硬件。
- 将三个缓冲区全部传送到显示硬件,并指示它从不同的缓冲区读取屏幕不同部分的数据。
后一种方法可以显著提高效率。
显示处理器功能差异很大。叠加层的数量(无论层是否可以旋转或混合)以及对定位和叠加的限制很难通过 API 表达。为了适应这些选项,HWC 会执行以下计算:
- SurfaceFlinger 向 HWC 提供一个完整的层列表,并询问“您希望如何处理这些层?”
- HWC 的响应方式是将每个层标记为设备或客户端合成。
- SurfaceFlinger 会处理所有客户端,将输出缓冲区传送到 HWC,并让 HWC 处理其余部分。
- 由于硬件供应商可以定制决策代码,因此可以在每台设备上实现最佳性能。
当屏幕上的内容没有变化时,叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。在此类情况下,HWC 可以为部分或全部层请求 GLES 合成,并保留合成的缓冲区。如果 SurfaceFlinger 要求合成同一组缓冲区,HWC 可以显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。
Android 设备通常支持 4 个叠加平面。尝试合成的层数多于叠加层数会导致系统对其中一些层使用 GLES 合成,这意味着应用使用的层数会对能耗和性能产生重大影响。
HWC 可以抽象出叠加层和 2D 位块传送器等对象来合成 Surface,并与专门的窗口合成硬件进行通信以合成窗口。使用 HWC 来合成窗口,而不是让 SurfaceFlinger 与 GPU 进行合成。大多数 GPU 都未针对合成进行过优化,当 GPU 合成来自 SurfaceFlinger 的图层时,应用就无法使用 GPU 进行自我渲染。
HWC 实现应该支持:
- 至少 4 个叠加层:
- 状态栏
- 系统栏
- 应用
- 壁纸/背景
- 大于屏幕的图层(例如壁纸)
- 同时预乘的每像素 Alpha 混合和每平面 Alpha 混合
- 受保护视频播放的硬件路径
- RGBA 打包顺序、YUV 格式以及平铺、重排和步幅属性
Gralloc
需要使用图形内存分配器 (Gralloc) 来分配图像生产方请求的内存。通过供应商专用 HAL 接口实现的 gralloc 内存分配器将用于执行缓冲区分配任务。
屏幕
系统可以具有多个屏幕,并且在正常系统操作期间可以添加或移除屏幕。屏幕应 HWC 或框架的请求添加/移除。在外部屏幕与设备连接或断开连接时(称为热插拔),HWC 设备请求添加或移除屏幕。客户端请求虚拟屏幕,其内容会渲染到离屏缓冲区(而不是物理屏幕)。
虚拟屏幕
SurfaceFlinger 支持一个内部屏幕(内置于手机或平板电脑中的屏幕)、一个外部屏幕(如通过 HDMI 连接的电视)以及一个或多个令合成的输出在系统中可用的虚拟屏幕。虚拟屏幕可用于记录屏幕信息或通过网络发送屏幕信息。为虚拟屏幕生成的帧会写入 BufferQueue。
虚拟屏幕可以与主屏幕共享相同的一组层(层堆叠),也可拥有自己的一组层。虚拟屏幕没有 VSYNC,因此内部屏幕的 VSYNC 用于为所有屏幕触发合成。
在支持虚拟屏幕的硬件混合渲染器 (HWC) 实现中,虚拟屏幕可以与 OpenGL ES (GLES)、HWC 或者 GLES 及 HWC 合成在一起。在不支持虚拟屏幕的实现中,虚拟屏幕始终使用 GLES 进行合成。
VSYNC
VSYNC 信号可同步显示管道。显示管道由应用渲染、SurfaceFlinger 合成以及用于在屏幕上显示图像的硬件混合渲染器 (HWC) 组成。VYSNC 可同步应用唤醒以开始渲染的时间、SurfaceFlinger 唤醒以合成屏幕的时间以及屏幕刷新周期。这种同步可以消除卡顿,并提升图形的视觉表现。
SurfaceFlinger 通过调用 setVsyncEnabled 来控制 HWC 是否生成 VSYNC 事件。SurfaceFlinger 使 setVsyncEnabled 能够生成 VSYNC 事件,因此它可以与屏幕的刷新周期同步。当 SurfaceFlinger 同步到屏幕刷新周期时,SurfaceFlinger 会停用 setVsyncEnabled 以阻止 HWC 生成 VSYNC 事件。如果 SurfaceFlinger 检测到实际 VSYNC 与它先前建立的 VSYNC 之间存在差异,则 SurfaceFlinger 会重新启动 VSYNC 事件生成过程。
VSYNC 偏移
同步应用和 SurfaceFlinger 渲染循环应同步到硬件 VSYNC。在 VSYNC 事件中,屏幕开始显示帧 N,而 SurfaceFlinger 开始为帧 N + 1 合成窗口。应用处理等待的输入并生成帧 N+2。
与 VSYNC 同步会实现一致的延迟时间。它可以减少应用和 SurfaceFlinger 中的错误,并最大限度减小相位内外屏幕之间的偏移。这要假定应用和 SurfaceFlinger 的每帧时间没有很大变化。延迟至少为两帧。
为了解决此问题,您可以通过使应用和合成信号与硬件 VSYNC 相关,从而利用 VSYNC 偏移减少输入设备到屏幕的延迟。这是有可能的,因为应用加合成通常需要不到 33 毫秒的时间。
VSYNC 偏移的结果是具有相同周期和偏移相位的三个信号:
- HW_VSYNC_0 - 屏幕开始显示下一帧。
- VSYNC - 应用读取输入内容并生成下一帧。
- SF_VSYNC - SurfaceFlinger 开始为下一帧进行合成。
通过 VSYNC 偏移,SurfaceFlinger 接收缓冲区并合成帧,而应用同时处理输入内容并渲染帧。
DispSync
DispSync 维护屏幕基于硬件的周期性 VSYNC 事件的模型,并使用该模型在硬件 VSYNC 事件的特定相位偏移处执行回调。
DispSync 是一个软件锁相回路 (PLL),它可以生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC 和 SF_VSYNC 信号,即使没有来自硬件 VSYNC 的偏移也是如此。
DispSync 具有以下特点:
- 参考 - HW_VSYNC_0。
- 输出 - VSYNC 和 SF_VSYNC。
- 反馈 - 自硬件混合渲染器的退出栅栏有信号状态时间戳。
二、数据流
有关 Android 图形管道的描述,请参见下图:
左侧的对象是生成图形缓冲区的渲染器,如主屏幕、状态栏和系统界面。SurfaceFlinger 是合成器,而硬件混合渲染器是制作器。
BufferQueue
BufferQueues 是 Android 图形组件之间的粘合剂。它们是一对队列,可以调解缓冲区从生产方到消耗方的固定周期。一旦生产方移交其缓冲区,SurfaceFlinger 便会负责将所有内容合成到显示部分。
使用方创建并拥有 BufferQueue 数据结构,并且可存在于与其生产方不同的进程中。当生产方需要缓冲区时,它会通过调用 dequeueBuffer() 从 BufferQueue 请求一个可用的缓冲区,并指定缓冲区的宽度、高度、像素格式和使用标记。然后,生产方填充缓冲区并通过调用 queueBuffer() 将缓冲区返回到队列。接下来,使用方通过 acquireBuffer() 获取该缓冲区并使用该缓冲区的内容。当使用方操作完成后,它会通过调用 releaseBuffer() 将该缓冲区返回到队列。同步框架可控制缓冲区在 Android 图形管道中移动的方式。
BufferQueue 的一些特性(例如可以容纳的最大缓冲区数)由生产方和使用方联合决定。但是,BufferQueue 会根据需要分配缓冲区。除非特性发生变化,否则将会保留缓冲区;例如,如果生产方请求具有不同大小的缓冲区,则系统会释放旧的缓冲区,并根据需要分配新的缓冲区。
BufferQueue 永远不会复制缓冲区内容,因为移动如此多的数据是非常低效的操作。相反,缓冲区始终通过句柄进行传递。
有关 BufferQueue 通信过程,请参见下图。
BufferQueue 包含将图像流生产方与图像流消耗方结合在一起的逻辑。图像生产方的一些示例包括由相机 HAL 或 OpenGL ES 游戏生成的相机预览。图像消耗方的一些示例包括 SurfaceFlinger 或显示 OpenGL ES 流的另一个应用,如显示相机取景器的相机应用。
BufferQueue 是将缓冲区池与队列相结合的数据结构,它使用 Binder IPC 在进程之间传递缓冲区。生产方接口,或者您传递给想要生成图形缓冲区的某个人的内容,即是 IGraphicBufferProducer(SurfaceTexture 的一部分)。BufferQueue 通常用于渲染到 Surface,并且与 GL 消耗方及其他任务一起消耗内容。BufferQueue 可以在三种不同的模式下运行:
类同步模式 - 默认情况下,BufferQueue 在类同步模式下运行,在该模式下,从生产方进入的每个缓冲区都在消耗方那退出。在此模式下不会舍弃任何缓冲区。如果生产方速度太快,创建缓冲区的速度比消耗缓冲区的速度更快,它将阻塞并等待可用的缓冲区。
非阻塞模式 - BufferQueue 还可以在非阻塞模式下运行,在此类情况下,它会生成错误,而不是等待缓冲区。在此模式下也不会舍弃缓冲区。这有助于避免可能不了解图形框架的复杂依赖项的应用软件出现潜在死锁现象。
舍弃模式 - 最后,BufferQueue 可以配置为丢弃旧缓冲区,而不是生成错误或进行等待。例如,如果对纹理视图执行 GL 渲染并尽快绘制,则必须丢弃缓冲区。
为了执行这项工作的大部分环节,SurfaceFlinger 就像另一个 OpenGL ES 客户端一样工作。例如,当 SurfaceFlinger 正在积极地将一个缓冲区或两个缓冲区合成到第三个缓冲区中时,它使用的是 OpenGL ES。
Hardware Composer HAL 执行另一半工作。该 HAL 充当所有 Android 图形渲染的中心点。
参考资料:
- https://source.android.google.cn/devices/graphics