目录
一、引言
二、概览
三、实现HWC
3.1 为什么是HWC?
3.2 HWC的支持需求
3.3 HWC的实现思路
3.4 HWC的基元
3.5 HIDL接口
3.6 函数指针
3.7 图层和屏幕句柄
3.8 屏幕合成操作
3.9 多个屏幕
3.10 虚拟屏幕合成
3.10.1 模式
3.10.2 输出格式
3.11 同步fence
3.12 热插拔处理
3.12.1 概念理解
3.12.2 更新显示功能:
3.12.3 处理显示功能的更改:
3.12.4 常见的连接场景处理
3.12.5 使用顺序配置 ID 来防止竞争条件
3.13 客户端帧缓冲区管理
3.13.1 分辨率切换期间的帧缓冲区管理
四、Layers and displays
4.1 Displays
4.2 虚拟屏幕
五、VSYNC
5.1 VSYNC 偏移
5.2 DispSync
5.3 VSYNC/退出偏移
5.4 VSYNC 和 SF_VSYNC 偏移
六、帧同步
6.1 每秒帧数 (FPS) 节流干预
6.1.1 GameManagerService
6.1.2 SurfaceFlinger
七、多种刷新率
7.1 实现
7.2 配置群组
7.3 Composer API 更新
7.3.1 getDisplayVsyncPeriod
7.3.2 setActiveConfigWithConstraints
desiredTimeNanos
seamlessRequired
7.3.3 onVsyncPeriodTimingChanged [callback]
7.4 平台如何决定变换刷新频率?
7.5 Frame Rate API
7.6 开发者选项
一、引言
Hardware Composer HAL是什么?怎么实现的呢?Composer HAL涉及到哪些概念呢?
二、概览
Hardware Composer HAL 用于确定铜鼓可用硬件来合成缓冲区的最有效方法。
Hardware Composer HAL 作为HAL是基于特定设备的。通常是由显示硬件OEM厂商完成。
一部普通Android手机,其屏幕方向为纵向,状态栏在顶部,导航栏在底部,其他区域显示应用的内容。这3块单独的内容对应的是3个图形缓冲区,那么,可以考虑如下面2种方式来处理合成:
第一种:将应用内容渲染到暂存buffer,然后在其上渲染状态栏,其下渲染导航栏,最后将这个buffer传送给显示硬件。
第二种:将三个缓冲区全部传送到显示硬件,并指示它从不同的缓冲区读取屏幕不同部分的数据。
这第一种就是我们采用GLES,通过GPU来渲染合成的。第二种就是HWC硬件方式合成的。硬件合成的方式就可以显著提高效率。
由于显示处理器功能差异很大。Overlay的数量以及对定位和重叠的限制很难通过API来表达。为了完成这些工作,HWC执行如下动作:
1)Surfaceflinger向HWC提供一个所有层的列表,询问HWC哪些是可以处理的
2)HWC的响应是:将每个层标记Device(HWC)或者Client(GPU)合成。
3)Surfaceflinger会先把所有标记成Client的图层通过GPU来完成合成,合成后的结果(缓存)给到HWC,让HWC将此缓存以及标记成Device的图层一起处理完成。
一般情况下怎么处理才能实现最佳性能呢?
当屏幕的内容没有变化时,Overlay planes 的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。
这种情况,HWC就可以请求GLES来合成一部分或者全部,然后保留合成后的缓冲区。
如果Surfaceflinger请求合成同一组buffers,HWC就可以直接给先前合成好的buffer,这样就可以减少设备空闲耗电,从而提升续航
Android设备典型的时支持4个Overlay planes,如果需要合成多于4个Overlay planes的层数时,系统就需要对其中一些layer使用GLES来合成。这也意味着应用使用的层数会对能耗和性能产生重大的影响。
小结:大概介绍了下Android系统屏幕的内容合成的可行方式,通过2种可行的处理方式引出HWC要成处理需要的流程的3个步骤。同时介绍了省电提升续航的思路。
三、实现HWC
3.1 为什么是HWC?
HWC HAL是用于合成从Surfaceflinger接收到的图层,而分担GLES和GPU执行合成的压力,提升整体性能和效率
HWC可以抽象出 overlays 和 2D blitter 对象来合成Surface,或者与特定窗口硬件通信来合成窗口。
使用HWC来合成该窗口而不是用surfaceflinger通过GPU来合成原因?
1)大部分GPU没有为合成优化
2)当使用Surfaceflinger通过GPU来合成layers 时,应用是无法用GPU来做渲染的工作的
3.2 HWC的支持需求
HWC的实现需要能支持下面几个方面的需求:
1)至少能支持4个overlays:(statusbar systembar app wallpaper/background)
2)大于屏幕的Layers,如:壁纸
3)同时预乘的每像素 Alpha 混合和每平面 Alpha 混合
4)播放受保护视频的硬件路径
5)支持RGBA的顺序打包,YUV的格式,tilling,swizzling,stride 属性(平铺、重排和步幅属性)
3.3 HWC的实现思路
要实现HWC,可以按照下面的思路展开:
1)实现一个非运行的HWC,并将所有的合成工作发送到GLES
2)实现一种算法,以逐步将合成委托给HWC。例如仅将前3个或者前4个Surface委托给HWC的 overlay 硬件合成的方式就可以显著提高效率。
3)优化HWC。包括:
a 选择最大限度提高从GPU移除的负载的Surface,并将其发送给HWC。
b 检测屏幕是否正在更新。如果不是则将合成委托给GLES而不用HWC,这样可以省电。当屏幕更新时,继续将合成委托给到HWC。
c 为常见用例做准备,例如:
主屏幕,包括:状态栏,系统蓝,应用窗口,动态壁纸
纵向和横向模式下的全屏游戏
带有字幕和播放空间的全屏视频
受保护的视频播放
分屏多窗口
注意:目标是提升性能降低功耗,所以用例要尽可能针对常规可预测的用例,而非边缘用例。
3.4 HWC的基元
HWC提供两个基元Layer 和 display表示合成工作及其与屏幕硬件的交互。
HWC还提供VSYNC的控制,以及对Surfaceflinger的回调。回调是用来通知Surfaceflinger有VSYNC事件发生。
3.5 HIDL接口
Android 8.0 及更高版本使用Composer HAL 的 HIDL 接口,用于在 HWC 和 SurfaceFlinger 之间绑定 IPC。该HIDLjie'kou 取代了旧版 hwcomposer2.h 接口。如果供应商提供 HWC 的Composer HAL 新版实现,则Composer HAL 将直接接受来自 SurfaceFlinger 的 HIDL 调用。如果供应商提供 HWC 的旧版实现,Composer HAL 将从 hwcomposer2.h 加载函数指针,从而将 HIDL 调用转发到函数指针调用中。
HWC 提供相应函数包括:
a 确定给定屏幕的属性;
b 在不同的屏幕配置(例如 4k 或 1080p 分辨率)和颜色模式(例如原生颜色或真正的sRGB)之间切换;
c 打开、关闭屏幕或将其切换到低功率模式(如果支持)。
3.6 函数指针
如果厂商直接实现了 Composer HAL(新版实现,就是实现了HWC的 HIDL接口),那么Surfaceflinger就可以通过HIDL IPC的方式调用它的函数。
例如,要创建图层,SurfaceFlinger 会在混合渲染器 HAL 上调用 createLayer()。
如果厂商实现的是 hwcomposer2.h 接口(旧版实现),Composer HAL会调用 hwcomposer2.h 函数指针。
在 hwcomposer2.h 注释中,HWC 接口函数由 lowerCamelCase 名称引用,这些名称并未作为命名的字段存在于接口中。几乎每个函数都是通过使用 hwc2_device_t 提供的 getFunction 请求函数指针来进行加载。
例如,函数 createLayer 是一个 HWC2_PFN_CREATE_LAYER 类型的函数指针,当枚举值 HWC2_FUNCTION_CREATE_LAYER 传递到 getFunction 中时便会返回该指针。
有关Composer HAL 函数和 HWC 直通式函数的详细文档,请参阅:
https://android.googlesource.com/platform/hardware/interfaces/+/master/graphics/composer
有关 HWC 函数指针的详细文档,请参阅头文件:
https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/hwcomposer2.h
3.7 图层和屏幕句柄
Layer and display 是通过由HWC生成的句柄控制。这些句柄对 SurfaceFlinger 而言是不透明的。当 SurfaceFlinger 要创建新图层时,它会调用 createLayer,该函数会针对直接实现返回 Layer 类型,或者针对直通式实现 返回 hwc2_layer_t 类型。
当 SurfaceFlinger 要修改该图层的属性时,它会将该 hwc2_layer_t 值以及进行修改所需的任何其他信息传递给相应的修改函数。hwc2_layer_t 类型的大小足以容纳一个指针或一个索引。
物理屏幕是通过热插拔创建的。在热插拔物理屏幕时,HWC 会创建一个句柄并通过热插拔回调将该句柄传递给 SurfaceFlinger。
虚拟屏幕是由通过调用 createVirtualDisplay() 来请求屏幕的 SurfaceFlinger 创建的。如果 HWC 支持虚拟屏幕合成,则会返回一个句柄。然后,SurfaceFlinger 将屏幕的合成工作委托给 HWC。如果 HWC 不支持虚拟屏幕合成,则 SurfaceFlinger 会创建句柄并合成屏幕。
3.8 屏幕合成操作
如果 SurfaceFlinger 具有可合成的新内容,则会唤醒,且每个 VSYNC 唤醒一次。该新内容可以是来自应用的新图像缓冲区,也可以是一个或多个图层的属性更改。
当 SurfaceFlinger 唤醒时,它将执行以下步骤:
1)处理事务(如果存在)。
2)如果存在新的图形缓冲区,则将其锁定。
3)如果步骤 1 或 2 导致显示内容更改,则执行新的合成。
为了执行新的合成,SurfaceFlinger 会创建和销毁图层或修改图层状态(如适用)。
它还会使用 setLayerBuffer 或 setLayerColor 等调用,用图层的当前内容来更新图层。
更新所有图层之后,SurfaceFlinger 会调用 validateDisplay,以告诉 HWC 检查各个图层的状态,并确定如何进行合成。
在默认情况下,SurfaceFlinger 会尝试配置每个图层,以使相应图层由 HWC 进行合成。
在某些情况下,SurfaceFlinger 会通过 GPU 回退来合成图层。
在调用 validateDisplay 之后,SurfaceFlinger 会调用 getChangedCompositionTypes,以查看 HWC 是否需要在执行合成之前更改任何图层的合成类型。如果有更改,SurfaceFlinger 就需要调用 acceptDisplayChanges来处理这些更改
如果已将任何图层标记为进行 SurfaceFlinger 合成,SurfaceFlinger 会将这些图层合成到目标缓冲区中。然后,SurfaceFlinger 通过调用 setClientTarget 将该缓冲区提供给屏幕,以便缓冲区可以在屏幕上显示,或者进一步与未标记为进行 SurfaceFlinger 合成的图层合成。如果没有将任何图层标记为进行 SurfaceFlinger 合成,则 SurfaceFlinger 会跳过合成步骤。
最后,SurfaceFlinger 会调用 presentDisplay,以告知 HWC 完成合成过程并显示最终结果。
3.9 多个屏幕
Android 10 支持多个物理屏幕。在设计要在 Android 7.0 及更高版本上使用的 HWC 实现时,还存在一些 HWC 定义中并不存在的限制:假设只有一个内部屏幕。内部屏幕是初始热插拔在启动期间报告的屏幕。内部屏幕在热插拔后无法断开连接。
在设备的正常操作期间,除了内部屏幕之外,还可以热插拔任意数量的外部屏幕。
该框架假设,在热插拔第一个内部屏幕之后,所有热插拔操作都是在外部屏幕上进行,因此如果添加了更多内部屏幕,它们将被错误地归类为 Display.TYPE_HDMI,而不是 Display.TYPE_BUILT_IN。
尽管上述 SurfaceFlinger 操作按屏幕执行,但会对所有活动的屏幕依序执行这些操作,即使只更新了一个屏幕的内容也不例外。
例如,如果更新了外部屏幕,则顺序为:
// In Android 9 and lower:// Update state for internal display
// Update state for external display
validateDisplay(<internal display>)
validateDisplay(<external display>)
presentDisplay(<internal display>)
presentDisplay(<external display>)
// In Android 10 and higher:// Update state for internal display
// Update state for external display
validateInternal(<internal display>)
presentInternal(<internal display>)
validateExternal(<external display>)
presentExternal(<external display>)
3.10 虚拟屏幕合成
虚拟屏幕合成与外部屏幕合成类似。虚拟屏幕合成与物理屏幕合成之间的区别在于,虚拟屏幕将输出发送到 Gralloc 缓冲区,而不是发送到屏幕。硬件混合渲染器 (HWC) 将输出内容写入缓冲区,提供完成栅栏信号,并将缓冲区发送给使用方(例如视频编码器、GPU、CPU 等)。如果显示通道写入内存,虚拟屏幕便可使用 2D/位块传送器或叠加层。
3.10.1 模式
在 SurfaceFlinger 调用HWC 的 validateDisplay() 方法之后,每个帧处于以下三种模式之一:
1)GLES - GPU 合成所有图层,直接写入输出缓冲区。HWC 不参与合成。
2)MIXED - GPU 将一些图层合成到帧缓冲区,由 HWC 合成帧缓冲区和剩余的图层,直接写入输出缓冲区。
3)HWC - HWC 合成所有图层并直接写入输出缓冲区。
3.10.2 输出格式
虚拟屏幕的缓冲区输出格式取决于其模式:
1)GLES 模式 - EGL 驱动程序在 dequeueBuffer() 中设置输出缓冲区格式,通常为RGBA_8888。使用方必须能够接受该驱动程序设置的输出格式,否则便无法读取缓冲区。
2)MIXED 和 HWC 模式 - 如果使用方需要 CPU 访问权限,则由使用方设置格式。否则,格式将为 IMPLEMENTATION_DEFINED,Gralloc 会根据使用标记设置最佳格式。例如,如果使用方是视频编码器,则 Gralloc 会设置 YCbCr 格式,而 HWC 可以高效地写入该格式。
注意:Android 10 取消了有关 eglSwapBuffers() 在渲染开始后将缓冲区移出队列的要求。缓冲区可能会立即退出队列。
3.11 同步fence
同步fence是Android图形系统的关键部分。fence允许 CPU 工作与并行的 GPU 工作相互独立进行,仅在存在真正的依赖关系时才会阻塞。例如:,当应用提交在 GPU 上生成的缓冲区时,它还将提交一个同步fence对象。当 GPU 完成写入缓冲区的操作时,该fence会变为有信号状态。
HWC 要求 GPU 在显示缓冲区之前完成写入缓冲区的操作。同步fence通过包含缓冲区的图形管道传递,并在缓冲区写入完成时发出信号。在显示缓冲区之前,HWC 会检查同步fence是否已发出信号,如果有,则显示缓冲区。
3.12 热插拔处理
3.12.1 概念理解
显示功能:指的是如显示模式和支持的 HDR 类型等显示相关的功能
外部连接:指的是如使用 HDMI 或 DisplayPort 连接等
例如这样的一个场景与设备:Android 电视机顶盒 (STB) 和 机顶盒 (OTT) 设备,当用户从一个显示器切换到另一个显示器;在没有连接显示器的情况下启动设备;
Android 12 及更高版本框架可以 处理热插拔 和 动态显示功能。那么,
显示热插拔是怎么处理的?
显示热插拔时,Composer HAL 实现中显示功能是怎么更改的?
显示热插拔情况下管理关联的帧缓冲区并防止竞争条件?
这些问题会接下来进行说明。
注意: Android API 和框架中使用的术语显示模式等同于 Composer HAL 中使用的术语显示配置。
3.12.2 更新显示功能:
Android 框架如何处理由 Composer HAL 发起的显示功能更改?
在 Android 可以正确处理显示功能的更改之前,OEM 必须实现 Composer HAL,以便它使用onHotplug(display, connection=CONNECTED)通知框架显示功能的任何更改。
实现Composer HAL的onHotplug(display, connection=CONNECTED)后,Android 会按如下方式
3.12.3 处理显示功能的更改:
1)框架接收通知:在检测到显示功能发生变化时,框架会收到一个onHotplug(display, connection=CONNECTED)通知。
2)框架丢弃旧的显示状态创建新的显示状态:收到通知后,框架会丢弃其显示状态,并使用getActiveConfig 、 getDisplayConfigs 、 getDisplayAttribute 、 getColorModes 、 getHdrCapabilities和getDisplayCapabilities方法使用 HAL 的新功能重新创建它。
3)框架通知应用程序:框架重新创建新的显示状态后,它会将onDisplayChanged回调发送到正在侦听此类事件的应用程序。
注意:确保getDisplayConfigs准确报告所有支持的显示刷新率和分辨率。
3.12.4 常见的连接场景处理
3.12.5 使用顺序配置 ID 来防止竞争条件
如果 Composer HAL 在框架调用setActiveConfig或setActiveConfigWithConstraints的同时更新支持的显示配置,则可能会出现竞争条件。
解决方案:实现 Composer HAL 以使用顺序 ID 并防止此问题。
竞争条件可能如何发生?
考虑以下事件序列,当新的顺序 ID 未分配给新的显示配置时,会导致竞争条件:
1)支持的显示配置 ID 为:
id=1 , 1080x1920 60 赫兹
id=2 , 1080x1920 50赫兹
2)框架调用setActiveConfig(display, config=1)
3)同时,Composer HAL 处理显示配置的更改并将其内部状态更新为一组新的显示配置,如下所示:
id=1 , 2160x3840 60赫兹
id=2 , 2160x3840 50赫兹
id=3 , 1080x1920 60 赫兹
id=4 , 1080x1920 50赫兹
4)Composer HAL 向框架发送一个onHotplug事件,以通知支持的模式集已更改。
5)Composer HAL 接收setActiveConfig(display, config=1) (来自第 2 步)
6)HAL 解释框架已请求将配置更改为2160x3840 60 Hz ,尽管实际上需要1080x1920 60 Hz 。
使用非顺序 ID 分配的过程在这里以对所需配置更改的误解结束。
配置 Composer HAL 以使用顺序 ID
为避免此类竞争条件,OEM 必须按如下方式实现 Composer HAL:
1)当 Composer HAL 更新支持的显示配置时,它会为新的显示配置分配新的顺序 ID。
2)当框架使用无效的配置 ID 调用setActiveConfig或setActiveConfigWithConstraints时,Composer HAL 会忽略该调用。
这些步骤用于防止竞态条件,如下面的讨论所示。
重新以上述事件序列为例,当新的顺序 ID 分配给新的显示配置时:
1)支持的显示配置 ID 为:
id=1 , 1080x1920 60 赫兹
id=2 , 1080x1920 50赫兹
2)框架调用setActiveConfig(display, config=1)
3)同时,Composer HAL 处理显示配置的更改并将其内部状态更新为一组新的显示配置,如下所示:
id=3 , 2160x3840 60赫兹
id=4 , 2160x3840 50赫兹
id=5 , 1080x1920 60 赫兹
id=6 , 1080x1920 50赫兹
4)Composer HAL 向框架发送一个onHotplug事件,以通知支持的模式集已更改。
5)Composer HAL 接收setActiveConfig(display, config=1) (来自第 2 步)
6)Composer HAL 忽略调用,因为 ID 不再有效。
7)该框架接收并处理步骤 4 中的onHotplug事件。它使用函数getDisplayConfigs和getDisplayAttribute调用 Composer HAL。通过这些功能,框架为所需的分辨率和刷新率 1080x1920 和 60 Hz 识别新 ID (5)。
8)框架发送另一个更新后的 ID 为 5 的setActiveConfig事件。
9)Composer HAL 从第 5 步接收setActiveConfig(display, config=5) 。
10)HAL 正确解释框架已请求将配置更改为 1080x1920 60 Hz。
如上例所示,使用顺序 ID 分配的过程可确保防止竞争条件并更新正确的显示配置更改。
适用于Hardware Composer HAL 的 AIDL
从 Android T(AOSP 实验版)起,我们开始使用 AIDL 定义混合渲染器 (HWC) HAL,还废弃了 HIDL 版本 android.hardware.graphics.composer@2.1 至 android.hardware.graphics.composer@2.4。
本页将介绍适用于 HWC 的 AIDL HAL 与 HIDL HAL 之间的区别,以及 AIDL HAL 的实现和测试。
AIDL HAL 和 HIDL HAL 之间的区别
新的 AIDL Composer HAL 名为 android.hardware.graphics.composer3,在 IComposer.aidl 中定义
它公开提供了一个类似于 HIDL HAL android.hardware.graphics.composer@2.4 的 API,但做出了以下更改:
1)移除了快速消息队列 (FMQ),改为使用 Parcelable 命令
2)移除了 IComposerClient.getClientTargetSupport,因为此方法没有活跃的客户端。
3)用浮点数(而非字节)表示颜色,以便更好地与 Android 中的上层图形堆栈(如 ASurfaceTransaction_setColor 中所定义)保持一致。
4)添加了用于控制 HDR 内容的新字段。
5)在 Composition.aidl 中新增了用于装饰屏幕的新合成类型 DISPLAY_DECORATION。
6)为 DisplayCommand.aidl 添加了新的 expectedPresentTime 字段
7)添加了用于控制启动显示配置的新 API
8)新增了用于控制显示空闲定时器的新 API
3.13 客户端帧缓冲区管理
从 Android 13 开始,每当屏幕分辨率发生变化时,系统都会分配在客户端合成期间使用的新的帧缓冲区。分辨率变化后,SurfaceFlinger 会对下一个“失效”周期执行此分配。
3.13.1 分辨率切换期间的帧缓冲区管理
分辨率会由于以下两种情况之一而发生变化:
1)由硬件混合渲染器 (HWC) 发起的热插拔事件,从一个外部屏幕切换到具有不同默认分辨率的另一个外部屏幕时发生。
在热插拔事件期间,旧的屏幕数据取消分配后,系统会释放旧的帧缓冲区的句柄。
2)由 SurfaceFlinger 发起的显示模式切换,用户使用用户设置更改分辨率时或应用使用 preferredDisplayModeId 更改分辨率时发生。
在显示模式切换期间,SurfaceFlinger 会在调用 setActiveConfig 或 setActiveConfigWithConstraints 之前释放现有客户端帧缓冲区的句柄。
为了避免出现灾难性问题(如内存碎片化),在没有为新旧帧缓冲区预留足够内存的设备上,HWC 必须停止使用旧帧缓冲区并释放这些帧缓冲区的所有句柄,如下所示:
1)对于热插拔事件,调用 onHotplug 之前立即执行上述操作。
2)对于模式切换,调用 setActiveConfig 或 setActiveConfigWithConstraints 后立即执行上述操作。
释放句柄会导致帧缓冲区内存会被完全取消分配,SurfaceFlinger 无法在下一个“失效”周期内执行新帧缓冲区分配。
四、Layers and displays
层和屏幕是两个基元,用于表示合成工作以及与屏幕硬件的交互。
层是合成的最重要单元。层是 Surface 和 SurfaceControl 实例的组合。
每个层都有一组属性,用于定义它与其他层的交互方式。层属性如下表所述。
定位:定义层在其屏幕上的显示位置。包括层边缘的位置及其相对于其他层的 Z 顺序(指示该层在其他层之前还是之后)等信息。
4.1 Displays
屏幕是合成的另一个重要单元。系统可以具有多个屏幕,并且在正常系统操作期间可以添加或移除屏幕。屏幕应 HWC 或框架的请求添加/移除。 在外部屏幕与设备连接或断开连接时(称为热插拔),HWC 设备请求添加或移除屏幕。客户端请求虚拟屏幕,其内容会渲染到离屏缓冲区(而不是实体屏幕)。
4.2 虚拟屏幕
SurfaceFlinger 支持一个内部屏幕(内置于手机或平板电脑中的屏幕)、一个外部屏幕(如通过 HDMI 连接的电视)以及一个或多个令合成的输出在系统中可用的虚拟屏幕。
虚拟屏幕可用于记录屏幕信息或通过网络发送屏幕信息。为虚拟屏幕生成的帧会写入 BufferQueue。
虚拟屏幕可以与主屏幕共享相同的一组层(层堆叠),也可拥有自己的一组层。
虚拟屏幕没有 VSYNC,因此内部屏幕的 VSYNC 可为所有屏幕触发合成。
在支持虚拟屏幕的 HWC 实现中,虚拟屏幕可以与 OpenGL ES (GLES)、HWC 或者 GLES 及 HWC 合成在一起。
在不支持虚拟屏幕的实现中,虚拟屏幕始终使用 GLES 进行合成。
五、VSYNC
VSYNC 信号可同步显示流水线。
Android的显示流水线一般有3部分组成:
1)应用渲染
2)SurfaceFlinger 合成
3)用于在屏幕上显示图像的硬件混合渲染器 (HWC) 。
VSYNC 可同步应用唤醒以开始渲染的时间、SurfaceFlinger 唤醒以合成屏幕的时间以及屏幕刷新周期。这种同步可以消除卡顿,并提升图形的视觉表现。
HWC 可生成 VSYNC 事件并通过回调将事件发送到 SurfaceFlinger:
typedef void (*HWC2_PFN_VSYNC)(hwc2_callback_data_t callbackData,hwc2_display_t display, int64_t timestamp);
注意:HWC 从 HAL_PRIORITY_URGENT_DISPLAY 的线程触发 hwc2_callback_data_t,延迟时间尽可能短,通常小于 0.5 毫秒。
SurfaceFlinger 通过调用 setVsyncEnabled 来控制 HWC 是否生成 VSYNC 事件。
SurfaceFlinger 使 setVsyncEnabled 能够生成 VSYNC 事件,因此它可以与屏幕的刷新周期同步。
当 SurfaceFlinger 同步到屏幕刷新周期时,SurfaceFlinger 会停用 setVsyncEnabled 以阻止 HWC 生成 VSYNC 事件。
如果 SurfaceFlinger 检测到实际 VSYNC 与它先前建立的 VSYNC 之间存在差异,则 SurfaceFlinger 会重新启动 VSYNC 事件生成过程。
5.1 VSYNC 偏移
同步 应用和SurfaceFlinger 渲染循环 应同步到硬件 VSYNC。
在 VSYNC 事件中,屏幕开始显示帧 N,而 SurfaceFlinger 开始为帧 N+1 合成窗口。应用处理等待的输入并生成帧 N+2。
与 VSYNC 同步会实现一致的延迟时间。它可以减少 应用和 SurfaceFlinger 中的错误,并最大限度减小相位内外屏幕之间的偏移。这要假定 应用和 SurfaceFlinger 的每帧时间没有很大变化。延迟至少为两帧。
为了解决此问题,可以通过使 应用和合成信号 与 硬件 VSYNC 相关,从而利用 VSYNC 偏移减少输入设备到屏幕的延迟。这是有可能的,因为应用加合成通常需要不到 33 毫秒的时间。
VSYNC 偏移的结果是具有相同周期和偏移相位的三个信号:
1)HW_VSYNC_0 - 屏幕开始显示下一帧。
2)VSYNC - 应用读取输入内容并生成下一帧。
3)SF_VSYNC - SurfaceFlinger 开始为下一帧进行合成。
通过 VSYNC 偏移,SurfaceFlinger 接收缓冲区并合成帧,而应用同时处理输入内容并渲染帧。
注意:VSYNC 偏移会缩短可用于应用和合成的时间,因此增加了出错几率。
5.2 DispSync
DispSync 维护屏幕基于硬件的周期性 VSYNC 事件的模型,并使用该模型在硬件 VSYNC 事件的特定相位偏移处执行回调。
DispSync 是一个软件锁相回路 (PLL),它可以生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC 和 SF_VSYNC 信号,即使没有来自硬件 VSYNC 的偏移也是如此。
图 上图 为DispSync 流程
DispSync 具有以下特点:
参考 - HW_VSYNC_0。
输出 - VSYNC 和 SF_VSYNC。
反馈 - 自硬件混合渲染器的退出栅栏有信号状态时间戳。
5.3 VSYNC/退出偏移
退出fences 的有信号状态时间戳必须 与 HW VSYNC 相符,即使在不使用偏移相位的设备上也是如此。否则,实际造成的错误会更加严重。智能面板通常有一个增量:退出fences 是对显示内存进行直接内存访问 (DMA) 的终点,但是实际的显示切换和 HW VSYNC 会晚一段时间。
PRESENT_TIME_OFFSET_FROM_VSYNC_NS 在设备的 BoardConfig.mk makefile 中设置。它取决于屏幕控制器和面板特性。从退出fence 时间戳到 HW VSYNC 信号的时间以纳秒为单位进行测量。
5.4 VSYNC 和 SF_VSYNC 偏移
VSYNC_EVENT_PHASE_OFFSET_NS 和 SF_VSYNC_EVENT_PHASE_OFFSET_NS 根据高负载使用情形进行了保守设置。
例如:在窗口过渡期间进行部分 GPU 合成或 Chrome 滚动显示包含动画的网页。这些偏移允许较长的应用渲染时间和较长的 GPU 合成时间。
超过一两毫秒的延迟时间是非常明显的。为了最大限度地缩短延迟时间而不显著增加错误计数,请集成彻底的自动化错误测试。(XTS测试)
注意:VSYNC 和 SF_VSYNC 偏移同样在设备的 BoardConfig.mk 文件中配置。两个设置都是 HW_VSYNC_0 之后以纳秒为单位的偏移,默认值为零(如未设置的话),也可以为负值。
六、帧同步
Android Frame Pacing 库(也称为 Swappy)是 Android Game SDK 的一部分。它可帮助 OpenGL 和 Vulkan 游戏在 Android 上实现流畅的渲染和正确的帧同步。
帧同步是指:游戏的逻辑和渲染循环 与 操作系统的显示子系统和底层显示硬件 之间的同步。
Android 显示子系统旨在避免某些视觉假象,例如:画面撕裂。
显示子系统可通过执行以下操作避免画面撕裂:
1)在内部缓冲之前的帧
2)检测延迟帧的提交情况
3)当检测到延迟帧时继续显示当前帧
帧展示时间不一致是因为 游戏渲染循环的运行速率 与 本机显示硬件支持的速率不同。如果底层显示硬件的游戏渲染循环运行速度过慢,会产生问题,导致展示时间不一致。例如:当运行速度为 30 FPS 的游戏尝试在原生支持 60 FPS 的设备上渲染时,游戏的渲染循环会导致同一帧在屏幕上又重复显示 16 毫秒。这种类型的中断会导致帧时间严重不一致,例如帧时间可能为 33 ms、16 ms、49 ms 等。过于复杂的场景会让该问题更复杂,因为它们会导致丢帧。
Frame Pacing 库可执行以下任务:
1)补偿由于游戏帧较短而出现的卡顿现象。
添加呈现时间戳,以便按时呈现帧,而不是提前呈现。
使用呈现时间戳扩展 EGL_ANDROID_presentation_time 和 VK_GOOGLE_display_timing。
2)对导致卡顿和延迟的长帧使用同步fence。
将 wait 注入应用,让显示管道能够跟上进度,而不会积累背压。
使用同步fence(EGL_KHR_fence_sync 和 VkFence)。
3)如果设备支持多个刷新频率,会选择其中一个以便提供灵活流畅的呈现效果。
4)基于帧统计信息 提供用于调试和性能分析的统计信息。
如需了解如何根据需求将库配置为以不同模式运行,请参阅:
支持的操作模式:https://developer.android.com/games/sdk/frame-pacing/?hl=zh-cn#supported_operating_modes
如需使用 OpenGL 渲染程序或 Vulkan 渲染程序实现,请参阅以下文章:
将 Android Frame Pacing 集成至 OpenGL 渲染程序:https://developer.android.com/games/sdk/frame-pacing/opengl/?hl=zh-cn
将 Android Frame Pacing 集成至 Vulkan 渲染程序:https://developer.android.com/games/sdk/frame-pacing/vulkan/?hl=zh-cn
如需了解详情,请参阅实现适当的帧同步:https://developer.android.com/games/sdk/frame-pacing/?hl=zh-cn
6.1 每秒帧数 (FPS) 节流干预
通过 FPS 节流干预,游戏只需使用平台端更改即可以保持适当的 FPS 速度,而无需开发者执行任何操作。
FPS 节流干预的实现使用以下组件:
6.1.1 GameManagerService
GameManagerService 组件会保留每位用户和每个游戏的游戏模式和游戏干预信息。FPS 信息存储在 GameManagerService 中,并在每个用户个人资料的 <PACKAGE_NAME, Interventions> 映射中存储其他干预信息(例如分辨率缩放系数)。 当游戏模式更改或干预更新时,可以访问 FPS 信息。UID 对于每个 PACKAGE_NAME 和用户都是唯一的,可以进一步转换为 <UID, Frame Rate> 对以发送到 SurfaceFlinger。
6.1.2 SurfaceFlinger
SurfaceFlinger 组件现已支持对应用的 FPS 进行节流,但前提是帧速率必须是显示刷新率的除数。 在发生 vsync 的情况下,SurfaceFlinger 会验证 vsync 时间戳是否与应用的帧速率处于同一相位,以检查受到节流的应用的 vsync 有效性。如果帧速率未与 vsync 处于同一相位,则 SurfaceFlinger 会保持该帧,直到帧速率和 vsync 处于同相位。
下图介绍了 GameManagerService 与 SurfaceFlinger 之间的交互:
SurfaceFinger 会维护一个 <UID, Frame Rate> 对映射,以设置新的帧速率节流优先级。UID 在用户和游戏之间是唯一的,因此同一台设备上的每位用户都可以对同一游戏采用不同的帧速率设置。为了对游戏的帧速率进行节流,GameServiceManager 会调用 SurfaceFlinger 来替换 UID 的帧速率。借助此机制,SurfaceFlinger 会在游戏模式更改或干预更新时更新映射。SurfaceFlinger 通过相应地锁定缓冲区来处理 FPS 更改。
七、多种刷新率
Android 11 增加了对具有多种刷新率的设备的支持。此功能包含三个主要组成部分:
1)android.hardware.graphics.composer@2.4 中引入的新 HAL API。
2)平台代码,用于解析不同刷新率的设备配置并设置所需的刷新率
3)新增的 SDK 和 NDK API,使应用可以设置所需的帧速率
7.1 实现
我们在 android.hardware.graphics.composer@2.4 HAL 中添加了对刷新率切换的专属支持。早期版本的 Composer HAL 对刷新率切换的支持有限。
7.2 配置群组
IComposerClient::Attribute 中添加了新属性 CONFIG_GROUP,您可以使用 getDisplayAttribute_2_4 API 对其进行查询。
通过此属性,供应商可以将屏幕配置组合在一起。在大多数情况下,同一组中的配置允许无缝切换这些配置。
平台使用配置群组来区分可以相互切换的不同配置,用以切换刷新率而不是其他配置属性。
下面的示例演示了在支持 4 种屏幕配置的设备上使用群组的好处:
1080p 60Hz
1080p 90Hz
1080i 72Hz
1080i 48Hz
尽管设备支持 48Hz、60Hz、72Hz 和 90Hz 的刷新率,但屏幕会在不同模式下运行,并且从 60Hz 切换到 72Hz 时,会导致屏幕配置从 1080p 更改为 1080i,而这可能并不是想要的行为。这一点可以通过配置群组来解决。将 60Hz 和 90Hz 放到一个配置群组中,而将 48Hz 和 72Hz 放入另一个配置群组中。平台知道,它可以在 60Hz 到 90Hz 之间切换,还可以在 48Hz 和 72Hz 之间切换,但不能在 60Hz 和 72Hz 之间切换,因为这会导致配置更改,而不仅仅是简单地变换刷新率。
7.3 Composer API 更新
7.3.1 getDisplayVsyncPeriod
为了在变换刷新率时更好地进行控制并提高可预测性,我们添加了 getDisplayVsyncPeriod 。
getDisplayVsyncPeriod 会返回屏幕的当前刷新率(以 Vsync 周期为单位)。
当在刷新率之间进行转换,而平台需要获取当前的刷新率以决定何时启动下一帧时,这尤其有用。
7.3.2 setActiveConfigWithConstraints
setActiveConfigWithConstraints 方法是现有 setActiveConfig 方法的一个新扩展,可提供有关配置更改的更多信息。
限制条件以 vsyncPeriodChangeConstraints 参数的一部分提供,并包含以下参数。
desiredTimeNanos
CLOCK_MONOTONIC 中的时间,在该时间过后,vsync 周期可以发生变化(即,vsync 周期在此时间之前不得发生变化)。
当平台需要提前计划刷新率更改,但它在队列中已有一些要呈现的缓冲区时,这会非常有用。
平台会考虑这些缓冲区并相应地设置此时间,并确保刷新率转换尽可能顺利。
seamlessRequired
如果为 true,则要求 vsync 周期无缝地发生变化,而不能产生任何明显的视觉痕迹。
当因内容发生变化而需要变换刷新率时(例如,设备处于空闲状态,此时动画开始播放),平台则会使用此标志。
这使供应商可以避免进行可能引起明显的视觉痕迹的某些配置更改。
如果配置无法无缝更改,并且 seamlessRequired 设置为 true,则实现应返回 SEAMLESS_NOT_POSSIBLE 作为返回代码,并在能够无缝完成相同的配置更改时调用新的 onSeamlessPossible 回调。
实现成功后,系统将返回一个 VsyncPeriodChangeTimeline,告知平台预计何时会变换刷新率。
当新屏幕要开始以新的 vsync 周期刷新时,newVsyncAppliedTimeNanos 参数需要设置为 CLOCK_MONOTONIC 中的时间。
此标志与 desiredTimeNanos 结合使用时,平台可以预先规划刷新率开关,并提前使应用进入切换到新刷新率的倒计时。这样可以实现刷新频率的无缝过渡。
某些实现要求在发送刷新频率之前发送刷新帧。为此,HAL 采用了两个参数:一个是refreshRequired,用于指示需要某个刷新帧;另一个是 refreshTimeNanos,用于指示需要在其后发送刷新帧的第一个 vsync。
7.3.3 onVsyncPeriodTimingChanged [callback]
可由 HAL 调用的新回调,用于告知平台时间轴的某些参数发生了更改,平台需要调整其时间轴。
如果由于某种原因,旧的时间轴因 HAL 的处理时间过长或者刷新帧延迟而错过,则应调用此回调。
7.4 平台如何决定变换刷新频率?
刷新率选择在以下两个系统服务中发生:
DisplayManager
DisplayManager 用于设置与刷新率有关的上层策略。它会设置默认的屏幕配置,此配置与混合渲染器 HAL 配置相同。此外,它还会设置一个由最大值和最小值界定的范围,供 SurfaceFlinger 从中选出刷新率。
SurfaceFlinger
设置一个与默认配置位于同一配置群组且刷新率介于最小值/最大值范围内的配置,从而确定刷新率。
屏幕管理器会执行以下步骤来确定策略:
1)从 SurfaceFlinger 查询活跃配置,找出默认配置 ID
2)通过遍历系统条件来限制这个通过最小值和最大值界定的范围
a) 默认刷新率设置:默认刷新率值在 R.integer.config_defaultRefreshRate 配置叠加层中设置。该值用于确定动画和轻触互动的标准设备刷新率。
b) 峰值刷新率设置:峰值刷新率值是从 Settings.System.PEAK_REFRESH_RATE 读取的。该值在运行时更改,以反映当前设备设置(例如,从菜单选项更改)。默认值在 R.integer.config_defaultPeakRefreshRate 配置叠加层中设置。
c) 最小刷新频率设置:最小刷新频率值是从 Settings.System.MIN_REFRESH_RATE 读取的。该值可以在运行时更改,以反映当前设备设置(例如,从菜单选项更改)。默认值为 0,因此不存在默认的最小值。
d) 应用要求的 ModeId:应用可以设置 WindowManager.LayoutParams.preferredDisplayModeId 以反映屏幕应运行的首选配置。在大多数情况下,DisplayManager 会相应地设置默认配置 ID,并设置最小和最大刷新频率以匹配配置的刷新频率。
e) 省电模式:当设备处于低功耗模式(通过 Settings.Global.LOW_POWER_MODE. 指示)时,刷新率上限为 60Hz。
DisplayManager 设置策略后,SurfaceFlinger 会根据活跃层(将帧更新排入队列的层)设置刷新率。如果该层的所有者设置了帧速率,则 SurfaceFlinger 会尝试将刷新率设置为该帧速率的倍数。 例如,如果两个活跃层的帧速率设置为 24 和 60,则 SurfaceFlinger 会选择 120Hz(如果可用)。如果 SurfaceFlinger 无法使用此类刷新率,则会尝试选择对帧速率来说误差最小的刷新率。
如需了解详情,请参阅 developer.android.com 上的开发者文档:https://developer.android.com/media/optimize/performance/frame-rate
SurfaceFlinger 维护以下标志来控制刷新率的确定方式:
1)ro.surface_flinger.use_content_detection_for_refresh_rate: 如果已设置,系统会根据活跃层确定刷新率,即使未设置帧速率。SurfaceFlinger 持续运用启发法,通过关注连接到缓冲区的呈现时间戳,找出该层发布缓冲区的平均 fps。
2)ro.surface_flinger.set_touch_timer_ms:如果大于 0,则在配置的超时时间内有用户轻触屏幕时,将使用默认刷新率。此启发法完成时,便会对动画采用默认刷新率。
3)ro.surface_flinger.set_idle_timer_ms:如果大于 0,则在配置的超时时间内没有屏幕更新时,将使用最小刷新频率。
4)ro.surface_flinger.set_display_power_timer_ms:如果大于 0,则在配置的超时时间内开启屏幕(或退出 AOD)时,将使用默认刷新率。
7.5 Frame Rate API
应用可通过 Frame Rate API 将其预期帧速率告知 Android 平台,该 API 可在以 Android 11 为目标平台的应用中使用。
如需详细了解帧速率 API,请查看 developer.android.com 上的开发者文档:https://developer.android.com/media/optimize/performance/frame-rate
7.6 开发者选项
菜单中新增了一个开发者选项,它可以使用当前刷新率在屏幕上切换叠加层。
新选项位于设置 > 系统 > 开发者选项 > 显示刷新频率下。