從本篇文章開始,我將對Android比較復雜的圖形系統進行分析,開篇我們先對圖形系統做個概覽,先不對代碼做具體分析。
文章從如下三個層次進行講解.其中每一層之間的數據傳遞是使用Buffer(緩沖區)作為載體, 上層和framework間的buffer為圖形緩沖區,framework與顯示屏間的buffer是硬件幀緩沖區。
一、圖形渲染流程
1.1 app層繪制
由ViewRootImpl發起performTraversals開始View的繪制流程:
1)測量View的寬高(Measure)
2)設置View的寬高位置(Layout)
3)創建顯示列表,并執行繪制(Draw)
4)繪制通過圖形處理引擎來實現,生成多邊形和紋理(Record、 Execute)。
其中引擎包括:
2D : Canvas,Canvas調用的API到下層其實封裝了skia的實現。
3D: OpenGL ES , 當應用窗口flag等于WindowManager.LayoutParams.MEMORY_TYPE_GPU , 則表示需要用OpenGL接口來繪制UI.
1.2 Surface
每個Window對應一個Surface,任何View都要畫在Surface的Canvas上。圖形的傳遞是通過Buffer作為載體,Surface是對Buffer的進一步封裝,也就是說Surface內部具有多個Buffer供上層使用,如何管理這些Buffer呢?請看下面這個模型:
Surface對應生產者代理對象,Surface(Native)對應生產者本地對象。那么流程就是:
上層app通過Surface獲取buffer,供上層繪制,繪制過程通過Canvas來完成,底層實現是skia引擎,繪制完成后數據通過Surface被queue進BufferQueue, 然后監聽會通知SurfaceFlinger去消費buffer, 接著SurfaceFlinger就acquire數據拿去合成, 合成完成后會將buffer release回BufferQueue。如此循環,形成一個Buffer被循環使用的過程。
另外,這個過程有這么幾個狀態:
Free:可被上層使用
Dequeued:出列,正在被上層使用
Queued:入列,已完成上層繪制,等待SurfaceFlinger合成
Acquired:被獲取,SurfaceFlinger正持有該Buffer進行合成
所以Surface主要干兩件事:
- 獲取Canvas來干繪制的活。
- 申請buffer,并把Canvas最終生產的圖形、紋理數據放進去。
1.3 SurfaceFlinger
SurfaceFlinger 是一個獨立的Service, 它接收所有Surface作為輸入,創建layer(其主要的組件是一個 BufferQueue)與Surface一一對應,根據ZOrder, 透明度,大小,位置等參數,計算出每個layer在最終合成圖像中的位置,然后交由HWComposer或OpenGL生成最終的柵格化數據, 放到layer的framebuffer上。
1.4 Layer
Layer是SurfaceFlinger 進行合成的基本操作單元,其主要的組件是一個 BufferQueue。Layer在應用請求創建Surface的時候在SurfaceFlinger內部創建,因此一個Surface對應一個 Layer。Layer 其實是一個 FrameBuffer,每個 FrameBuffer 中有兩個 GraphicBuffer 記作 FrontBuffer 和 BackBuffer。
1.5 Hardware Composer
Hardware Composer HAL (HWC) 在 Android 3.0 中被引入。它的主要目標是通過可用硬件確定組合緩沖區的最有效方式。
1)SurfaceFlinger 為 HWC 提供完整的 layers 的列表并詢問,“你想要如何處理它?”。
2)HWComposer根據硬件性能決定是使用硬件圖層合成器還是GPU合成,分別將每個layer對應標記為 overlay 或 GLES composition 來進行響應。
3)SurfaceFlinger處理需要GPU合成的layers,將結果遞交給HWComposer做顯示(通過Hwcomposer HAL),需要硬件圖層合成器合成的layers由HWComposer自行處理(通過Hwcomposer HAL)。
4)合成Layer時,優先選用HWComposer,在HWComposer無法解決時,SurfaceFlinger采用默認的3D合成,也即調OpenGL標準接口,將各layer繪制到fb上。
分析:這樣設計的好處是可以充分發揮硬件性能,同時降低SurfaceFlinger和硬件平臺的耦合度(方便移植),另外SurfaceFlinger能將一些合成工作委托給Hardware Composer,從而降低來自OpenGL和GPUd的負載。
總結下兩種合成方式:
離線合成:
先將所有圖層畫到一個最終層(FrameBuffer)上,再將FrameBuffer送到LCD顯示。由于合成FrameBuffer與送LCD顯示一般是異步的(線下生成FrameBuffer,需要時線上的LCD去取),因此叫離線合成。毫無疑問,SurfaceFlinger采用默認的3D合成,也即調OpenGL標準接口,將各layer繪制到fb上屬于離線合成。
在線合成:
不使用FrameBuffer,在LCD需要顯示某一行的像素時,用顯示控制器將所有圖層與該行相關的數據取出,合成一行像素送過去。只有一個圖層時,又叫Overlay技術。由于省去合成FrameBuffer時讀圖層,寫FrameBuffer的步驟,大幅降低了內存傳輸量,減少了功耗,但這個需要硬件支持。毫無疑問,HWComposer合成屬于在線合成。
經過上述分析,我們大概了解了整個顯示數據的產生、傳送、合成的過程以及相關類在這個過程中所起的作用,最后總結一張圖形數據流:
1.6 Screen顯示
顯示屏上的內容,是從硬件幀緩沖區讀取的,大致讀取過程為:從Buffer的起始地址開始,從上往下,從左往右掃描整個Buffer,將內容映射到顯示屏上。
下圖顯示的是雙緩沖:一個FrontBuffer用于提供屏幕顯示內容,一個BackBuffer用于后臺合成下一幀圖形。
假設前一幀顯示完畢,后一幀準備好了,屏幕將會開始讀取下一幀的內容,也就是開始讀取上圖中的后緩沖區的內容.此時,前后緩沖區進行一次角色互換,之前的后緩沖區變為前緩沖區,進行圖形的顯示,之前的前緩沖區則變為后緩沖區,進行圖形的合成。
最后通過官方給出的圖了解下關鍵組件如何協同工作:
總結下渲染Android應用視圖的渲染流程:
測量流程用來確定視圖的大小、布局流程用來確定視圖的位置、繪制流程最終將視圖繪制在應用窗口上。
Android應用程序窗口UI首先是使用Canvas通過Skia圖形庫API來繪制在一塊畫布上,實際地是通過Surface繪制在這塊畫布里面的一個圖形緩沖區中,這個圖形緩沖區最終會通過layer的形式交給SurfaceFlinger來合成,而合成后柵格化數據的操作交由HWComposer或OpenGL生成,即將這個圖形緩沖區渲染到硬件幀緩沖區中,供屏幕顯示。
二、CPU/GPU的合成幀率與Display的刷新率同步問題
接上節,我們已經知道系統層不斷地合成顯示內容到后緩沖區,屏幕消費前緩沖區的顯示內容,兩緩沖區再交換。那么兩者的頻率是怎樣的呢?
我們先來了解兩個概念:
屏幕刷新率(HZ):代表屏幕在一秒內刷新屏幕的次數,Android手機一般為60HZ(也就是1秒刷新60幀,大約16.67毫秒刷新1幀)。
系統幀速率(FPS):代表了系統在一秒內合成的幀數,該值的大小由系統算法和硬件決定。
屏幕刷新率決定了屏幕消費顯示內容的速度,而系統幀速率則決定了生產顯示內容的速度。這是一個典型的生產者消費者的問題。兩個緩沖區的操作速率不一致,勢必會出現同步問題,如何解決?
從Android4.1版本開始,Android對顯示系統進行了重構,引入了三個核心元素:VSYNC, Tripple Buffer和Choreographer。來解決同步問題。下面分別來介紹:
2.1 垂直同步(VSync)
垂直同步(VSync):當屏幕從緩沖區掃描完一幀到屏幕上之后,開始掃描下一幀之前,發出的一個同步信號,該信號用來切換前緩沖區和后緩沖區。
很明顯這個垂直同步信號要求的是讓合成幀的速率以屏幕刷新率為標桿。為什么要這樣?很簡單:屏幕的顯示節奏是固定的,而系統幀速率是可以調整的,所以操作系統需要配合屏幕的顯示,在固定的時間內準備好下一幀,以供屏幕進行顯示。
那么以Android手機60Hz的刷新率來換算的話,垂直同步信號每16ms發送一次,那么也就意味著如何一幀在16ms內合成并顯示,那么就算是流暢的。當然為什么設計刷新率為60Hz, 那是因為人眼的物理結構對60Hz刷新率的感受就已經是流暢了。
另外從執行者的角度看CPU與GPU的分工:
CPU:Measure,Layout,紋理和多邊形生成,發送紋理和多邊形到GPU
GPU:將CPU生成的紋理和多邊形進行柵格化以及合成
好了,弄清楚上述信息之后,來看看VSYNC信號的例子:
1)沒有VSYNC信號同步時
1> 第一個16ms開始:Display顯示第0幀,CPU處理完第一幀后,GPU緊接其后處理繼續第一幀。三者都在正常工作。
2> 進入第二個16ms:因為早在上一個16ms時間內,第1幀已經由CPU,GPU處理完畢。故Display可以直接顯示第1幀。顯示沒有問題。但在本16ms期間,CPU和GPU卻并未及時去繪制第2幀數據(前面的空白區表示CPU和GPU忙其它的事),直到在本周期快結束時,CPU/GPU才去處理第2幀數據。
3> 進入第三個16ms,此時Display應該顯示第2幀數據,但由于CPU和GPU還沒有處理完第2幀數據,故Display只能繼續顯示第一幀的數據,結果使得第1幀多畫了一次(對應時間段上標注了一個Jank),導致錯過了顯示第二幀。
通過上述分析可知,此處發生Jank的關鍵問題在于,為何第1個16ms段內,CPU/GPU沒有及時處理第2幀數據?原因很簡單,CPU可能是在忙別的事情,不知道該到處理UI繪制的時間了。可CPU一旦想起來要去處理第2幀數據,時間又錯過了。 為解決這個問題,Android 4.1中引入了VSYNC,核心目的是解決刷新不同步的問題。
2)引入VSYNC信號同步后
在加入VSYNC信號同步后,每收到VSYNC中斷,CPU就開始處理各幀數據。已經解決了刷新不同步的問題。
但是上圖中仍然存在一個問題:
CPU和GPU處理數據的速度似乎都能在16ms內完成,而且還有時間空余,這種情況沒問題。
CPU/GPU的幀率高于Display的刷新率,這種情況雖然顯示也沒問題,但是CPU/GPU出現空閑狀態。
但如果CPU/GPU的幀率低于Display的刷新率,情況又不同了,將會發生如下圖的情況:
1> 在第二個16ms時間段,Display本應顯示B幀,但卻因為GPU還在處理B幀,導致A幀被重復顯示。
2> 同理,在第二個16ms時間段內,CPU無所事事,因為A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦過了VSYNC時間點,CPU就不能被觸發以處理繪制工作了。
上述情況產生了兩個問題:1. CPU/GPU的幀率高于Display的刷新率,CPU/GPU出現空閑狀態,這個狀態能不能利用起來提前為下一輪準備好數據呢?2. 為什么CPU不能在第二個16ms處開始繪制工作呢?原因就是只有兩個Buffer(Android 4.1之前)。如果有第三個Buffer的存在,CPU就能直接使用它,而不至于空閑。
2.2 Tripple Buffer
針對上述問題,google給出的解決方案是:加入第三個Buffer,CPU和GPU還有SurfaceFlinger各占一個Buffer,并行處理圖形:
上圖中,第二個16ms時間段,CPU使用C Buffer繪圖。雖然還是會多顯示A幀一次,但后續顯示就比較順暢了。
是不是Buffer越多越好呢?回答是否定的。由上圖可知,在第二個時間段內,CPU繪制的第C幀數據要到第四個16ms才能顯示,這比雙Buffer情況多了16ms延遲。所以緩沖區并不是越多越好。
稍微總結下:
1> 一個Buffer: 如果在同一個Buffer進行讀取和寫入(合成)操作,將會導致屏幕顯示多幀內容, 出現圖像撕裂現象。
2> 兩個Buffer: 解決撕裂問題,但是有卡頓問題,而且CPU/GPU利用率不高
3> 三個Buffer: 提高CPU/GPU利用率,減少卡頓,但是缺引入延遲問題。
2.3 Choreographer
屏幕的VSync信號只是用來控制幀緩沖區的切換,并未控制上層的繪制節奏,也就是說上層的生產節奏和屏幕的顯示節奏是脫離的:
VSync信號如果光是切換backBuffer 和 frontBuffer,而不及時通知上層開始CPU GPU的工作,那么顯示內容的合成還是無法同步的。
所以,google在Android 4.1系統中加入了上層接收垂直同步信號的邏輯,大致流程如下:
也就是說,屏幕在顯示完一幀后,發出的垂直同步除了通知幀緩沖區的切換之外,該消息還會發送到上層,通知上層開始繪制下一幀。
那么,上層是如何接受這個VSync消息的呢?
Google為上層設計了一個Choreographer類,作為VSync信號的上層接收者。
首先看看Choreographer的類圖:
可以發現,Choreographer需要向SurfaceFlinger來注冊一個VSync信號的接收器DisplayEventReceiver。同時在Choreographer的內部維護了一個CallbackQueue,用來保存上層關心VSync信號的組件,包括ViewRootImpl,TextView,ValueAnimator等。
再看看上層接收VSync的時序圖:
知道了Choreographer是上層用來接收VSync的角色之后,我們需要進一步了解VSync信號是如何控制上層的繪制的:
一般,上層需要繪制新的UI都是因為View的requestLayout或者是invalidate方法被調用觸發的,我們以這個為起點,跟蹤上層View的繪制流程:
requestLayout或者invalidate觸發更新視圖請求
更新請求傳遞到ViewRootImpl中,ViewRootImpl向主線程MessageQueue中加入一個阻塞器,該阻塞器將會攔截所有同步消息,也就是說此時,我們再通過Handler向主線程MessageQueue發送的所有Message都將無法被執行。
ViewRootImpl向Choreographer注冊下一個VSync信號
Choreographer通過DisplayEventReceiver向framework層注冊下一個VSync信號
當底層產生下一個VSync消息時,該信號將會發送給DisplayEventReceiver,最后傳遞給Choreographer
Choreographer收到VSync信號之后,向主線程MessageQueue發送了一個異步消息,我們在第二步提到,ViewRootImpl向MessageQueue發送了一個同步消息阻塞器。這里Choreographer發送的異步消息,是不會被阻塞器攔截的。
最后,異步消息的執行者是ViewRootImpl,也就是真正開始繪制下一幀了
總結:
Android通過Buffer來保存圖形信息,為了讓圖形顯示的更加流程,在提供一一個Buffer用于顯示的同時,開辟一個或者多個Buffer用于后臺圖形的合成。
Android4.1之前,VSync信號并未傳遞給上層,導致生產與消費節奏不統一。
Android4.1之后,上層開始繪制時機都放到了VSync信號的到來時候
除了在上層引入VSync機制,Anroid在4.1還加入了三緩沖,用來減少卡頓的產生。
每個Surface都有自己的繪制流程,需要先經過CPU處理,再經過GPU處理,之后經過SurfaceFlinger與其他Surface繪制好的圖形和合成在一起,供屏幕顯示。
VSync信號貫穿整個繪制流程,控制著整個Android圖形系統的節奏。
本篇只是簡單描述下圖形系統渲染流程的輪廓,我從下一篇開始詳細分析每個流程。本文僅代表我個人的理解,歡迎批評指正!
參考:
https://blog.csdn.net/armwind/article/details/73436532
https://blog.csdn.net/STN_LCD/article/details/73801116
https://blog.csdn.net/a740169405/article/details/70548443?utm_source=blogxgwz0
https://blog.csdn.net/freekiteyu/article/details/79483406