本文基于 android-14.0.0_r15 版本讲解。
引子
一个 Activity 的显示通常会涉及到 APP WMS/AMS SurfaceFlinger 等多个进程,这给显示系统的学习增加了不少难度:
从学习的角度,我们可以暂时把 WMS/AMS 这部分 App 框架相关的内容先放一放,直接写一个与 SurfaceFlinger 交互的 Native 程序,让程序能显示简单的图形。搞定了 SurfaceFlinger 再来学习 App 框架那一套东西,这样可以极大地平缓显示系统的学习曲线。
显示系统其实都是围绕着帧缓存展开的:
- 分配帧缓存
- 将要显示的图形填充到帧缓存
- 合并多个帧缓存并显示
- 复用和回收帧缓存
接下来我们来看看帧缓存的分配和帧缓存的合并
帧缓存的分配
上一节我们聊过,帧缓存实际上就是一块内存。在 Android 系统中分配与回收帧缓存,使用的是一个叫 ION 的内核模块,App 使用 ioctl 系统调用后,会在内核内存中分配一块符合要求的内存,用户态会拿到一个 fd(有的地方也称之为 handle),然后接着调用 mmap ,就能把内核内存映射到用户态内存中,接着返回对应的用户态内存地址给 App。
我们知道一个普通的 fd,比如某个文件对应的 fd,如果把它从 A 进程传递到 B 进程,在 B 进程中,这个 fd 的值是没有意义的,索引不到对应的文件的。
ION 应该是在驱动中动了手脚,ION 分配的内存对应的 fd 可以从 A 进程传递到 B 进程,且在 B 进程中能够索引到内存,也就是也可以将 fd 传入 mmap 中获取到内存地址。
为什么需要这个 fd 可跨进程传递 的特性呢?因为在 Android 中,显示一帧画面需要涉及到多个进程,比如 App 进程、 Gralloc Hal 进程、HwComposer Hal 进程、SurfaceFlinger 进程,如果在不同的进程之间直接传递 buffer,开销过大,我们直接在不同进程之间传递 buffer 的索引 fd 即可,fd 是一个整型变量,大大减小了内存的开支。
在 Android 中,ION 被封装在了 Gralloc HAL 中,这是一个 AIDL HAL,Native App 可以直接访问这个 HAL。 Gralloc Hal 进一步去访问 ION 模块分配回收内存,ION 分配的是内核内存.
帧缓存的合并
Native App 调用 Gralloc Hal 分配好内存以后,可以调用 mmap 把内核内存映射到用户态,然后 App 可以来填充这块内存了,这个填充的过程就是常说的渲染,渲染通常通过一些图形库来完成,比如 skia,opengles 等,硬件上可以是使用 cpu 或者 gpu,通常 gpu 效率更好。
填充完了,就可以把这块 buffer 的 fd 发送给 SurfaceFinger。因为同一时间有多个 app 和 sf 交互,SurfaceFlinger 大概率会收到多个 buffer。
比如在 Launcher 中,最上面的状态栏和导航栏是一个图层对应一个 buffer,后面的壁纸是一个图层对应一个 buffer,各种 App 图标是一个图层对应一个 buffer。
SurfaceFlinger 会把这些 buffer 交给 HWComposer HAL, HWComposer HAL 会把这些 buffer 合并为一个 buffer,合并一般有两种类型:
- 一种是通过特定的硬件来合并,比如,高通的 soc 中,用于专门合并 buffer 的模块叫 mdp,一般效率很高。
- 一种是通过 opengl 等图形库来合并,图形库本身会去使用 cpu 或者 gpu 来合并,使用 cpu 还是 gpu 一般取决于相应的配置
接着显示电路就会把合并好的 buffer 转换为电信号,传递给显示设备显示。
整个过程如下图所示:
显示简单图形的 Native App Demo
在了解了图形显示的大致流程后,我们接着来完成一个简单的显示图像的 Native App Demo:
#define LOG_TAG "DisplayDemo"
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <hardware/gralloc.h>
#include <ui/GraphicBuffer.h>
#include <utils/Log.h>
#include <gui/BLASTBufferQueue.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/Surface.h>
#include <gui/SurfaceControl.h>
#include <system/window.h>
#include <utils/RefBase.h>
#include <android-base/properties.h>
#include <android/gui/ISurfaceComposerClient.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <ui/DisplayState.h>
using namespace android;
bool mQuit = false;
/*
Android 系统支持多种显示设备,比如说,输出到手机屏幕,或者通过WiFi 投射到电视屏幕。Android用 DisplayDevice 类来表示这样的设备。不是所有的 Layer 都会输出到所有的Display, 比如说,我们可以只将Video Layer投射到电视, 而非整个屏幕。LayerStack 就是为此设计,LayerStack 是一个Display 对象的一个数值, 而类Layer里成员State结构体也有成员变量mLayerStack, 只有两者的mLayerStack 值相同,Layer才会被输出到给该Display设备。所以LayerStack 决定了每个Display设备上可以显示的Layer数目。
*/
int mLayerStack = 0;
void fillRGBA8Buffer(uint8_t* img, int width, int height, int stride, int r, int g, int b) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t* pixel = img + (4 * (y*stride + x));
pixel[0] = r;
pixel[1] = g;
pixel[2] = b;
pixel[3] = 0;
}
}
}
int main(int argc, char ** argv) {
// 建立 App 到 SurfaceFlinger 的 Binder 通信通道
sp<SurfaceComposerClient> surfaceComposerClient = new SurfaceComposerClient;
status_t err = surfaceComposerClient->initCheck();
if (err != OK) {
ALOGD("SurfaceComposerClient::initCheck error: %#x\n", err);
return -1;
}
// 获取到显示设备的 ID
// 返回的是一个 vector,因为存在多屏或者投屏等情况
const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
if (ids.empty()) {
ALOGE("Failed to get ID for any displays\n");
return -1;
}
//displayToken 是屏幕的索引
sp<IBinder> displayToken = nullptr;
// 示例仅考虑只有一个屏幕的情况
displayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
// 获取屏幕相关参数
ui::DisplayMode displayMode;
err = SurfaceComposerClient::getActiveDisplayMode(displayToken, &displayMode);
if (err != OK)
return -1;
ui::Size resolution = displayMode.resolution;
//resolution = limitSurfaceSize(resolution.width, resolution.height);
// 创建 SurfaceControl 对象
// 会远程调用到 SurfaceFlinger 进程中,Surfaceflinger 中会创建一个 Layer 对象
String8 name("displaydemo");
sp<SurfaceControl> surfaceControl = surfaceComposerClient->createSurface(name, resolution.getWidth(),
resolution.getHeight(), PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
/*parent*/ nullptr);
// 配置 Layer 对象
SurfaceComposerClient::Transaction{}
.setLayer(surfaceControl, std::numeric_limits<int32_t>::max())
.show(surfaceControl)
.setBackgroundColor(surfaceControl, half3{0, 0, 0}, 1.0f, ui::Dataspace::UNKNOWN) // black background
.setAlpha(surfaceControl, 1.0f)
.setLayerStack(surfaceControl, ui::LayerStack::fromValue(mLayerStack))
.apply();
// 初始化一个 BLASTBufferQueue 对象,传入了前面获取到的 surfaceControl
// BLASTBufferQueue 是帧缓存的大管家
sp<BLASTBufferQueue> mBlastBufferQueue = new BLASTBufferQueue("DemoBLASTBufferQueue", surfaceControl,
resolution.getWidth(), resolution.getHeight(),
PIXEL_FORMAT_RGBA_8888);
// 获取到 GraphicBuffer 的生产者并完成初始化。
sp<IGraphicBufferProducer> igbProducer;
igbProducer = mBlastBufferQueue->getIGraphicBufferProducer();
igbProducer->setMaxDequeuedBufferCount(2);
IGraphicBufferProducer::QueueBufferOutput qbOutput;
igbProducer->connect(new StubProducerListener, NATIVE_WINDOW_API_CPU, false, &qbOutput);
while(!mQuit) {
int slot;
sp<Fence> fence;
sp<GraphicBuffer> buf;
// 1. dequeue buffer
igbProducer->dequeueBuffer(&slot, &fence, resolution.getWidth(), resolution.getHeight(),
PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
nullptr, nullptr);
igbProducer->requestBuffer(slot, &buf);
int waitResult = fence->waitForever("dequeueBuffer_EmptyNative");
if (waitResult != OK) {
ALOGE("dequeueBuffer_EmptyNative: Fence::wait returned an error: %d", waitResult);
break;
}
// 2. fill the buffer with color
uint8_t* img = nullptr;
err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
if (err != NO_ERROR) {
ALOGE("error: lock failed: %s (%d)", strerror(-err), -err);
break;
}
int countFrame = 0;
countFrame = (countFrame+1)%3;
fillRGBA8Buffer(img, resolution.getWidth(), resolution.getHeight(), buf->getStride(),
countFrame == 0 ? 255 : 0,
countFrame == 1 ? 255 : 0,
countFrame == 2 ? 255 : 0);
err = buf->unlock();
if (err != NO_ERROR) {
ALOGE("error: unlock failed: %s (%d)", strerror(-err), -err);
break;
}
// 3. queue the buffer to display
IGraphicBufferProducer::QueueBufferOutput qbOutput;
IGraphicBufferProducer::QueueBufferInput input(systemTime(), true /* autotimestamp */,
HAL_DATASPACE_UNKNOWN, {},
NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
Fence::NO_FENCE);
igbProducer->queueBuffer(slot, input, &qbOutput);
sleep(1);
}
return 0;
}
对应的 Android.bp:
cc_defaults {
name: "demo_defaults",
cflags: [
"-Wall",
"-Werror",
"-Wunused",
"-Wunreachable-code",
],
shared_libs: [
"libbase",
"libbinder",
"libcutils",
"liblog",
"libutils",
"libui",
"libgui",
"libEGL",
"libGLESv1_CM",
],
}
cc_binary {
name: "DispalyDemo",
defaults: ["demo_defaults"],
srcs: [
"main.cpp",
],
cflags: [
"-Wno-deprecated-declarations",
"-Wno-unused-parameter"
],
}
以上代码可以在手机上全屏显示一个纯色的图案。
代码中肯定有很多的疑惑,我们后面逐步分析,慢慢解开显示系统的神秘面纱。