從Android?12開始,Android的繪制系統有結構性變化,?在繪制的生產消費者模式中,新增BLASTBufferQueue,客戶端進程自行進行queue的生產和消費,隨后通過Transation提交到SurfaceFlinger,如此可以使得各進程將緩存提交到SufrfaceFlinger后合并到同一事務后同步提交,在同一幀生效。實際上,從Android12到Android14整個繪制系統在各個環節也都有了或大或小的調整,比如Android13發布了1.3版本的Vulkan,?Android14新增了TextureView,等等。本文基于Android14。
Android?繪制系統整體架構:
從上到下可以理解為“生產者(Producer)”到“消費者(Consumer)”的處理過程。
首先,從WindowManagerService的角度,每個窗口稱為Window,一個Window一般是一個APP的頁面,或者Status Bar,或者Navigation Bar,或者WallPaper,這些都是一個個Window。WindowManagerService(WMS)作為服務端,對所有客戶端窗口的添加、層級、布局等進行統一管理。在WMS端,每個Window對應一個Surface。Surface可以理解為圖像數據緩存的持有者,以及Canvas的持有者。Canvas是畫布,提供了繪制各種圖形的能力供開發者使用。一個客戶端窗口在建立之初,會先向WMS去申請一個Surface,WMS在創建了Surface之后,通過binder返回給客戶端。客戶端拿到Surface后,會去創建一個BLASTBufferQueue來管理圖像內存的申請。每次要使用Surface的Canvas進行繪制前,需要先向BLASTBufferQueue申請一塊內存(dequeue),我們這里稱為Buffer,然后再將生成的圖像數據寫入Buffer。這個向BLASTBufferQueue申請Buffer并寫入圖像數據的過程,可以認為是“生產”階段。隨后,enqueue這個buffer,將其提交給SurfaceFlinger去合成。這個階段,可以理解為圖像Buffer的“消費”階段。
SurfaceFlinger(SF)是負責與Hardware層溝通,維護著設備掛載、VSync信號收發、Layer合成等工作。WMS的每個Surface在SurfaceFlinger中都對應生成一個Layer對象。客戶端將某個Surface上的Buffer提交給SurfaceFlinger,實際上就是更新了對應Layer的Buffer數據。SurfaceFlinger調用HWComposer將這些Layer進行合成并顯示在屏幕。
Android在HAL層提供稱為一個Hardware Composer的組件,用于隔離與具體硬件的交互。Hardware Composer簡稱HWComposer或HWC2(之所以是2,是早期已有一個HWC版本,只支持軟件合成)。SurfaceFlinger把Layer數據交給HWComposer,各廠商來負責HWComposer合成接口的具體實現。在合成完畢后,將數據提交到屏幕設備的緩存(一般稱為Frame?Buffer),屏幕就顯示出畫面來了。
上面的過程,可以拆解為幾部分:
- Surface的創建與管理。
- 客戶端(EndPoint)繪制(Draw)和渲染(Render)圖像。
- 第三部分,是硬件Composition(合成)工作
- Vsync:由硬件產生的信號,用于同步framebuffer的生產和消費。SurfaceFlinger對Vsync進行了使用和管理,并向上分發給APP。Vsync是不斷繪制的驅動力,也是圖像緩存有序投送到屏幕的重要機制。
現在,分別討論下四部分:
- Surface的創建與管理
在Surface的創建過程中,有幾個角色貫穿其中:
PhoneWindow:一個Activity對應一個PhoneWindow,代表一個應用窗口。在AMS創建Activity之初,PhoneWindow在服務端的對應的window對象(ActivityRecord)已經添加到WMS。
ViewRootImpl:其主要作用是與服務端通信,承接外部觸發的繪制調用,從而從上往下對整個View樹進行繪制。可以把ViewRootImp理解為View的調度者。ViewRootImp在邏輯上是View Hierarchy的最頂層,但其并不是一個真正的View。他持有一個字View--DecorView,DecorView才是真正的View,是View樹的最上層,包含著Activity的畫面內容。在Activity的resume階段,ViewRootImpl的relayout方法會將DecorView添加到WMS中,這樣Activity的內容就顯示了出來。邏輯上,我們可以把DecorView也理解為一個Window。Activity對應一個PhoneWindow,再通過ViewRootImpl將DecorView在WMS端添加為PhoneWindow的子Window。
WMS的Session:客戶端一個進程對應WMS里的一個Session,客戶端持有Session的binder客戶端,在窗口添加等事務上,客戶端都是通過這個Session來與WMS通信的。
WindowContainer:WMS端管理系統整體的Window體系,包括其位置、層級關系。它是通過WindowContainer這個類來表達一個Window的。DisplayContent代表一個屏幕級別的Window,DisplayArea代表一塊屏幕上的一塊區域,比如平板等大屏幕設備上,可能一塊屏幕上同時顯示多個應用區域,此時就用DisplayArea表達。WindowToken簡單理解為對應一個客戶端Window,比如一個應用的Activity,這里需要注意的是,Activity的WindowToken是作為ActivityRecord存在的,也就是說ActivityRecord是WindowToken的子類。而Activity的具體內容的承載者,DecorView,對應WindowState。上面所有的DisplayContent、DisplayArea、WindowToken、WindowState等,都是WindowContainer的子類,這些Window在WMS內是以window樹的形式組織起來的。事實上,在DisplayContent下面,還有一個層級,稱為Feature,具體的層級結構見Android12 - WMS之WindowContainer樹(DisplayArea)_android windowcontainer-CSDN博客。當客戶端通過Session接口調用添加DecorView時,WMS端會生成一個對應的WindowState對象,并將其作為Activity對應的ActivityRecord(也就是WindowToken)的子window。
SurfaceControl:在WMS端,每個WindowContainer對應一個SurfaceControl。SurfaceControl是WMS端管理Surface的具體對象,在WMS端,可以理解一個SurfaceControl就代表一個Surface。SurfaceControl在SurfaceFlinge端對應一個Layer,持有一個layer的句柄handle。所有的繪制動作,最后都會提交到SurfaceFinger作為Layer去合成。SurfaceControl的作用,或者Surface的作用,主要是將客戶端的窗口與SurfaceFlinger的Layer關聯起來。在客戶端Add一個DecorView時, 在WMS端對應創建的WindowState會同時創建一個SurfaceControl、Layer,隨后將SurfaceControl返回給客戶端。客戶端拿到SurfaceControl之后轉換成Surface。后續的繪制就在這個Surface上進行。
SurfaceComposerClient:是一個Binder,主要作用是SurfaceControl調用SurfaceFlinger過程中,作為一個通道的角色。由于SurfaceControl在WMS、客戶端都持有,所以客戶端、WMS都可以通過這個通道調用SF。比如Layer的創建、Graphihc?Buffer的提交等。
1. 客戶端繪制和渲染
客戶端通過Surface中提供的Canvas進行繪制,Canvas是基于Skia的SKCanvas。Skia(https://skia.org/)是由Google管理的開源2D(也可以支持3D)圖像庫,目前Android、Google Chrome、ChromeOS、Mozilla?FireFox、FireFoxOS上都使用Skia作為繪制引擎。Skia可以集成OPEN?GL和Vulkan進行3D繪制。Android Q以后,Skia作用被加強,即使硬件加速場景中,繪制也會先封裝成Skia的GrOpList再提交給GPU。在Android 14中, Skia包的目錄為external/skia。
渲染的過程是將畫好的圖像,進行柵格化(Rasterizer),變成一個個像素,這是一個非常耗時的過程。Android 3以前,只支持軟件渲染,即Software Render。過程如下:
APP在View的onDraw階段使用Canvas繪制后,通過Skia進行軟件的柵格化,即通過CPU計算,將繪制內容轉化成一個個像素信息,隨后投送給屏幕進行顯示。由于軟件渲染效率低,當下軟件渲染只是作為兼容方案得以保留,默認使用硬件加速。
硬件加速的流程簡單表述如下:
Android將硬件加速相關能力封裝在hwui組件中,hwui地址:platform/frameworks/base/libs/hwui
在硬件加速模式下,APP在onDraw中通過Canvas繪制的內容將最終被封裝成DisplayList的一個個GrOp繪制命令,然后通過OpenGL或者Vulkan交由GPU進行渲染,隨后將結果投送給屏幕顯示。而具體是使用OpenGL還是Vulkan是可選擇的。早期Android只使用OpenGL,由于Vulkan支持多線程渲染等性能方面的優勢,Android逐漸傾向使用Vulkan進行渲染。另外,在哪些維度上進行硬件加速也是可選的:
即在整體使用硬件加速的情況下,如果某個View的繪制暫時不支持硬件加速,或者在某些位移動畫上為了減少渲染成本,可以動過設置View的layerType?=?LAYER_TYPE_SOFTWARE來單純在某個特定View上使用Software?Render。
硬件加速除了利用GPU來加速渲染效率外, 本身在計算渲染范圍時相較軟件渲染也更加高效,即軟件渲染每次更新一個View局部,將使得整個View hierarchy都重新渲染。而硬件加速如只標注有變化的部分,所謂damage?area,將繪制指令保存在DisplayList中,如此大大提高渲染速度。
OpenGL?ES VS?Vulkan
以下為OpenGL ES和Vulkan在Android上發布的版本歷史。
Vulkan作為一個面向更低級別規范、跨平臺的API,可以提供更細粒度的內存管理和資源管理
以下為Vulkan與OpenGL ES的使用率(from?GDC?2023?https://www.youtube.com/watch?v=C7OjI7CpjLw&t=1188s):
對于未來計劃,OpenGL ES將不會再有功能更新,新的功能將只會在Vulkan上支持。因此,Vulkan是未來Android主推的渲染引擎。
無論是OpenGL還是Vulkan,都需要GPU的支持。例如常見的車載高端芯片高通8155,明確標明了支持:?OpenGL?ES?3.x、Vulkanhttps://www.qualcomm.com/content/dam/qcomm-martech/dm-assets/documents/qul7413_sa8155_productbrief_r4.pdf
2. HW Composer(HWC2)圖像合成
前面提到過,每個window對應一個SF中的Layer,合成(Composition)工作就是將這些Layer進程合并成一個完整的屏幕內容,提交給硬件屏幕顯示出來。大概過程如下:
這個頁面有三個Layer:StatusBar、NavigationBar和中間的APP內容頁面,其中可能會有重疊的部分,稱為Overlay。Composition的工作就是將這三個Layer合并成一個畫面,計算重疊部分的顏色,提交給屏幕顯示出來。
合成的工作發生在渲染后的內容提交給SurfaceFlinger之后。大致流程如下:
合成有硬件合成的部分和軟件合成的部分。硬件合成除了更高效的同時,可以將合成工作從GPU解放出來,提高GPU效率,節省能耗。嵌入式設備的SOC中,硬件的合成一般由獨立的DPU(Display?Processing)完成。
比如高通SA8155這款SOC的布局如下:
其中GPU的部分負責渲染,“Dispay?Processing”的部分用來處理合成工作。
由于硬件對合成Layer數量是有限制的,例如高通QCS2290支持4個Layer、AMD有的芯片支持7個等)以及Layer的PixelFormat(比如支持PIXEL_FORMAT_RGBA_8888,不支持YUV)是有限制的,因此在硬件合成之前,如果合成Layer過多或者Format不滿足要求,會需要使用GPU先進行一輪軟件合成,合并或轉換一些Layer的格式。
軟件合成過程。(Google?I/O?'18)
4. VSYNC
VSync簡介:
首先關注兩個重要概念:
refresh?rate - 60Hz :代表每秒鐘屏幕可以更新多少次,這一值早期是固定的,依賴于硬件。現代旗艦設備的屏幕都支持多個刷新率,從60Hz~165Hz不等,而且是可以由App層定制刷新率。
frame?rate:每秒鐘GPU可以繪制多少幀,值越大越好
VSync是一個通用概念,在Linux、PC、移動設備上都有所實現。
想象一下繪制過程是這樣的:GPU繪制數據,將繪制結果投擲給屏幕顯示出來。
問題是,refresh rate和Frame Rate并不保證是一致的頻率,也就是是說GPU渲染的時間并不能保證就正好是16ms(60Hz)內完成的。如果只有一塊內存(Frame?Buffer)用來交換數據,假如Refresh?Rate大于Frame Rate,由于GPU是從上到下寫這塊內存的,在當屏幕來取數據的時候,GPU剛剛在舊幀基礎上寫了一半的新幀,此時就會出現圖片撕裂問題,如:
解決方法是雙緩存方案:
提供Back?Buffer和Frame?Buffer兩個緩存,屏幕始終從Frame?Buffer取數據顯示,GPU往Back Buffer里寫,當GPU完全將數據寫好后,再將Back Buffer整個拷貝到Frame Buffer。這樣就能保證屏幕每次都取到完整的幀。
此時仍有一個問題,如果GPU的Frame?Rate大于屏幕的Refresh Rate,那么屏幕再取到下一幀前,可能GPU都寫完好幾幀了,就會出現丟幀現象。此時就需要VSync:
屏幕根據自己的刷新頻率,去給上層發送一個VSync信號,GPU在拿到這個VSync信號后,才去繪制。這樣就能同步屏幕與上層繪制的節奏了。
如果屏幕的Refresh Rate大于GPU的Frame Rate怎么辦?
屏幕將會仍然顯示舊幀。比如中間方框的兩次刷新,屏幕仍然顯示前一次的幀內容。
Android的VSYNC
實際上Android的VSync要復雜得多,主要由SurfaceFlinger負責實現。通過之前的介紹我們知道一幀的繪制過程有APP繪制渲染、SurfaceFlinger合成、Display硬件讀取幀緩存顯示圖片三個階段,如果每一個階段都依賴VSync信號來執行,那可能會出現這種情況:
也就是說,VSync1的時候APP正在繪制渲染,SF還沒有可以合成的東西,所以什么都不做;等到VSync2的時候,Render1的工作已經完成,可以做合成了;VSync3的時候,合成做完了,才可以顯示到屏幕上。從繪制渲染到顯示經歷了3個VSync。面對這種情況,Android對VSync的設計如下:
即有三種信號
HW_VSYNC_[ID]:由底層硬件按Refresh?Rate的頻率發出,一般為60Hz、90Hz、120H等等,隨后會通過HWC通知給SurfaceFlinger。
VSYNC-app:SurfaceFlinger通知給上層應用的VSYNC,用于控制和驅動應用的繪制渲染。
VSYNC-sf:通知給SurfaceFlinger自身的,用于合成Layer的信號。
VSYNC-app和VSYNC-sf相對于HW_VSYNC_[ID],并不是同步發送的,而是有一定的延遲,稱為相位差。從HW_VSYNC_[ID]到VSYNC-app發出的時間差稱為app phase,HW_VSYNC_[ID]到VSYNC-sf發出的時間差稱為sf phase。這種設計的好處是,如果在同一個VSync周期內,經sf?phase后在執行合成時恰好前一步的Render完成了,就可在一個周期完成兩步,而不用非得等下一個VSync。
另外,Android并非直接把硬件的HW_VSYNC_[ID]信號直接分發給應用和SurfaceFlinger,而是通過先收集HW_VSYNC_[ID]樣本,再根據屏幕Refresh Rate、預先配置的相位差等信息,經過計算后模擬出來的VSYNC-app和VSYNC-sf。
由于只需要一定的硬件VSync樣本后便可以模擬出預期的VSYNC-app和VSYNC-sf,因此并不需要一直接收HW_VSYNC_[ID]信號,在收到足夠的樣本數后(在Android?14中為6個)就可以關閉硬件VSync的接收。在每次將合成數據提交給屏幕后,會返回一個硬件VSync時間戳(PresentFence),此時SF會對比當前模擬VSync與硬件VSync是否誤差過大,如果過大,會重新打開硬件VSync收集樣本重新計算。另外,每次終端應用主動請求VSync時,也會判斷前后兩次模擬的VSync時間差是否超過750ms,如果是則重新請求打開硬件VSync。在systrace上硬件VSync打開的TAG是HW_VSYNC_ON_[ID]。
參考資料:https://source.android.com/docs/core/graphics/implement-vsync
可變刷新率
現代旗艦機屏幕的刷新率是可變的,比如Pixel?5:
可以看到,該屏幕是支持60Hz、90Hz兩種刷新率的。
而且應用層也可以在應用級別、窗口級別指定具體的刷新率。在經過應用層指定后,最終的刷新率并不一定是指定的值,而是經過SurfaceFlinger綜合計算后得出。具體見https://developer.android.com/media/optimize/performance/frame-rate