本文是 Android Systrace 系列文章的第五篇,主要是對 Android 系統中的 SurfaceFlinger 進行簡單介紹,介紹了 SurfaceFlinger 中幾個比較重要的線程,包括 Vsync 信號的解讀、應用的 Buffer 展示、卡頓判定等,由於 Vsync 這一塊在Systrace 基礎知識 - Vsync 解讀 和 Android 基於 Choreographer 的渲染機制詳解 這兩篇文章裏面已經介紹過,這裏就不再做詳細的講解了。
本系列的目的是通過 Systrace 這個工具,從另外一個角度來看待 Android 系統整體的運行,同時也從另外一個角度來對 Framework 進行學習。也許你看了很多講 Framework 的文章,但是總是記不住代碼,或者不清楚其運行的流程,也許從 Systrace 這個圖形化的角度,你可以理解的更深入一些。
系列文章目錄
Systrace 簡介 Systrace 基礎知識 - Systrace 預備知識 Systrace 基礎知識 - Why 60 fps ? Systrace 基礎知識 - SystemServer 解讀 Systrace 基礎知識 - SurfaceFlinger 解讀 Systrace 基礎知識 - Input 解讀 Systrace 基礎知識 - Vsync 解讀 Systrace 基礎知識 - Vsync-App :基於 Choreographer 的渲染機制詳解 Systrace 基礎知識 - MainThread 和 RenderThread 解讀 Systrace 基礎知識 - Binder 和鎖競爭解讀 Systrace 基礎知識 - Triple Buffer 解讀 Systrace 基礎知識 - CPU Info 解讀
正文
這裏直接上官方對於 SurfaceFlinger 流程的定義
大多數應用在屏幕上一次顯示三個層:屏幕頂部的狀態欄、底部或側面的導航欄以及應用界面。有些應用會擁有更多或更少的層(例如,默認主屏幕應用有一個單獨的壁紙層,而全屏遊戲可能會隱藏狀態欄)。每個層都可以單獨更新。狀態欄和導航欄由系統進程渲染,而應用層由應用渲染,兩者之間不進行協調。 設備顯示會按一定速率刷新,在手機和平板電腦上通常爲 60 fps。如果顯示內容在刷新期間更新,則會出現撕裂現象;因此,請務必只在週期之間更新內容。在可以安全更新內容時,系統便會收到來自顯示設備的信號。由於歷史原因,我們將該信號稱爲 VSYNC 信號。 刷新率可能會隨時間而變化,例如,一些移動設備的幀率範圍在 58 fps 到 62 fps 之間,具體要視當前條件而定。對於連接了 HDMI 的電視,刷新率在理論上可以下降到 24 Hz 或 48 Hz,以便與視頻相匹配。由於每個刷新週期只能更新屏幕一次,因此以 200 fps 的幀率爲顯示設備提交緩衝區就是一種資源浪費,因爲大多數幀會被捨棄掉。SurfaceFlinger 不會在應用每次提交緩衝區時都執行操作,而是在顯示設備準備好接收新的緩衝區時纔會喚醒。 當 VSYNC 信號到達時,SurfaceFlinger 會遍歷它的層列表,以尋找新的緩衝區。如果找到新的緩衝區,它會獲取該緩衝區;否則,它會繼續使用以前獲取的緩衝區。SurfaceFlinger 必須始終顯示內容,因此它會保留一個緩衝區。如果在某個層上沒有提交緩衝區,則該層會被忽略。 SurfaceFlinger 在收集可見層的所有緩衝區之後,便會詢問 Hardware Composer 應如何進行合成。」
---- 引用自SurfaceFlinger 和 Hardware Composer
下面是上述流程所對應的流程圖, 簡單地說, SurfaceFlinger 最主要的功能:「SurfaceFlinger 接受來自多個來源的數據緩衝區,對它們進行合成,然後發送到顯示設備。」
那麼 Systrace 中,我們關注的重點就是上面這幅圖對應的部分
App 部分 BufferQueue 部分 SurfaceFlinger 部分 HWComposer 部分
這四部分,在 Systrace 中都有可以對應的地方,以時間發生的順序排序就是 1、2、3、4,下面我們從 Systrace 的這四部分來看整個渲染的流程
App 部分
關於 App 部分,其實在Systrace 基礎知識 - MainThread 和 RenderThread 解讀這篇文章裏面已經說得比較清楚了,不清楚的可以去這篇文章裏面看,其主要的流程如下圖:
從 SurfaceFlinger 的角度來看,App 部分主要負責生產 SurfaceFlinger 合成所需要的 Surface。
App 與 SurfaceFlinger 的交互主要集中在三點
Vsync 信號的接收和處理 RenderThread 的 dequeueBuffer RenderThread 的 queueBuffer
Vsync 信號的接收和處理
關於這部分內容可以查看Android 基於 Choreographer 的渲染機制詳解 這篇文章,App 和 SurfaceFlinger 的第一個交互點就是 Vsync 信號的請求和接收,如上圖中第一條標識,Vsync-App 信號到達,就是指的是 SurfaceFlinger 的 Vsync-App 信號。應用收到這個信號後,開始一幀的渲染準備
RenderThread 的 dequeueBuffer
dequeue 有出隊的意思,dequeueBuffer 顧名思義,就是從隊列中拿出一個 Buffer,這個隊列就是 SurfaceFlinger 中的 BufferQueue。如下圖,應用開始渲染前,首先需要通過 Binder 調用從 SurfaceFlinger 的 BufferQueue 中獲取一個 Buffer,其流程如下:
「App 端的 Systrace 如下所示」
「SurfaceFlinger 端的 Systrace 如下所示」
RenderThread 的 queueBuffer
queue 有入隊的意思,queueBuffer 顧名思義就是講 Buffer 放回到 BufferQueue,App 處理完 Buffer 後(寫入具體的 drawcall),會把這個 Buffer 通過 eglSwapBuffersWithDamageKHR -> queueBuffer 這個流程,將 Buffer 放回 BufferQueue,其流程如下
「App 端的 Systrace 如下所示」
「SurfaceFlinger 端的 Systrace 如下所示」
通過上面三部分,大家應該對下圖中的流程會有一個比較直觀的瞭解了
BufferQueue 部分
BufferQueue 部分其實在Systrace 基礎知識 - Triple Buffer 解讀 這裏有講,如下圖,結合上面那張圖,每個有顯示界面的進程對應一個 BufferQueue,使用方創建並擁有 BufferQueue 數據結構,並且可存在於與其生產方不同的進程中,BufferQueue 工作流程如下:
上圖主要是 dequeue、queue、acquire、release ,在這個例子裏面,App 是「生產者」,負責填充顯示緩衝區(Buffer);SurfaceFlinger 是「消費者」,將各個進程的顯示緩衝區做合成操作
dequeue(生產者發起) : 當生產者需要緩衝區時,它會通過調用 dequeueBuffer() 從 BufferQueue 請求一個可用的緩衝區,並指定緩衝區的寬度、高度、像素格式和使用標記。 queue(生產者發起):生產者填充緩衝區並通過調用 queueBuffer() 將緩衝區返回到隊列。 acquire(消費者發起) :消費者通過 acquireBuffer() 獲取該緩衝區並使用該緩衝區的內容 release(消費者發起) :當消費者操作完成後,它會通過調用 releaseBuffer() 將該緩衝區返回到隊列
SurfaceFlinger 部分
工作流程
從最前面我們知道 SurfaceFlinger 的主要工作就是合成:
❝當 VSYNC 信號到達時,SurfaceFlinger 會遍歷它的層列表,以尋找新的緩衝區。如果找到新的緩衝區,它會獲取該緩衝區;否則,它會繼續使用以前獲取的緩衝區。SurfaceFlinger 必須始終顯示內容,因此它會保留一個緩衝區。如果在某個層上沒有提交緩衝區,則該層會被忽略。SurfaceFlinger 在收集可見層的所有緩衝區之後,便會詢問 Hardware Composer 應如何進行合成。
❞
其 Systrace 主線程可用看到其主要是在收到 Vsync 信號後開始工作
其對應的代碼如下,主要是處理兩個 Message
MessageQueue::INVALIDATE --- 主要是執行 handleMessageTransaction 和 handleMessageInvalidate 這兩個方法 MessageQueue::REFRESH --- 主要是執行 handleMessageRefresh 方法
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::onMessageReceived(int32_t what) NO_THREAD_SAFETY_ANALYSIS {
ATRACE_CALL();
switch (what) {
case MessageQueue::INVALIDATE: {
......
bool refreshNeeded = handleMessageTransaction();
refreshNeeded |= handleMessageInvalidate();
......
break;
}
case MessageQueue::REFRESH: {
handleMessageRefresh();
break;
}
}
}
//handleMessageInvalidate 實現如下
bool SurfaceFlinger::handleMessageInvalidate() {
ATRACE_CALL();
bool refreshNeeded = handlePageFlip();
if (mVisibleRegionsDirty) {
computeLayerBounds();
if (mTracingEnabled) {
mTracing.notify("visibleRegionsDirty");
}
}
for (auto& layer : mLayersPendingRefresh) {
Region visibleReg;
visibleReg.set(layer->getScreenBounds());
invalidateLayerStack(layer, visibleReg);
}
mLayersPendingRefresh.clear();
return refreshNeeded;
}
//handleMessageRefresh 實現如下, SurfaceFlinger 的大部分工作都是在handleMessageRefresh 中發起的
void SurfaceFlinger::handleMessageRefresh() {
ATRACE_CALL();
mRefreshPending = false;
const bool repaintEverything = mRepaintEverything.exchange(false);
preComposition();
rebuildLayerStacks();
calculateWorkingSet();
for (const auto& [token, display] : mDisplays) {
beginFrame(display);
prepareFrame(display);
doDebugFlashRegions(display, repaintEverything);
doComposition(display, repaintEverything);
}
logLayerStats();
postFrame();
postComposition();
mHadClientComposition = false;
mHadDeviceComposition = false;
for (const auto& [token, displayDevice] : mDisplays) {
auto display = displayDevice->getCompositionDisplay();
const auto displayId = display->getId();
mHadClientComposition =
mHadClientComposition || getHwComposer().hasClientComposition(displayId);
mHadDeviceComposition =
mHadDeviceComposition || getHwComposer().hasDeviceComposition(displayId);
}
mVsyncModulator.onRefreshed(mHadClientComposition);
mLayersWithQueuedFrames.clear();
}
handleMessageRefresh 中按照重要性主要有下面幾個功能
準備工作 preComposition(); rebuildLayerStacks(); calculateWorkingSet();
合成工作 begiFrame(display); prepareFrame(display); doDebugFlashRegions(display, repaintEverything); doComposition(display, repaintEverything);
收尾工作 logLayerStats(); postFrame(); postComposition();
由於顯示系統有非常龐大的細節,這裏就不一一進行講解了,如果你的工作在這一部分,那麼所有的流程都需要熟悉並掌握,如果只是想熟悉流程,那麼不需要太深入,知道 SurfaceFlinger 的主要工作邏輯即可
掉幀
通常我們通過 Systrace 判斷應用是否「掉幀」的時候,一般是直接看 SurfaceFlinger 部分,主要是下面幾個步驟
SurfaceFlinger 的主線程在每個 Vsync-SF 的時候是否沒有合成? 如果沒有合成操作,那麼需要看沒有合成的原因: 因爲 SurfaceFlinger 檢查發現沒有可用的 Buffer 而沒有合成操作? 因爲 SurfaceFlinger 被其他的工作佔用(比如截圖、HWC 等)?
如果有合成操作,那麼需要看對應的 App 的 可用 Buffer 個數是否正常:如果 App 此時可用 Buffer 爲 0,那麼看 App 端爲何沒有及時 queueBuffer(這就一般是應用自身的問題了),因爲 SurfaceFlinger 合成操作觸發可能是其他的進程有可用的 Buffer
關於這一部分的 Systrace 怎麼看,在 Systrace 基礎知識 - Triple Buffer 解讀-掉幀檢測 部分已經有比較詳細的解讀,大家可以過去看這一段
HWComposer 部分
關於 HWComposer 的功能部分我們就直接看官方的介紹即可
Hardware Composer HAL (HWC) 用於確定通過可用硬件來合成緩衝區的最有效方法。作爲 HAL,其實現是特定於設備的,而且通常由顯示設備硬件原始設備製造商 (OEM) 完成。 當您考慮使用疊加平面時,很容易發現這種方法的好處,它會在顯示硬件(而不是 GPU)中合成多個緩衝區。例如,假設有一部普通 Android 手機,其屏幕方向爲縱向,狀態欄在頂部,導航欄在底部,其他區域顯示應用內容。每個層的內容都在單獨的緩衝區中。您可以使用以下任一方法處理合成(後一種方法可以顯著提高效率): 將應用內容渲染到暫存緩衝區中,然後在其上渲染狀態欄,再在其上渲染導航欄,最後將暫存緩衝區傳送到顯示硬件。 將三個緩衝區全部傳送到顯示硬件,並指示它從不同的緩衝區讀取屏幕不同部分的數據。
顯示處理器功能差異很大。疊加層的數量(無論層是否可以旋轉或混合)以及對定位和疊加的限制很難通過 API 表達。爲了適應這些選項,HWC 會執行以下計算(由於硬件供應商可以定製決策代碼,因此可以在每臺設備上實現最佳性能): SurfaceFlinger 向 HWC 提供一個完整的層列表,並詢問“您希望如何處理這些層?” HWC 的響應方式是將每個層標記爲疊加層或 GLES 合成。 SurfaceFlinger 會處理所有 GLES 合成,將輸出緩衝區傳送到 HWC,並讓 HWC 處理其餘部分。
當屏幕上的內容沒有變化時,疊加平面的效率可能會低於 GL 合成。當疊加層內容具有透明像素且疊加層混合在一起時,尤其如此。在此類情況下,HWC 可以選擇爲部分或全部層請求 GLES 合成,並保留合成的緩衝區。如果 SurfaceFlinger 返回來要求合成同一組緩衝區,HWC 可以繼續顯示先前合成的暫存緩衝區。這可以延長閒置設備的電池續航時間。 運行 Android 4.4 或更高版本的設備通常支持 4 個疊加平面。嘗試合成的層數多於疊加層數會導致系統對其中一些層使用 GLES 合成,這意味着應用使用的層數會對能耗和性能產生重大影響。
-------- 引用自SurfaceFlinger 和 Hardware Composer
我們繼續接着看 SurfaceFlinger 主線程的部分,對應上面步驟中的第三步,下圖可以看到 SurfaceFlinger 與 HWC 的通信部分
這也對應了最上面那張圖的後面部分
不過這其中的細節非常多,這裏就不詳細說了。至於爲什麼要提 HWC,因爲 HWC 不僅是渲染鏈路上重要的一環,其性能也會影響整機的性能,Android 中的卡頓丟幀原因概述 - 系統篇 這篇文章裏面就有列有 HWC 導致的卡頓問題(性能不足,中斷信號慢等問題)
想了解更多 HWC 的知識,可以參考這篇文章Android P 圖形顯示系統(一)硬件合成HWC2,當然,作者的Android P 圖形顯示系這個系列大家可以仔細看一下
參考文章
關於我 && 博客
關於我 , 非常希望和大家一起交流 , 共同進步 . 博客內容導航 優秀博客文章記錄 - Android 性能優化必知必會
「一個人可以走的更快 , 一羣人可以走的更遠」
本文使用 mdnice 排版