内网的一篇好文 征得大佬同意拿来分享一下。硬核介绍Android闪现体系,深化每一个细节,从硬件到软件,带你体会不一样的景色。
- Android烘托系列(1)之原理概述篇
- Android烘托系列(2)之怎样烘托UI
- Android烘托系列(3)之Choreographer
- Android烘托系列(4)之Surface与SurfaceFlinger
- Android烘托系列(5)之Window、Activity、View之间联系
- Android烘托系列(6)之Vsync
面试官的小抄 面试进阶一扫而光,或许是东半球最好的面试资料
硬件篇
一 硬件闪现模块
无论软件架构多么的高端大气上档次,都离不开硬件的支撑,软件架构是构建的硬件的运转原理之上的,闪现模块更是如此。
1 常见的闪现设备:
- LCD(liquid crystal display)液晶屏
液晶是一种资料,液晶这种资料具有一种特色:能够在电信号的驱动下液晶分子进行旋转,旋转时会影响透光性,因而咱们能够在整个液晶面板后面用白光照(称为背光),能够经过不同电信号让液晶分子进行选择性的透光,此刻在液晶面板前面看到的便是各式各样不同的颜色,这便是 LCD 闪现画面的原理。有些闪现器(譬如 LED 闪现器、CRT 闪现器)自己本身会发光称为自动发光,有些(LCD)本身不会发光只会透光,需求背光的协助才干看起来是发光的,称为被迫发光。
- CRT(cathode ray tube)阴极射线管闪现器
首要有五部分组成:电子枪(Electron Gun)、偏转线圈(Deflection coils)、荫罩(Shadow mask)、高压石墨电极和荧光粉涂层(Phosphor)及玻璃外壳。CRT闪现器靠电子束激发屏幕内表面的荧光粉来闪现图画的,因为荧光粉被点亮后很快会平息,所以电子枪有必要循环地不断激发这些点。
- OLED(organic light-emitting diode)有机发光二极管
有机半导体资料和发光资料在电场驱动下,经过载流子注入和复合导致发光的现象,很容易制造,并且只需求低的驱动电压,这些首要的特征使得OLED在满意平面闪现器的运用上显得非常杰出。OLED闪现屏比LCD更轻薄、亮度高、功耗低、呼应快、清晰度高、柔性好、发光效率高,能满意顾客对闪现技术的新需求。
- LED( light-emitting diode ) 发光二极管
由一个个小的LED模块面板组成,用来闪现文字、图画、视频等各种信息的设备。 LED电子闪现屏集微电子技术、核算机技术、信息处理于一体,具有颜色鲜艳、动态范围广、亮度高、寿命长、作业稳定牢靠等优点。
2 视频闪现接口
- 对外闪现器的视频接口
设备与设备之间的视频传输接口。依据出现的时刻以及功用,能够分为五代:
- 第一代:亮色混合,视频接口代表:CVBS、AV
- 第二代:亮色分离,视频接口代表:S-端子、色差信号
- 第三代:模数共存,视频接口代表:VGA
- 第四代:纯数字,视频接口代表:DVI(DVI-D、DVI-A、DVI-I)
- 第五代:更强功用,视频接口代表:HDMI、MHL、DP、thunderbolt
- CVBS(RF)
- C端子(RCA/AV)
- S端子
- YPbPr
- VGA、DVI、HDMI、DP
- SDI
- HDBASE-T
- 对内闪现器视频接口
对内的闪现器视频传输接口指的是设备内部,板与板之间或板上的器材与器材之间的视频传输接口,现在常见的对内闪现器视频接口首要有以下几种:
- DVP(ITU BT601/656/1120)
- LVDS(差异于Serdes)
- MIPI(DSI/CSI)
- eDP
- DVP digital video parallel
- LVDS low voltage differential signaling
4/5/10 lane差分信号
- MIPI mobile indurstry processor interface
2/3/4/5 lane差分信号
- eDP embeded DP
1/2/3/4 lane 差分信号,无clk,依据displayport架构和协议的一种全数字化接口
- LCD时序
一个典型的 Android 闪现体系中,一般包括 SOC、DDIC、Panel 三个部分,
- SOC 担任绘画与多图层的组成,把组成好的数据经过硬件接口按某种协议传输给 DDIC,
- 闪现驱动芯片DDIC(Display Driver IC)担任把 buffer 里的数据呈现到 Panel 上。
DDIC 是面板的首要操控元件之一。 DDIC 经过电信号的方式向闪现面板发送驱动信号和数据,继而完结对屏幕亮度和颜色的操控,使得比如字母、图片等图画信息得以在屏幕上闪现。
如上图所示首要 CPU 或 GPU 担任绘画,画出的多个 layer 交由 MDP 进行组成,组成的数据经过 mipi 协议和 DSI 总线传输给 DDIC, DDIC 将数据存到 GRAM 内(非 video 屏), Panel 不断 scanGRAM 来闪现内容。
- 并口时序信号
- 多帧画面输出时序
多帧画面顺次输出到屏幕的时候咱们就能够看到运动的画面了,一般这个速度抵达每秒 60 帧时人眼就现已感觉画面很流畅了。如上图所示,在消隐区完毕(或开端)时 DDIC 会向 SOC 宣布一个中断信号,这个信号称为 TE 信号, SOC 这边便是经过该中断信号来判别上一帧数据是否已被 DDIC 读走,然后决议是否能够将 buffer 写入下一帧数据。
- LCD上的画面更新流程
如上图所示,首要 SOC 准备画面 A, DDIC 上一帧画面更新完毕进入消隐区,一起向 SOC 侧发送 TE 信号,SOC 收到 TE 信号后,A 画面的数据开端经过 DSI 总线向 DDIC 传输(DSI Write), 当消隐区时刻完毕时开端这一帧数据从数据变为像素点颜色的进程 (Disp Scan), Disp Scan 是以行为单位将 GRAM 内一行的数据内容经过改动电流电压等办法改动 Panel 上像素点的颜色。然后完结一行画面的更新,按下来 Disp Scan 将以一定速度逐行读取 GRAM 的内容,而于此一起 DSI Write 也还在进行中,因为 DSI Write 较 Disp Scan 早了一个 Vporch 的时刻,所以 Disp Scan 扫描到的数据都是 A 画面的数据。那么人眼会看到画面“逐渐” 出现到闪现屏上,当 A 画面的一切行都经 Disp Scan 抵达屏幕后,下一个 Vporch 开端,DDIC 再次向 SOC 宣布 TE 信号, 下帧 B 画面的数据开端经过 DSI 总线传输到 DDIC, 如此循环往复能够将接连的 A, B, C 画面更新到屏幕上。 Vporch消隐区时序保证了画面更新的时序性,如上图所示,写是在进入 vporch 时就开端了,而读的动作是脱离 vproch 时,所以在读和写的这场 “百米跑” 竞赛中总是写跑在前面,这样保证读一直读到的是同一帧画面的数据。
二 闪现模块驱动及体系接口
DRM,英文全称 Direct Rendering Manager, 即直接烘托办理器。DRM是linux内核的一个子体系,它供给一组 API,用户空间程序能够经过它发送画面数据到 GPU 或许专用图形处理硬件(如高通的MDP/海思的VDP/瑞芯微的VOP),也能够运用它履行比如装备分辨率,刷新率之类的设置操作。原本是规划供给给 PC 运用来支撑杂乱的图形设备,后来也用于嵌入式体系上。现在在各大渠道Android体系上的闪现体系也是运用的这组API来完结画面的烘托更新。 在 DRM 之前 Linux 内核现已有一个叫 FBDEV 的 API,用于办理图形适配器的帧缓存区,但不能用于满意依据 3D 加快的现代依据 GPU 的视频硬件的需求,FBDEV 社区维护者也较少; 且无法供给 overlay hw cursor 这样的 features; 开发者们本身就鼓舞以后迁移到DRM 上。 如上图所示,展现了DRM子体系框图,首要分为3个部分:
1 KMS kernel mode setting
CRTC 对闪现buffer进行扫描,并发生时序信号的硬件模块,一般指Display Controller ENCODER 将CRTC输出的timing时序转换成外部设备所需求的信号,如HDMI转换器或DSI Controller CONNECTOR 连接物理闪现设备的连接器,如HDMI、DisplayPort、DSI总线,一般和Encoder驱动绑定 PLANE 硬件图层,有的Display硬件支撑多层组成闪现,但一切的Display Controller至少要有1个plane Bridge 桥接设备,一般用于注册 encoder 后面另外再接的转换芯片,如 DSI2HDMI 转换芯片; Panel:泛指屏,各种 LCD、HDMI 等闪现设备的抽象; FB Framebuffer,单个图层的闪现内容,仅有一个和硬件无关的根本元素 VBLANK 软件和硬件的同步机制,RGB时序中的笔直消影区,软件一般运用硬件VSYNC来完结 PROPERTY 任何想设置的参数,都可做成property,是DRM驱动中最灵敏、最便利的Mode setting机制
2 GEM graphic execution manager
DUMB 只支撑接连物理内存,依据kernel中通用CMA API完结,多用于小分辨率简略场景 PRIME 接连、非接连物理内存都支撑,依据DMA-BUF机制,能够完结buffer同享,用于大内存杂乱场景 FENCE buffer同步机制,依据内核dma_fence机制完结,用于防止闪现内容出现异步问题
3 libdrm
DRM是Linux下的图形烘托架构,用来办理闪现输出和分配buffer。运用程序能够直接操纵DRM的ioctl或许是用framebuffer供给的接口进行闪现相关操作。libdrm库封装了这些接口,让用户能够更加便利的进行闪现操控。 各模块的souce code方位如下(rockchip android12 渠道):
libdrm | external/libdrm |
---|---|
hwcomposer | hardware/rockchip/hwcomposer/drmhwc2 |
drm driver | kernel-5.18/drivers/gpu/drm |
三 用户空间的帧数据流
在 Android 体系上运用要制作一个画面,首要要向 SurfaceFlinger 请求一个画布,这个画布所运用的 buffer 是 SurfaceFlinger 经过 allocator service(android.hardware.graphics.allocator@4.0-service)来分配出来的,allocator service 是经过 ION 从 kernel 开辟出来的一块同享内存,这里请求的都是每个图层所具有独立 buffer, 这个 buffer 会同享到 HWC Service 里,由 SurfaceFlinger 来作为中枢操控这块 buffer 的一切权,其一切权会随状况不同在 App, SurfaceFlinger, HWC Service 间来回流转。 HWC Service正是那个运用 libdrm 和 kernel 打交道的人 ,HWC Service担任把 SurfaceFlinger 交来的图层做组成,将组成后的画画提交给 DRM 去闪现。
1 App到Allocator Service
Android 12版别,APP首要经过 Surface 的接口向 Allocator请求3块 buffer,并经过importBuffer 把内存映射到运用的进程空间里,这种提前分配buffer的模式能够避免在烘托时分配buffer带来的delay,该进程能够在perfetto上观察到: 曾经的版别,App经过 Surface 的接口向Surfaceflinger请求 buffer, 在运用第一非有必要制作画面时 dequeueBuffer 会让 SurfaceFlinger 去 alloc buffer, 在运用侧会经过 importBuffer 把这块内存映射到运用的进程空间来。
2 App到SurfaceFlinger
App经过dequeueBuffer拿到画布,经过queueBuffer来提交制作好的数据,这个进程能够在perfetto上看到: 如上图所式,因为app在进程内分配了graphicBuffer并进行了importBuffer操作,不需求经过binder和Surfaceflinger之间发生通信,直接经过dequeueBuffer和requestBuffer拿到对应的buffer地址。之后app经过gpu完结制作之后履行eglSwapBuffer,然后调用queueBuffer提交数据,终究经过binder知会surfaceflinger侧履行importBuffer完结数据映射及后续处理。而老版其他android需求经过binder在surfaceflinger端履行queueBuffer系列操作。
3 SurfaceFlinger到HWC service
HWC Service 担任将 SurfaceFlinger 送来的图层做组成,形成终究的画面,然后经过 DRM 的接口更新到屏幕上去。 如上图所示,surfaceflinger使命由一个个onMessageInvalidate组成,在此进程中,经过binder完结和上层app及基层的hwcomposer的信息对通,不断的完结画面的获取、组成和上屏操作,循环往复。下面详细剖析onMessageInvalidate干了那些作业: 如上图所示,onMessageInvalidate函数的具体流程大致分为11步:
- handleMessageTransaction->setBuffer App完结了制作之后,会调用setBuffer设置对应的transactionflag,并终究告诉到Surfaceflinger。
- handleMessageInvalidate->handlePageFlip->latchBuffer SurfaceFlinger获知App提交了制作完结的buffer,会调用handlePageFlip,并进一步骤用latchBuffer锁定acquire这个buffer,以便接下来进行画面的组成刷新。
- updateInputFlinger 该函数用于处理画面组成进程的input作业。
- preComposition 预组成函数会检测是否还有其他图元需求消费,并调用signalLayerUpdate进行下一轮的invalidate消费。
- prepare 该函数调用rebuildLayerStacks构建Layer栈,从头核算一切需求制作的Layer的脏区域。
- updateCompositionState 从头核算ouputlayer的几何状况,核算DisplayFrame、SourceCrop等参数
- writeCompositionState 将outputLayer特点设置给hwcomposer
- prepareFrame 确定layer的组成办法,gpu组成或许hwc组成,这一步需求和hwc service进行binder通信,hwc service调用了ValidateDisplay进行相关处理。
- finishFrame 对于需求gpu进行制作的layer,进行组成制作操作。
- postFramebuffer 提交frame进入闪现流程,并经过binder音讯告诉hwc service调用了PresentDisplay,该函数会调用drm相关接口操作内核drm驱动。完结frame的闪现处理
- postComposition 组成送显完毕之后的buffer fence释放,vsync的同步等善后作业。
软件篇
四 BufferQueue机制
1 BufferQueue要处理什么问题
APP 绘画的画布是由Allocator Service供给的,而画布是一块同享内存,APP 向 Allocator Service请求到画布,是将同享内存的地址映射到本身进程空间。 App担任在画布上作画,画完的作品提交给 SurfaceFlinger,这个提交操作并不是把内存仿制一份给 SurfaceFlinger,而是把同享内存的操控权交还给 SurfaceFlinger。
SurfaceFlinger 把拿来的多个运用的同享内存再送给HWC Service去组成,HWC Service 把组成的数据交给 DRM 去输出完结 app 画面闪现到屏幕上的进程。为了更有效地利用时刻这样的同享内存不止一份,或许有两份或三份,即常说的 double buffering, triple buffering。那么咱们就需求规划一个机制能够办理 buffer 的操控权,这个便是BufferQueue。BufferQueue 要处理的是生产者和顾客的同步问题,
运用程序生产画面,SurfaceFlinger 消费画面;SurfaceFlinger 生产画面而 HWC Service 消费画面。用来存储这些画面的存储区咱们称其为帧缓冲区 buffer。
2 Buffer State的切换
在 BufferQueue 的规划中,一个 buffer 的状况有以下几种:
- FREE :表明该 buffer 能够给到运用程序,由运用程序来绘画
- DEQUEUED: 表明该 buffer 的操控权现已给到运用程序侧,这个状况下运用程序能够在上面绘画了
- QUEUED: 表明该 buffer 现已由运用程序绘画完结,buffer 的操控权现已回到 SurfaceFlinger 手上了
- ACQUIRED: 表明该 buffer 现已交由 HWC Service 去组成了,这时操控权已给到 HWC Service 了
状况切换如上图所示,Buffer 的初始状况为 FREE, 当生产者经过 dequeueBuffer 来请求 buffer 成功时,buffer 状况变为了 DEQUEUED 状况, 运用画图完结后经过 queueBuffer 把 buffer 状况改到 QUEUED 状况,当 SurfaceFlinger 经过 acquireBuffer 操作把 buffer 拿去给 HWC Service 组成, 这时 buffer 状况变为 ACQUIRED 状况,组成完结后经过 releaseBuffer 把 buffer 状况从头改为 FREE 状况。 从时刻轴上来看一个 buffer 的状况总是这样循环变化: FREE->DEQUEUED->QUEUED->ACQUIRED->FREE 运用程序在 DEQUEUED 状况下绘画,而 HWC Service 在状况为 ACQUIRED 状况下做组成。
3 BufferSlot办理
**每一个运用程序的图层在 SurfaceFlinger 里称为一个 Layer, 而每个 Layer 都具有一个独立的 BufferQueue, **每个 BufferQueue 都有多个 Buffer,Android 体系上现在支撑每个Layer最多64个buffer,每个 buffer 用一个结构体 BufferSlot 来代表。
struct BufferSlot{
BufferState mBufferState;// Buffer的状况 FREE/DEQUEUED/QUEUED/ACQUIRED
sp<GraphicBuffer> mGraphicBuffer;//真实的buffer的存储空间
uint64_t mFrameNumber;//表⽰这个slot被queued的编号
sp<Fence> mFence;// gpu和cpu之间的信号同步
}
BufferSlot 能够分红两个部分,Used Slots 和 Unused Slots, 而 Used Slots 又能够分为 Active Slots 和 UnActive Slots, 处在 DEQUEUED, QUEUED, ACQUIRED 状况的被称为 Active Slots, 剩下 FREE 状况的称为 UnActive Slots, 所以一切 Active Slots 都是正在有人运用中的 slot, 运用者或许是生产者也或许是顾客。而 FREE 状况的 Slot 依据是否现已为其分配过内存来分红两个部分,一是现已分配过内存的, 在 Android 源码中称为 mFreeBuffers, 没有分配过内存的称为 mFreeSlots, 所以假如咱们在代码中看到是从 mFreeSlots 里拿出一个 BufferSlot 那阐明这个 BufferSlot 是还没有装备 GraphicBuffer 的,这个 slot 或许是第一次被运用到。其分类如下图所示: 运用上帧时,SurfaceFlinger 正是经过BufferSlot办理分配这些 Slot 的,运用侧对图层 buffer 的操作接口在这个文件:frameworks/native/libs/gui/Surface.cpp。 运用第一次 dequeueBuffer 前会经过 connect 接口和 SurfaceFlinger 树立 “连接”: 运用在第一次 dequeueBuffer 时会先调用 requestBuffer,如下图所示,函数内部先尝试去dequeueBuffer,因为此刻surfaceflinger对应layer的slot还没有分配buffer,此刻surfaceflinger回复的flag会有BUFFER_NEEDS_REALLOCATION标识,然后接下来的流程检测到有该标志位就会宣布一次requestBuffer: 在 SurfaceFlinger 这端,第一次收到 dequeueBuffer 时发现分配出来的 slot 没有 GraphicBuffer, 这时会去请求对应的 buffer,如下图所示: APP侧收到带有 BUFFER_NEEDS_REALLOCATION 符号的回来成果后就会调 requestBuffer 来获取对应 buffer 的信息: 运用侧在 requestBuffer 后会拿到 GraphicBuffer 的信息,然后会经过 importBuffer 在本进程内经过 binder 传过来的 parcel 包把 GraphicBuffer 重建出来: 以上是Surfaceflinger经过BufferSlot办理buffer的经典流程,这部分在Android12上为了功用提升得到了优化,不过根本流程和原理是一致的,详细原理上述章节现已详细论述。
从App侧看,前三帧都会有requestBuffer, 都会有importBuffer,在第4帧时就没有requestBuffer/importBuffer了,因为咱们当时体系总共运用了三个buffer,在Android12上表现为App侧allocate并import了3个buffer。 当一个surface被创立出来开端上帧时其流程如下图所示,运用所运用的画布是在前三帧被分配出来的,从第四帧开端进入稳定上帧期,这时会重复循环利用前三次分配的buffer。
4 Buffer办理
上边说到每个图层Layer都有最多64个BufferSlot,每个BufferSlot都会记载本身的状况BufferState,以及自己的GraphicBuffer指针mGraphicBuffer。 但不是每个Layer都能运用到那么多,一般2到3个,default是2个,在Surface创立时初始化: 接下来剖析BufferSlot办理的场景,如下图所示:
- Time1: 在上图中,初始状况下,有 0, 1, 2 这三个 BufferSlot, 因为它们都没有分配过 GraphicBuffer, 所以它们都坐落 mFreeSlots 行列里,当运用来 dequeueBuffer 时,SurfaceFlinger 会先检查在 mFreeBuffers 行列中是否有 Slot, 假如有则直接分配该 Slot 给运用。明显此刻 mFreeBuffers 里是空的,这时 Surfaceflinger 会去 mFreeSlots 里去找出第一个 Slot, 这时就找到了 0 号 Slot, dequeueBuffer 完毕时运用就拿到了 0 号 Slot 的运用权,于此一起 SurfaceFlinger 也会为 0 号 Slot 分配 GraphicBuffer, 之后运用将经过 requestBuffer 和 importBuffer 来获取到该 Slot 的实际内存空间。运用 dequeueBuffer 之后 0 号 Slot 切换到 DEQUEUED 状况,并被放入 mActiveBuffers 列表。
- Time2: 运用完结制作后经过 queueBuffer 来提交制作好的画面,完结后 0 号 Slot 状况变为 QUEUED 状况,放入 mQueue 行列,此刻 1,2 号 Slot 还停留在 mFreeSlots 行列中。
- Time3: 上面这个状况会继续到下一个 Vsync-sf 信号到来,当 Vsync-sf 信号到来时,SurfaceFlinger 主线程会检查 mQueue 行列中是否有 Slot, 有就意味着有运用上帧,这时它会把该 Slot 从 mQueue 中取出放入 mActiveBuffers 行列,并将 Slot 的状况切换到 ACQUIRED, 代表这个 Slot 已被拿去做画面组成。那么这之后 0 号 Slot 被从 mQueue 行列拿出放入 mActivieBuffers 里
- Time4: 接下来运用继续调用 dequeueBuffer 请求 buffer, 此刻 0 号 Slot 在 mActiveBuffers 里,1,2 号在 mFreeSlots 里,SurfaceFlinger 仍然是先检查 mFreeBuffers 里有没有 Slot, 发现还是没有,再检查 mFreeSlots 里是否有,所以取出了 1 号 Slot 给到运用侧,一起 1 号 Slot 状况切换到 DEQUEUED 状况, 放入 mActiveBuffers
- Time5:1 号 Slot 运用绘画完毕,经过 queueBuffer 提交上来,这时 1 号 Slot 状况由 DEQUEUED 状况切换到了 QUEUED 状况,进入 mQueue 行列,之后将保持该状况直到下一个 Vsync-sf 信号到来。
- Time6: 此刻 Vsync-sf 信号到来,发现 mQueue 中有个 Slot 1, 这时 SurfaceFlinger 主线程会把它取出,把状况切换到 ACQUIRED, 并放入 mActiveBuffers 里。
- Time7: 这时 0 号 Slot HWC Service 运用完毕,经过 releaseBuffer 还了回来,0 号 Slot 的状况将从 ACQUIRED 切换回 FREE, Surfaceflinger 会把它从 mActivieBuffers 里拿出来放入 mFreeBuffers 里。留意这时放入的是 mFreeBuffers 里而不是 mFreeSlots 里,因为此刻 0 号 Slot 是有 GraphicBuffer 的。
- Time11: 当下的状况是 0,1 两个 Slot 都在 mFreeBuffers 里,2 号 Slot 在 mActiveBuffers 里,这时运用来 dequeueBuffer
- Time12: SurfaceFlinger 仍然会先检查 mFreeBuffers 列表看是否有可用的 Slot, 发现 0 号可用,所以 0 号 Slot 状况由 FREE 切换到 DEQUEUED 状况,并被放入 mActiveBuffers 里
- Time13: 运用对 0 号 Slot 的绘图完结后提交上来,这时状况从 DEQUEUED 切换到 QUEUED 状况,0 号 Slot 被放入 mQueue 行列,之后会保持该状况直到下一下 Vsync-sf 信号到来
- Time14: 这时 Vsync-sf 信号到来,SurfaceFlinger 主线程中检查 mQueue 行列中是否有 Slot, 发现 0 号 Slot, 所以经过 aquireBuffer 操作把 0 号 Slot 状况切换到 ACQUIRED
- Time 23: 当时状况 mQueue 里有两个 buffer
- Time 24:Vsync-sf 信号抵达,从 mQueue 行列里取走了 0 号 Slot,
- Time 25: 再一次 Vsync-sf 到来,这时 SurfaceFlinger 会先检查 mQueue 行列是否有 buffer,发现有 2 号 Slot, 会先取走 2 号 Slot
- Time 26: 此刻 0 号 Slot 现已被 HWC Service 运用完毕,需求把 Slot 还回来,0 号 Slot 在此刻进入 mFreeBuffers 行列。
所以假如运用上帧速度较慢,比如其上帧周期时长大于两倍屏幕刷新周期时,每次运用来 dequeueBuffer 时前一次 queueBuffer 的 BufferSlot 都现已被 release 回来了,这时总会在 mFreeBuffers 里找到可用的,那么就不需求三个 Slot 都分配出 GraphicBuffer。 其中有两个时序需求留意:
- 每次 Vsync-sf 信号到来时总是先检查 mQueue 行列看是否有 Layer 上帧,然后才会走到 releaseBuffer 把 HWC Service 运用的 Slot 回收回来
- 本次 Vsync-sf 被 aquireBuffer 取走的 Slot 总是会在下一个 Vsync-sf 时才会被 release 回来
5上帧流程
- 经过surfaceview上帧观测完好流程:
App经过 dequeueBuffer 拿到了 BufferSlot 0, 开端第 1 步绘图,绘图完结后经过 queueBuffer 将 Slot 0 提交到 SurfaceFlinger, 下一个 Vsync-sf 信号抵达后,开端第 2 步图层处理,这时 SurfaceFlinger 经过 aquireBuffer 把 Slot 0 拿去给到 HWC Service,与此一起进入第 3 步 HWC Service 开端把多个图层做组成,组成完结后经过 libdrm 供给的接口告诉 DRM 模块经过 DSI 传输给 DDIC, Panel 经过 Disp Scan Gram 把图画闪现到屏幕。
- Tripple Buffer接连上帧进程
App在每个 Vsync 信号到来后都会经过 dequeueBuffer/queueBuffer 来请求 buffer 和提交绘图数据,Surfaceflinger 都会在下一个 vsync 信号到来时取走 buffer 去做组成和闪现,并在下一下个 vsync 时将 buffer 还回来,再次循环。
五 Fence机制
Fenc处理了什么问题
一般凡是同享的资源都要树立一个同步机制来办理,比如在多线程编程中对临界资源的经过加锁完结互斥拜访,再比如 BufferQueue 中 Surfaceflinger 和运用对同享内存(帧缓冲)的拜访中有 bufferstate 来标识同享内存操控权的办法来做同步。没有同步机制的无序拜访极或许造成数据紊乱。
上面图中的 BufferState 的办法仅仅处理了在 CPU 办理之下,当下同享内存的操控权归属问题,但当同享资源是在两个硬件之中时,状况就不同了,比如当一个帧缓冲区同享内存给到 GPU 时,GPU 并不清楚 CPU 还有没有在运用它,同样地,当 GPU 在运用同享内存时,CPU 也不清楚 GPU 是否已运用完毕。 如上图所示,CPU 调用 OpenGL 函数绘图进程的一个简化版流程如上图所示,首要 CPU 侧调用 glClear 清空画布,再调用 glXXX()来画各式各样的画面,对于 CPU 来讲在 glXXX() 履行完毕后,它的绘图作业现已完结了。但其实 glXXX()的具体作业是由 GPU 来完结的,CPU 侧的 glXXX() 仅仅在向 GPU 传达使命而已,使命传达完并不意味着使命现已完结了。真实使命做完是在 GPU 把 glXXX() 所对应的作业做完才是真实的使命完结了。从 CPU 下达完使命到 GPU 完结使命间存在时差,并且这个时差受 GPU 作业频率影响并不是一个定值。在 OpenGL 的语境中 CPU 能够经过 glFilish() 来等候 GPU 完结一切作业,但这明显浪费了 CPU 本能够并行作业的时刻,这段时刻 CPU 没有用来做其他作业。 Fence 供给了一种办法来处理不同硬件对同享资源的拜访操控,处理上述说到的CPU和GPU异步拜访资源的问题。 如上图所示,Fence 是一个内核 driver, 对一个 Fence 目标有两种操作, signal 和 wait, 当生产者(App)向 GPU 下达了许多绘图指令(drawCall)后 GPU 开端作业,这里 CPU 就认为绘图作业现已完结了,之后把创立的 Fence 目标经过 binder 告诉给顾客(SurfaceFlinger),SurfaceFlinger 收到告诉后,此刻 SurfaceFlinger 并不知道 GPU 是否现已绘图完毕,即 GPU 是否已对同享资源拜访完毕,顾客先经过 Fence 目标的 wait 办法等候,假如 GPU 绘图完结会调用 Fence 的 signal, 这时顾客就会从 Fence 目标的 wait 办法中跳出。即 wait 办法完毕时便是 GPU 作业完结时。这个 signal 由 kernel driver 来完结。有了 Fence 的状况下,CPU 在完结自已的作业后就能够继续做其他作业,到了真实要运用同享资源时再经过 Fence wait 来和 GPU 同步,尽最大或许做到了让不同硬件并行作业。
2 Fence与BufferQueue的协作办法
如上图所示,首要 App 经过 dequeueBuffer 获得某一 Slot 的运用权,这时 Slot 的状况切换到 DEQUEUED 状况,跟着 dequeueBuffer 函数回来的还有一个 releaseFence 目标,这时因为 releaseFence 还没有 signaled, 这意味着尽管在 CPU 这边现已拿到了 buffer 的运用权,但其他硬件还在运用这个 buffer, 这时的 GPU 还不能直接在上面绘画,它要等 releaseFence signaled 后才干绘画。 接下来咱们先假定 GPU 的作业花费的时刻较长,在它完结之前 CPU 侧 APP 现已完结了 queueBuffer 动作,这时 Slot 的状况已切换为 QUEUED 状况,或许 vsync 现已到来状况变为 ACQUIRED 状况, 这在 CPU 侧代表该 buffer 给 HWC 去组成了,但这时 HWC 的硬件 VOP 还不能去读里边的数据,它还需求等候 acauireFence 的 signaled 信号,只要等到了 acquireFence 的 signaled 信号才代表 GPU 的绘画作业真实做完了,GPU 现已完结了对帧缓冲区的拜访,这时 HWC 的硬件才干去读帧缓冲区的数据,完结图层组成的作业。 同样地,当 SurfaceFlinger 履行到 releaseBuffer 时,并不能代表 HWC 现已彻底完结组成作业了,很有或许它还在读取缓冲区的内容做组成, 但不妨碍 releaseBuffer 的流程履行,尽管 HWC 还在运用缓冲区做组成,但帧缓冲区的 Slot 有或许被运用请求走变成 DEQUEUED 状况,尽管 Slot 是 DEQUEUED 状况这时 GPU 并不能直接存取它,它要等代表着 HWC 运用完毕的 releaseFence 的 signaled 信号。 如上图所示,App请求buffer的一起,会获取一个fence目标,用于等候HWC运用完毕这个buffer,尔后GPU能够操作这个buffer。 同理,运用侧上帧时要创立一个 fence 来代表 GPU 的功用还在进行中,提交 buffer 的一起把 fence 目标传给 SurfaceFlinger,SurfaceFlinger 侧用于等候GPU制作完结。尔后CPU能够操作这个buffer。 如上图所示,是一个因 GPU 作业时刻太长,然后让 DRM 作业线程卡在等 Fence 的状况,complete_commit 函数履行时前面有一段时刻是陷于等候状况了,从图中所示咱们能够看出它在等下 73026 号 fence 的 signal 信号。这种状况阐明 drm 内部的 dma 要去读这 miHoYo.yuanshen 这个运用的 buffer 时发现运用的 GPU 还没有把画面画完,它不得不等候它画完才干开端读取,但既然都现已送到 crtc_commit 了,在 CPU 这侧,该 Slot 的 BufferState 现已是 ACQUIRED 状况。
六 画面闪现流程
上述章节剖析了Userspace、Kernel、App、Surfaceflinger、Hwcomposer的联系,帧活动的要害节点、同步问题处理等各种技术细节,那么App所制作的画面是怎样一步步进入Android规划的那些流程之中的呢?
1 App启动流程图
如上图所示为AMS完结App的Activity Thread创立之后的首要流程,ActivityThread类中的handleLaunchActivity,performLaunchActivity,handleResumeActivity这3个首要的办法完结了Activity的创立到启动作业,完结了Activity的onCreate、onStart、onResume这三个生命周期的履行。
- handleLaunchActivity: 创立Activity实例,并调用其attach办法创立了PhoneWindow目标(Window的完结类),并为该Window目标设置了WindowManager(Windows的办理办法)。
- performLaunchActivity: 调用onCreate办法,经过setContentView加载界说的布局文件。初始化DecorView,将设置的布局文件解析为View树,增加到mContentParent中,并调用onStart函数进行其他初始化操作。
- handleResumeActivity: 调用onResume办法,获取Activity的WindowManager、Window、DecorView目标,调用WindowManager.addView办法初始化ViewRootImpl,然后调用ViewRootImpl.setView为Window增加DecorView,一起setView内部调用了requestLayout,该函数内部又调用了scheduleTraversals,该办法完结View三大流程<measure、layout、draw>
Android中的视图是以View树的方式安排的,而View树有必要依附在Window上才干作业,一个Window对应着一个View树,Activity内部能够有多个Window。因为View的丈量、布局、制作仅仅在View树内进行的,因而一个Window内View的改动不会影响到另一个Window。
- Activity 像个操控器,不担任视图部分,它仅仅操控生命周期和处理作业。
- Window 像个承载器,装着内部视图,并真实操控视图。
- ViewRootImpl 像个连接器,担任沟通,经过硬件的感知(分发作业,制作View内容)来告诉视图,进行用户之间的交互。
- DecorView 顶层视图,是一切View的最外层布局。
2 画布的请求
在Android体系中每个 Activity 都有一个独立的画布(在运用侧称为 Surface, 在 SurfaceFlinger 侧称为 Layer),无论这个 Activity 安排了多么杂乱的 view 结构,它们终究都是被画在了所属 Activity 的这块画布上,当然也有一个例外,SurfaceView 是有自已独立的画布的. 如上文剖析,每个运用都会创立有自已的 Activity, 一起 Android 会为每个运用的Activity 创立一个 ViewRootImpl,并在ViewRootImpl里向Choreographer注册一个回调,每当有Vsync信号降临就会履行mTraversalRunnable敞开绘图流程,这里边会调用到ViewRootImpl的 performTraversals 这个函数: 该函数内部又调用了relayoutWindow函数: relayoutWindow又经过binder调用了WMS远端的relayoutwindow函数: relayoutWindow内部调用了createSurfaceControl: createSurfaceLocked又进一步骤用了如下函数: 终究调用native的接口: 至此,完结了从一个App的Activity创立到Surfaceflinger内部为它创立了一个Layer来对应。 经过上图咱们看到 java 层 Surface 的 lock 办法终究它有去调用到 GraphicBufferProducer 的 dequeueBuffer 函数,dequeueBuffer 在获取一个 Slot 后,假如 Slot 没有分配 GraphicBuffer 会在这时给它分配 GraphicBuffer, 然后会回来一个带有 BUFFER_NEEDS_REALLOCATION 符号的 flag, 运用侧看到这个 flag 后会经过 requestBuffer 和 importBuffer 接口把 GraphicBuffer 映射到自已的进程空间。到此运用拿到了它制作界面所需的“画布”。 上述代码进程的总结如上图所示,这个进程中触及到三个进程,APP,system_server, surfaceflinger, 先从运用调用 performTraversals 中调用 relayoutWindow 这个函数开端,它跨进程调用到了 system_server 进程中的 WMS 模块,这个模块的 relayoutWindow 又经一系列进程创立一个 SurfaceContorl, 在 SurfaceControl 创立进程中会跨进程调用 SurfaceFlinger 让它创立一个 Layer 出来。 之后 SurfaceControll 目标会跨进程经过参数回传给运用,运用依据 SurfaceControl 创立出运用侧的 Surface 目标,而 Surface 目标经过一些 api 封装向上层供给拿画布(dequeueBuffer)和提交画布(queueBuffer)的操作接口。这样运用完结了对画布的请求操作。
3 帧数据的制作
App拿到画布之后,就能够进入制作流程了,剖析如下: performTraversals函数中获取了surface之后,初始化ThreadRender时会把Surface目标传过去。 如上图所示,ThreadRender的初始化中调用了setSurface,该函数经过jni调用到native层,并经过RenderProxy向RenderThread的音讯行列post了一个音讯,在音讯处理中会调用Context的setSurface,既是CanvasContext::setSurface,并在该函数内部初始化pipelineSurface,终究调用到VulkanSurface的ConnectAndSetWindowDefaults函数,进行了一系列的window初始化操作。其进程如下图所示: 该进程也能够在perfetto上观察到,如下图: 接下来App主线程会遍历ViewTree(根View->DecorView)对一切的View完结measure、layout、draw的作业: performDraw中终究调用到ThreadedRenderer.java的draw,如下图: 如上图所示,draw函数终究调用到DecorView的基类函数updateDisplayListIfDirty,该函数中又调用了如下函数:
在 Android 的规划里 View 会对应一个RenderNode, RenderNode里的一个重要数据结构是DisplayList, 每个DisplayList都会包括一系列 DisplayListData。这些 DisplayList 也会同样以树形结构安排在一起。上图中的RecordingCanvas相当于一个绘图指令记载员,将这个View及其子View经过draw函数制作的指令以DisplayList的方式记载下来。 DisplayListData界说在framework/base/libs/hwui/RecordingCanvas.h中,首要能够分为3部分:
- draw最初的函数,根本的绘图指令
- push模版函数
- fBytes 存储区
以DisplayListData::drawRect 办法为例,函数调用流程如下: 由上图可知,drawRect函数没有将App规划的View转换成像素点数据,而是把画这个 Rect 的办法和参数存入了 fBytes 这块内存中,那么终究fBytes这段内存空间就放置了一条条的制作指令。 如上图所示,完结了对view的指令翻译之后,正式进入绘图流程: 如上图所示,终究来到CavasContext::draw函数,要害步骤分为3步:
- mRenderPipeline->getFrame
函数内部终究经过调用dequeueBuffer获取buffer。
- mRenderPipeline->draw
renderFrameImpl中会把在UI线程中记载的DisplayList从头“制作”到skSurface中,然后经过SkCanvas将其转化为gl指令,vulkanManager().finishFrame这句是将指令发送给GPU履行。
- mRenderPipeline->swapBuffers
终究经过queueBuffer将制作的Buffer提交到SurfaceFlinger做进一步的组成输出。 如上图所示,总结一下运用经过 android 的 View 体系画出第一帧的流程:首要是UI线程进行measure,layout,然后开端 draw, 在draw的进程中会树立displaylist树,将每个view应该怎样画记载下来,然后经过RenderProxy把后续使命下达给RenderThread, RenderThread首要完结三个动作,先经过 Surface 接口向 Surfaceflinger请求buffer, 然后经过 SkiaOpenGLPipline 的 draw 办法把 displaylist翻译成GPU 指令,指挥 GPU 把指令变成像素点数据,终究经过 swapBuffer 把数据提交给 SurfaceFlinger, 完结一帧数据的制作和提交。该进程能够经过perfetto观察到:
4 帧数据的提交
Android12中queueBuffer流程和之前有较大不同,提交流程如下: App的dequeueBuffer,会在native层调用BufferQueueProducer.cpp的queueBuffer函数,然后经过调用frameAvailableListener->onFrameAvailable告诉音讯监听目标ConsumerBase。 CosumerBase经过listener->onFrameAvailable告诉监听目标BLASTBufferQueue: BLASTBufferQueue::onFrameAvailable进一步骤用了processNextBufferLocked: 在processNextBufferLocked内部,终究调用了acquireBuffer函数去获取App提交的buffer,如下图所示: 一起,在processNextBufferLocked函数内部,终究会经过transaction机制触发Surfaceflinger侧进入onMessageInvalidate流程: 进入SurfaceFlinger::onMessageInvalidate流程之后就开端了画面送显流程,详细进程在第三部分第三节现已做过介绍,下面临代码段进行一些具体剖析: 如上图所示onMessageInvalidate做了一些frame的逻辑判别和处理之后,开端处理Transaction音讯,对App提交的layer进行了handlepageflip、latchbuffer等处理,并对refreshNeeded进行的赋值,触发surfaceflinger进入onMessageRefresh逻辑。 如上图所示,这些要害函数在前边章节都触及过,perfetto上也能够看到其函数调用的进程,到此SurfaceFlinger侧的处理流程完毕。整体流程如下图所示:
七 总结
整个流程下来,APP 的画面要闪现到屏幕上大致上要经过如下图所示体系组件的处理:
- 首要 App 向 SurfaceFlinger 请求画布 (Android12在内部请求),SurfaceFlinger 内部有一个 BufferQueue 的办理实体,它会分配一个 GraphicBuffer 给到 APP, App 拿到 buffer 后调用图形库向这块 buffer 内绘画。
- APP 绘画完结后运用向 SurfaceFlinger 提交制作完结的 buffer(经过 queueBuffer 接口), 当然这时候的制作完结仅仅说在 CPU 侧制作完结,此刻 GPU 或许还在该 buffer 上作画,所以这时向 SurfaceFlinger 提交数据的一起还会带上一个 acquireFence,运用接下来运用该 buffer 的人能知道什么时候 buffer 运用完毕了。
- SurfaceFlinger 收到运用提交的帧缓冲区 buffer 后是在下一个 vsync-sf 信号来时做处理,首要遍历一切的 Layer, 找到哪些 Layer 有上帧, 经过 latchBuffer 把 Buffer 拿出来,告诉给 HWC Service 去参与组成, 终究调用 HWC Service 的 presentDisplay 接口来奉告 HWC Service SurfaceFlinger 的作业已完结。
- HWC Service 收到组成使命后开端组成数据,在 SurfaceFlinger 调用 presetDisplay 时会去调用 DRM 接口 DRMAtomicReq::Commit 告诉 kernel 能够向 DDIC 发送数据了.
- 假如有 TE 信号来提示已进入消隐区,这时 DRM 驱动会马上开端经过 DSI 总线向 DDIC 传输数据,与此一起 Panel 的 Disp Scan 也在进行中,传输完结后这帧画面就完好地闪现到了屏幕上。
至此,一帧画面的更新进程就完结了。
本期就到这里了,更多精彩内容点击检查