前言
本文將會作為開啟SurfaceFlinger的系列第一篇文章陵像。然而SurfaceFlinger幾乎貫通了整個Android領域中所有的知識闸溃。從HAL硬件抽象層到Framework層逆粹,從CPU繪制到OpenGL等硬件繪制裤唠。
為了讓整個系列的書寫更有邏輯性。這一次我將一反常態(tài)鞠值,先把整個架構的設計思想概述寫出來媚创,作為后面的系列文章的指導彤恶。本文之后都會將SurfaceFlinger稱為SF。
遇到什么疑問声离,可以來本文下討論:http://www.reibang.com/p/c954bcceb22a
正文
SF的渲染第一定律:
SF是整個Android系統(tǒng)渲染的核心進程芒炼。所有應用的渲染邏輯最終都會來到SF中進行處理焕议,最終會把處理后的圖像數(shù)據(jù)交給CPU或者GPU進行繪制。
姑且讓我們先把這句話當作Android渲染系統(tǒng)的第一定律世囊。SF在整個Android系統(tǒng)中窿祥,并非擔當渲染的角色,而是作為圖元拋射機一樣必逆,把所有應用進程傳遞過來的圖元數(shù)據(jù)加工處理后恳谎,交給CPU和GPU做真正的繪制靠胜。
SF的渲染第二定律:
在每一個應用中都以Surface作為一個圖元傳遞單元浪漠,向SF這個服務端傳遞圖元數(shù)據(jù)。
這是Android渲染體系的第二定律址愿。把這兩個規(guī)律組合起來就是如下一個簡單示意圖。
SF的渲染第三定律:
SF是以生產(chǎn)者以及消費者為核心設計思想俱饿,把每一個應用進程作為生產(chǎn)者生產(chǎn)圖元保存到SF的圖元隊列中塌忽,SF則作為消費者依照一定的規(guī)則把生產(chǎn)者存放到SF中的隊列一一處理土居。
用圖表示就如下:
SF體系渲染的第四定律:
為了能夠跨進程的傳輸大容量的圖元數(shù)據(jù)棉圈,使用了匿名共享內(nèi)存內(nèi)存作為工具把圖元數(shù)據(jù)都輸送到SF中處理眷蜓。
眾所周知吁系,我們需要從應用進程跨進程把圖元數(shù)據(jù)傳輸?shù)絊F進程中處理汽纤,就需要跨進程通信蕴坪,能考慮的如socket這些由于本身效率以及數(shù)據(jù)拷貝了2份(從物理內(nèi)存頁層面上來看)背传,確實不是很好的選擇径玖。一個本身拷貝大量的數(shù)據(jù)就是一個疑問挺狰。那么就需要那些一次拷貝的進程間通信方式丰泊,首先能想到的當然是Binder瞳购,然而Binder進程間通信,特別是應用的通信數(shù)據(jù)總量只有1M不到的大小加上應用其他通信吞杭,勢必會出現(xiàn)不足的問題芽狗。
為了解決這個問題痒蓬,Android使用共享內(nèi)存攻晒,使用的是匿名共享內(nèi)存(Ashmem)鲁捏。匿名共享內(nèi)存也是一種拷貝一次的進程間通信方式给梅,其核心比起binder的復雜的mmap更加接近Linux的共享內(nèi)存的概念破喻。
SF體系渲染的第五定律:
SF底層有一個時間鐘在不斷的循環(huán)曹质,或從硬件中斷發(fā)出羽德,或從軟件模擬發(fā)出計時喚起宅静,每隔一段時間都會獲取SF中的圖元隊列通過CPU/GPU繪制在屏幕姨夹。
第五定律的誕生實際上很符合Android系統(tǒng)的設計情況磷账,除了需要Android應用有辦法通知SF需要渲染的模式逃糟,當然需要SF自己不斷的把圖元繪制到屏幕的行為的自己回調(diào)自己的行為绰咽,SF自己不斷的繪制在SF中的圖元數(shù)據(jù)取募。
其中EventThread扮演一個極其重要的角色笑跛,在SF中設計大致如下:
Vsync的介紹
這里面出現(xiàn)了一個新的名次VSync飞蹂,其實這就是我們玩游戲經(jīng)常說的垂直同步信號陈哑。我以前用渣電腦玩游戲的時候惊窖,經(jīng)常掉幀數(shù)卡的不行界酒,之后關閉了垂直信號后感覺好了點毁欣,讓我有一段時間以為這是個不好的東西凭疮。
這里就先介紹一下Android曾經(jīng)迭代為ui體驗更好上的努力执解,黃油計劃衰腌。黃油計劃故名思議就是為了讓系統(tǒng)的ui表現(xiàn)如黃油表面一樣順滑右蕊。為此誕生了兩個重要的概念Vsync以及Triple Buffer尤泽,即垂直信號和三重緩沖坯约。
雙緩沖的概念大家應該都熟悉闹丐,在OpenGL我已經(jīng)解釋過了卿拴,雙緩沖就是渲染第一幀的同時已經(jīng)在繪制第二幀的內(nèi)容堕花,等到第二幀繪制完畢后就顯示出來缘挽。這么做的好處很明顯壕曼,如果一幀畫完腮郊,才開始畫下一幀轧飞,勢必有一個計算的過程導致ui交互遲緩踪少。
通過這種方式顯示前一幀的時候提前繪制好下一幀圖元,放在背后等待時機交換集漾,這樣就能從感官上流暢不少具篇。
這么做理想十分顯示驱显,但是怎么找到一個合適的時機進行交換前后兩幀這是一個問題埃疫?如果有人在想那就按照屏幕刷新頻率來栓霜,一般按照通用屏幕刷新60fps也就是約16ms刷新一次即可胳蛮。
理想是很豐滿仅炊,但是現(xiàn)實很骨干抚垄,這么做好像沒有問題督勺,我們深入考慮一下智哀,其實這個過程中有兩個變量屯吊,一個是繪圖速度盒卸,一個是顯示速度蔽介。就算是繪圖速度中也有分CPU和GPU的繪制速度虹蓄。
這里就沿用一下當年google在宣傳黃油計劃時候的示意圖薇组。讓我們先看看沒有緩沖正常運作的示意圖:
最好的情況就是上圖宋光,在顯示第0幀的時候跃须,CPU/GPU合成繪制完成第1幀在16ms內(nèi)菇民,當vsync信號來了第练,就把第1幀交換到顯示屏顯示娇掏。
vsync是什么婴梧?玩游戲的時候經(jīng)橙洌看到垂直同步就是它。它的作用是通過屏幕硬件中斷來告訴系統(tǒng)應該什么時候刷新屏幕漱办。通過這樣的方式娩井,大致上16ms的發(fā)送一次中斷讓系統(tǒng)刷新洞辣。
但是很可能出現(xiàn)下面這種情況屋彪,CPU因為繁忙來不及仔粥,顯示完第一幀的時候,還沒空渲染第二幀华糖,就算SF接受到了Vsync的信號客叉,也只能拿出已經(jīng)渲染好的第一幀顯示在屏幕上兼搏。這樣就重復顯示了第一幀,Google開發(fā)團隊稱這種為jank吓著。
能看到顯示第一幀因為第二幀沒準備好绑莺,只能重復顯示第一幀了。
再來看看帶著多重緩沖的的工作原理流程:
能看到此時就不是簡單的第一第二幀对扶,而是分為A緩沖,B緩沖络凿。能看到在正常情況下絮记,先顯示A緩沖的內(nèi)容怨愤,同時準備B緩沖篮愉,當一切正常的時候试躏,B緩沖應該在下一個vsync來之前準備好颠蕴,一旦vsync到來則顯示B緩沖犀被,A緩沖回到后臺繼續(xù)繪制。
那么這種方式一旦遇到jank會是怎么一個情況呢昌腰?
如果是雙緩沖好像沒有問題遭商,但是一旦出現(xiàn)jank了之后,之后顯示屏就會不斷的出現(xiàn)jank祠汇。如果緩沖A在顯示可很,而B準備的時間超過16ms我抠,就會導致A緩沖區(qū)重復顯示,而B當b顯示的時候袜茧,A也很可能準備時間不足16ms導致無法繪制完成菜拓,只能重復顯示B緩沖的內(nèi)容。
這種方式更加的危險笛厦,為了解決這個問題纳鼎,Google引入三重緩沖。
當三重緩沖處理jank的原理流程圖:
能看到為了避免后面連鎖式的錯誤喷橙,引入三重緩沖就為了讓空閑出來的等待時間啥么,能夠做更多的事情登舞。就如同雙緩沖遇到jank之后贰逾,一旦B緩沖CPU+GPU的時間超過了下一個vsync的時間,能夠發(fā)現(xiàn)其實CPU和GPU有一段時間都沒有事情做菠秒,光等待下一次Vsync的到來疙剑,才會導致整個系統(tǒng)后面的繪制出現(xiàn)連鎖式的出現(xiàn)jank。
而三緩沖的出現(xiàn)践叠,在重復顯示A緩沖區(qū)的時候言缤,CPU不會光等待而是會準備C緩沖區(qū)的圖元,之后就能把C緩沖區(qū)接上禁灼。這就是Google所說的三重緩沖區(qū)的來源管挟。
不過絕大多數(shù)情況都緩沖策略是由SF系統(tǒng)自己決定的,一般我們常說的雙緩沖弄捕,三緩沖指的就是這個僻孝。
實際上這種方式也可以用到音視頻的編寫優(yōu)化,里面常用的緩沖區(qū)設計和這里也有同工異曲的之妙守谓,但是沒有系統(tǒng)如此極致穿铆。如果閱讀過系統(tǒng)的videoView源碼就能看到NullPlayer本質(zhì)上就是借助Surface圖元緩沖區(qū)來達到極致的體驗,不過VideoView也有設計不合理的地方斋荞,之后研讀完Android的渲染體系荞雏,讓我們來分析分析這些源碼。
但是這一部分的知識平酿,不足以讓我們?nèi)ダ斫舛?.其實每一次Vsync從硬件/軟件過來的時候凤优,Dispsync都會嘗試著通知SF和app,這是完全沒有問題蜈彼,但是后面那個Phase相位又是什么東西筑辨?
其實這就是系統(tǒng)的設計的巧妙,我們?nèi)绻瑫r把信號通知同時告訴app和sf會導致什么結果柳刮?
如果此時app后返回了圖元挖垛,但是sf已經(jīng)執(zhí)行了刷新合成繪制行為(很有可能,因為app到sf傳輸圖元速度必定比sf自己通知自己慢)秉颗,此時就會導致類似jank的問題痢毒,導致下一個vsync還是顯示當前幀數(shù),因此需要如下一個時間差蚕甥,先通知app后通知sf哪替,如下圖:
加上這個理解就能明白第五定律。關于第五點的討論菇怀,在Vsync同步信號原理有詳細討論凭舶。
小結
這五大定律是指導SF設計的核心思想晌块,從Android4.1一直到9.0都沒有太大的變化。只要抓住這五個核心思想帅霜,我們閱讀起SF的難度就會下降不少匆背。
那么SF的體系和我之前聊過的Skia有什么關系呢?又和頂層的View的繪制流程有什么關系呢身冀?
我們按照角色區(qū)分一下:
- framework面向開發(fā)者所有的View是便于開發(fā)的控件钝尸,里面僅僅只是提供了當前View各種屬性以及功能。
- 而Android底層的Skia是Android對于屏幕上的畫筆搂根,經(jīng)過View繪制流程的onDraw方法回調(diào)珍促,把需要繪制的東西通過Skia繪制成像素圖元保存起來
- SF則是最后接受Skia的繪制結果,最后繪制到屏幕上剩愧。
所以說猪叙,Skia是Android渲染核心這句話沒錯,但是最終還是需要Skia和系統(tǒng)所提供起來仁卷,才是一個Android完整渲染體系穴翩。
經(jīng)過這一層層的屏蔽,讓開發(fā)者不需要對Android底層的渲染體系有任何理解五督,也能繪制出不錯的效果藏否。
最后會把繪制結果傳輸?shù)狡聊恢小?/p>
因此,本次計劃將會從底層核心充包,慢慢向上剖析副签,直到View的繪制流程,讓我們那徹底通讀整個android的渲染體系基矮。
計劃
本次計劃SurfaceFlinger的文章將會通過如下模塊一一解析(但是不代表一個模塊就只有一篇淆储,也不代表最終順序,僅僅代表你將會閱讀到什么內(nèi)容):
-
圖元核心傳輸工具家浇,匿名共享內(nèi)存ashmem驅動的核心原理,ashmem原理圖大致如下:
-
詳見匿名內(nèi)存ashmem源碼分析本砰。然而在Android高版本,已經(jīng)放棄了ashemem钢悲,改用ion驅動点额。ion的原理圖大致如下:
關于ion的分析,詳見ion驅動源碼淺析
ion實際上是生成DMA直接訪問內(nèi)存莺琳。原本ashmem的方式需要從GPU訪問到CPU再到內(nèi)存中的地址还棱。但是在這里就變成了GPU直接訪問修改DMA,CPU也能直接修改DMA惭等。這就是最大的變化珍手。
- SurfaceFlinger的啟動。
詳見SurfaceFlinger 的初始化,原理圖大致如下:
- SurfaceFlinger的啟動。
- 開機沒有Activity琳要,只能直接使用SF機制加上OpenGL es顯示開機動畫寡具,來看看從linux開機動畫到Android開機動畫 BootAnimation 。
詳見系統(tǒng)啟動動畫稚补,原理圖大致如下:
- 開機沒有Activity琳要,只能直接使用SF機制加上OpenGL es顯示開機動畫寡具,來看看從linux開機動畫到Android開機動畫 BootAnimation 。
- 理解應用進程如何和SF構建起聯(lián)系童叠。
詳見Vsync同步信號原理。SF是通過一個名為Choreographer監(jiān)聽VSync進而得知繪制周期的孔厉。原理圖大致如下:
- 理解應用進程如何和SF構建起聯(lián)系童叠。
- SF硬件抽象層hal的理解和運作拯钻,理解SF如何和底層HWC/fb驅動關聯(lián)起來帖努。
詳見SurfaceFlinger 的HAL層初始化
其核心數(shù)據(jù)結構如下:
底層硬件回調(diào)和SF之間的關聯(lián)原理圖如下:
- SF是如何連通DisplayManagerService[略,之后有機會進行補充]撰豺,只是簡單的通過SurfaceFlinger獲取屏幕信息放在Framework層管理。
- Android端在opengl es的核心原理拼余,看看Android對opengl es上做了什么封裝污桦。
這個模塊分為兩部分解析:
一個是正常的OpenGL es使用流程中,軟件模擬每一個關鍵步驟的工作原理是什么匙监,Android在其中進行了什么優(yōu)化凡橱。詳見OpenGL es上的封裝(上)
其中有一個十分關鍵的數(shù)據(jù)結構,UML圖如下:
- Android端在opengl es的核心原理拼余,看看Android對opengl es上做了什么封裝污桦。
一個紋理在OpenGL es中是如何合成繪制的亭姥,并且Android進行了本地紋理的優(yōu)化,詳見OpenGL es上的封裝(下)稼钩,整個OpenGL es的繪制原理如下:
- 圖元是怎么通過hal層生產(chǎn)出圖元數(shù)據(jù);應用的圖元數(shù)據(jù)又是獲取到應用达罗,如何進入SurfaceFlinger的緩沖隊列坝撑。
詳見GraphicBuffer的誕生。其中涉及了幾個重要的數(shù)據(jù)結構:
- 圖元是怎么通過hal層生產(chǎn)出圖元數(shù)據(jù);應用的圖元數(shù)據(jù)又是獲取到應用达罗,如何進入SurfaceFlinger的緩沖隊列坝撑。
同時運行原理圖如下:
- 應用的圖元數(shù)據(jù)是如何消費的粮揉。
詳見圖元的消費巡李,交換緩沖繪制參數(shù),本質(zhì)是取出一個GraphicBuffer存到緩沖隊列的時間和當前時間預計顯示最接近的一個扶认,渲染到屏幕中侨拦。同時把上一幀的GraphicBuffer放到空閑隊列中。
- 應用的圖元數(shù)據(jù)是如何消費的粮揉。
其中辐宾,我們需要記住下面這個SF中緩沖隊列設計的數(shù)據(jù)結構:
- SF是如何通過HWC合成圖層狱从,如何合并各個Layer,輸出到opengles中處理叠纹。
大致上可以分為如下如下7步驟:
1.preComposition 預處理合成
2.rebuildLayerStacks 重新構建Layer棧
3.setUpHWComposer HWC的渲染或者準備
這三步驟季研,我稱為繪制準備,詳見圖元的合成(上) 繪制的準備
在繪制準備的過程中吊洼,最重要的是區(qū)分了如下幾種繪制模式训貌,已經(jīng)存儲相關的數(shù)據(jù)到HWC的Hal層中。
- SF是如何通過HWC合成圖層狱从,如何合并各個Layer,輸出到opengles中處理叠纹。
Composition的Layer的Type | hasClientComposition | hasDeviceComposition | 渲染方式 |
---|---|---|---|
HWC2::Composition::Client | true | - | OpenGL es |
HWC2::Composition::Device | - | true | HWC |
HWC2::Composition::SolidColor | - | true | HWC |
HWC2::Composition::Sideband | - | true | HWC或者OpenGL es |
4.doDebugFlashRegions 打開debug繪制模式
5.doTracing 跟蹤打印
6.doComposition 合成圖元
7.postComposition 圖元合成后的vysnc等收尾工作。
后面四個步驟递沪,我們只需要關注最后兩個步驟即可豺鼻。詳見圖元的合成(下)
整一套的從消費到合成的流程原理圖大致如下:
在合成的過程中,分為HWC和OpenGL es兩種款慨,兩者負責的角色大致如下:
當然儒飒,在Android渲染體系中,也不是只有一對生產(chǎn)者消費者模型:
- SF的Vsync原理檩奠,以及相位差計算原理
整個VSync發(fā)送中有三種發(fā)送周期:硬件發(fā)送VSync周期桩了,軟件發(fā)送VSync周期,app處理VSync周期埠戳,sf處理VSync周期井誉。
詳見Vsync同步信號原理
- SF的Vsync原理檩奠,以及相位差計算原理
Android為了方便,會暫時把整個周期看成一個周期連續(xù)性的函數(shù)整胃,計算原理如下:
其實就是獲取每一個采樣點相位颗圣,計算采樣點相位的平均值就是理想相位。同理屁使,周期也是計算采樣點的平均周期在岂,從而計算出一個合適的軟件發(fā)送VSync軸。
最后在軟件渲染的基礎上蛮寂,app的VSync和sf的VSync各自進行延時接受處理蔽午,避免出現(xiàn)定律的時序沖突,就是上面那一副藍色的圖酬蹋。
- SF的fence 同步柵工作原理
詳見fence原理
想要弄懂Fence及老,需要先了解GraphicBuffer的狀態(tài)變更:
大致分為如下幾個狀態(tài):dequeue(出隊到應用中繪制),queue(入隊到SF緩沖區(qū)等待消費)除嘹,acquire(選擇渲染的GraphicBuffer)写半,free(消費完畢后等待dequeue)
- SF的fence 同步柵工作原理
Fence的狀態(tài)更簡單,有acquire尉咕,release叠蝇,retried狀態(tài)流轉大致如下:
retried是每一次繪制完都會合并在一個不用的Fence中進行記錄。
總結一句話年缎,F(xiàn)ence的acquire狀態(tài)其實是阻塞什么時候可以被消費悔捶,什么時候可以被渲染到屏幕;而Fence的release狀態(tài)則是控制什么時候可以出隊給應用進行繪制单芜,什么時候可以被映射到內(nèi)存蜕该。
只有理解這12點,才能說你了解SF了洲鸠,也不能說精通堂淡,畢竟你沒辦法盲敲出來馋缅。
等這12點全部理解通之后,會開啟Skia新的篇章绢淀,來聊聊Skia的工作原理以及源碼解析萤悴,最后我們會回歸本源,來聊聊View的繪制流程以及WMS皆的。
這個只是一個導讀覆履,在這12個知識點背后藏著不少的東西,希望一個總綱能讓人有一個總攬费薄,不至于迷失在源碼中硝全。
后話
作為每一個經(jīng)常和UI交互的工程師,有必要也必須要熟悉Android 的渲染原理楞抡,只有這樣才能讓我們寫出更加優(yōu)秀代碼伟众,特別是做音視頻的哥們,更加有必要閱讀這些代碼以及看看工業(yè)級別的Android的VideoView是如何設計的拌倍。其實我在學習一些關于音視頻資料的時候赂鲤,用ffmpeg編寫一個視頻播放器,發(fā)現(xiàn)其實那些demo還有很多地方可以優(yōu)化的柱恤,可以學習flutter如何工作的,如何依托自身平臺做進一步優(yōu)化找爱,而不是應該去做一個泛用的梗顺,還過得去的東西。
我會隨著進度不斷修改本文车摄,本文不是最終版本寺谤,會不斷的添加不少設計示意圖以及UML圖。