0 前言
安卓系统的每帧画面其实都是由若干图层合并而成的,该操作称之为“图像合成”[3]:
图0.1 图像合成示意图[3]
本文将对合成方式和它们的性能对比进行说明。
1 安卓
安卓系统主要包含GPU和HWC两种合成方式,可通过开发者模式关闭和打开HWC合成:
图1.1
也可通过下面命令禁止/允许HWC合成:
adb shell service call SurfaceFlinger 1008 i32 1 // 禁止HWC合成
adb shell service call SurfaceFlinger 1008 i32 0 // 允许HWC合成
在禁止HWC合成的情况下只能走GPU合成,那么在否允许HWC合成的情况下是否就一定走HWC合成呢?答案是否定的,其影响因素有多个,其中最为主要的就是HWC硬件模块是否支持对该图层进行硬件合成。安卓模拟器直到29.0版本才模拟实现HWC硬件模块,同时安卓10的HWC HAL层提供了相应的软件支持,因此原生系统至少需要"模拟器29.0 + 安卓10"支持HWC合成,更早期的版本只能用GPU合成。
1.1 合成方式选择
SurfaceFlinger在创建图层时会为其选择对应的合成方式(如果禁止HWC合成所有图层均被强制设置为GPU合成,否则多数情况下选择HWC合成),但这并非最终的结果的,它需要交HWC HAL层进行校验,如果它判断出HWC硬件模块不支持对该图层进行HWC合成,则依然会将其强制改为GPU合成。
(1)安卓9
安卓9模拟器的HWC在对图层进行准备(校验)时,强制将所有图层的合成方式改为HWC_FRAMEBUFFER(对应的就是GPU合成)
static int hwc_prepare(hwc_composer_device_1_t* dev __unused, size_t numDisplays, hwc_display_contents_1_t** displays) {
...
for (size_t i = 0; i < contents->numHwLayers; i++) {
// We do not handle any layers, so set composition type of any non
// HWC_FRAMEBUFFER_TARGET layer to to HWC_FRAMEBUFFER.
if (contents->hwLayers[i].compositionType == HWC_FRAMEBUFFER_TARGET) {
continue;
}
contents->hwLayers[i].compositionType = HWC_FRAMEBUFFER; // 瞧这里,全部改为HWC_FRAMEBUFFER合成(即GPU合成)
}
return 0;
}
// @file: device/generic/goldfish/hwcomposer/hwcomposer.cpp
(2)安卓10
安卓10模拟器的HWC在对图层进行准备(校验)时,不再这么粗暴,而是允许部分图层进行HWC合成:
Error EmuHWC2::Display::validate(uint32_t* outNumTypes, uint32_t* outNumRequests) {
...
if (!mChanges) {
...
bool hostCompositionV1 = rcEnc->hasHostCompositionV1();
...
if (hostCompositionV1) {
// Support Device and SolidColor, otherwise, fallback all layers to Client
bool fallBack = false;
for (auto& layer : mLayers) {
...
// 只将以下3种类型的图层合成方式改回GPU合成,其余的保留为HWC合成
if (layer->getCompositionType() == Composition::Client ||
layer->getCompositionType() == Composition::Cursor ||
layer->getCompositionType() == Composition::Sideband) {
ALOGW("%s: layer %u CompositionType %d, fallback", __FUNCTION__, (uint32_t)layer->getId(), layer->getCompositionType());
fallBack = true;
break;
}
}
...
if (fallBack) {
for (auto& layer : mLayers) {
if (layer->getCompositionType() == Composition::Invalid) {
continue;
}
if (layer->getCompositionType() != Composition::Client) {
mChanges->addTypeChange(layer->getId(), Composition::Client);
}
}
}
}
...
}
// @file: device/generic/goldfish-opengl/system/hwc2/EmuHWC2.cpp
1.2 合成类型查看
可通过下面命令查看其各图层的合成方式:
adb shell dumpsys SurfaceFlinger
其结果中的“Display 0 HWC layers”部分的“Comp Type”列就是合成类型,其参数说明如下:
- Client:GPU合成
- Device:HWC合成
(1)安卓9
安卓9模拟器尚未实现HWC合成,通过上面命令查看其各图层合成方式如下:
图1.1 安卓9图层合成方式
可见其所有图层的合成方式均为Client(即GPU合成)。
(2)安卓10
安卓10模拟器开始支持HWC合成,通过上面命令查看其各图层合成方式如下:
图1.2 安卓10图层合成方式
可见其所有图层的合成方式均为DEVICE(即HWC合成)。
2 GPU合成
所谓GPU合成,就是通过GPU来实现0.1各图层的合成,安卓模拟器的GPU主要有两种实现方案:
2.1 软件模拟
所谓软件模拟即通过CPU来模拟实现OpenGL APIs,具体可以在安卓系统内部实现,也可以在安卓模拟器中实现。
(1)安卓内模拟实现OpenGL
安卓9通过Swiftshader(源码路径:)模拟实现了OpenGL APIs,此时Swiftshader进程运行于虚拟机内,它直接消耗的是虚拟机的CPU和内存资源(当然,最终都会转化为宿主机的资源)。
(2)模拟器模拟实现OpenGL
模拟器29.0支持间接的Swiftshader模式,也就是将原本运行于安卓虚拟机内部的Swiftshader迁移到模拟器中实现,它直接消耗的是宿主机的CPU和内存资源,性能更佳。
拓展:Swiftshader是有Google实现,此外还有其它OpenGL软件实现可供选择,例如LLVMpipe、Softpipe、OpenSWR等。
(3)对比
方案2性能远胜于方案1,安卓10开始逐步淘汰方案1。
2.2 GPU加速
安卓系统可将OpenGL APIs调用透传到宿主机,从而实现GPU加速。具体透传的方案有两种:
(1)模拟GPU
安卓模拟器(其是QEMU的封装和二次开发)模式实现GPU,它内部会将具体的OpenGL APIs转发由宿主机的GPU实现:
图2.1 模拟GPU[6]
上述方案是安卓模拟器29.0原生支持的方案,已经比较成熟。
(2)VirtIO-GPU
通过VirtIO技术将GPU透传给虚拟机:
图2.2 VirtIO-GPU[6]
原生QEMU已支持该方案,安装Ubuntu虚拟机可用该方案,Cuttlefish模拟器也支持了该方案,但安卓Goldfish模拟器尚未支持该方案。
(3)对比
理论上方案2的性能更优于方案1,但想要用于安卓模拟器,还需不少移植工作。
3 HWC合成
模拟器29.0内部实现了HWC合成的支持,安卓只需将多个图层交付给模拟器即可,下面是安卓模拟器图像合成的入口函数:
bool FrameBuffer::compose(uint32_t bufferSize, void* buffer, bool needPost) {
ComposeDevice* p = (ComposeDevice*)buffer;
AutoLock mutex(m_lock);
switch (p->version) {
case 1: {
Post composeCmd;
composeCmd.composeVersion = 1;
composeCmd.composeBuffer.resize(bufferSize);
memcpy(composeCmd.composeBuffer.data(), buffer, bufferSize);
composeCmd.cmd = PostCmd::Compose;
sendPostWorkerCmd(composeCmd);
if(needPost) {
post(p->targetHandle, false);
}
return true;
}
...
}
// @file: emu-30-release/external/qemu/android/android-emugl/host/libs/libOpenglRender/FrameBuffer.cpp
真机推荐使用该合成方式,因为它不仅性能好,功耗还低。那么在安卓模拟器中是否也是如此呢?答案是否定的,因为它最终也是调用宿主机OpenGL APIs实现合成,和GPU合成殊途同归,其性能并不一定比GPU合成好到哪里去。