android幀的繪制過程以及fps的獲取

圖形顯示過程

幀的渲染過程中一些關鍵組件的流程圖

image.png

Image Stream Producers(圖像生產者)

任何可以產生圖形信息的組件都統稱為圖像的生產者,比如OpenGL ES, Canvas 2D, 和 媒體解碼器等。

Image Stream Consumers(圖像消費者)

SurfaceFlinger是最常見的圖像消費者,Window Manager將圖形信息收集起來提供給SurfaceFlinger,SurfaceFlinger接受后經過合成再把圖形信息傳遞給顯示器。同時,SurfaceFlinger也是唯一一個能夠改變顯示器內容的服務。SurfaceFlinger使用OpenGL和Hardware Composer來生成surface.

某些OpenGL ES 應用同樣也能夠充當圖像消費者,比如相機可以直接使用相機的預覽界面圖像流,一些非GL應用也可以是消費者,比如ImageReader 類。

Window Manager

Window Manager是一個用于控制window的系統服務,包含一系列的View。每個Window都會有一個surface,Window Manager會監視window的許多信息,比如生命周期、輸入和焦點事件、屏幕方向、轉換、動畫、位置、轉換、z-order等,然后將這些信息(統稱window metadata)發送給SurfaceFlinger,這樣,SurfaceFlinger就能將window metadata合成為顯示器上的surface。

Hardware Composer

為硬件抽象層(HAL)的子系統。SurfaceFlinger可以將某些合成工作委托給Hardware Composer,從而減輕OpenGL和GPU的工作。此時,SurfaceFlinger扮演的是另一個OpenGL ES客戶端,當SurfaceFlinger將一個緩沖區或兩個緩沖區合成到第三個緩沖區時,它使用的是OpenGL ES。這種方式會比GPU更為高效。

一般應用開發都要將UI數據使用Activity這個載體去展示,典型的Activity顯示流程為:

  1. startActivity啟動Activity;
  2. 為Activity創建一個window(PhoneWindow),并在WindowManagerService中注冊這個window;
  3. 切換到前臺顯示時,WindowManagerService會要求SurfaceFlinger為這個window創建一個surface用來繪圖。SurfaceFlinger創建一個”layer”(surface)。(以想象一下C/S架構,SF對應Server,對應Layer;App對應Client,對應Surface),這個layer的核心即是一個BufferQueue,這時候app就可以在這個layer上render了;
    將所有的layer進行合成,顯示到屏幕上。

一般app而言,在任何屏幕上起碼有三個layer:

  • 屏幕頂端的status bar
  • 屏幕下面的navigation bar
  • 還有就是app的UI部分。
    一些特殊情況下,app的layer可能多余或者少于3個,例如對全屏顯示的app就沒有status bar,而對launcher,還有個為了wallpaper顯示的layer。status bar和navigation bar是由系統進行去render,因為不是普通app的組成部分嘛。而app的UI部分對應的layer當然是自己去render,所以就有了第4條中的所有layer進行“合成”。

GUI框架

gui.jpg

Hardware Composer

那么android是如何使用這兩種合成機制的呢?這里就是Hardware Composer的功勞。處理流程為:

  1. SurfaceFlinger給HWC提供layer list,詢問如何處理這些layer;
  2. HWC將每個layer標記為overlay或者GLES composition,然后回饋給SurfaceFlinger;
  3. SurfaceFlinger需要去處理那些GLES的合成,而不用去管overlay的合成,最后將overlay的layer和GLES合成后的buffer發送給HWC處理。

借用google一張圖說明,可以將上面講的很多概念展現,很清晰。地址位于 https://source.android.com/devices/graphics/

hw.png

關于幀率

即 Frame Rate,單位 fps,是指 gpu 生成幀的速率,如 33 fps,60fps,越高越好。
但是對于快速變化的游戲而言,你的FPS很難一直保持同樣的數值,他會隨著你所看到的顯示卡所要描畫的畫面的復雜程度而變化。

VSync

安卓系統中有 2 種 VSync 信號:

  1. 屏幕產生的硬件 VSync: 硬件 VSync 是一個脈沖信號,起到開關或觸發某種操作的作用。
  2. 由 SurfaceFlinger 將其轉成的軟件 Vsync 信號:經由 Binder 傳遞給 Choreographer。

單層緩沖引發“畫面撕裂”問題

single.png

如上圖,CPU/GPU 向 Buffer 中生成圖像,屏幕從 Buffer 中取圖像、刷新后顯示。這是一個典型的生產者——消費者模型。理想的情況是幀率和刷新頻率相等,每繪制一幀,屏幕顯示一幀。而實際情況是,二者之間沒有必然的大小關系,如果沒有鎖來控制同步,很容易出現問題。

所謂”撕裂”就是一種畫面分離的現象,這樣得到的畫像雖然相似但是上半部和下半部確實明顯的不同。這種情況是由于幀繪制的頻率和屏幕顯示頻率不同步導致的,比如顯示器的刷新率是75Hz,而某個游戲的FPS是100. 這就意味著顯示器每秒更新75次畫面,而顯示卡每秒更新100次,比你的顯示器快33%。

雙緩沖

double.png

兩個緩存區分別為 Back Buffer 和 Frame Buffer。GPU 向 Back Buffer 中寫數據,屏幕從 Frame Buffer 中讀數據。VSync 信號負責調度從 Back Buffer 到 Frame Buffer 的復制操作,可認為該復制操作在瞬間完成。

雙緩沖的模型下,工作流程這樣的:

  • 在某個時間點,一個屏幕刷新周期完成,進入短暫的刷新空白期。此時,VSync 信號產生,先完成復制操作,然后通知 CPU/GPU 繪制下一幀圖像。復制操作完成后屏幕開始下一個刷新周期,即將剛復制到 Frame Buffer 的數據顯示到屏幕上。

  • 在這種模型下,只有當 VSync 信號產生時,CPU/GPU 才會開始繪制。這樣,當幀率大于刷新頻率時,幀率就會被迫跟刷新頻率保持同步,從而避免“tearing”現象。

VSYNC 偏移

應用和SurfaceFlinger的渲染回路必須同步到硬件的VSYNC,在一個VSYNC事件中,顯示器將顯示第N幀,SurfaceFlinger合成第N+1幀,app合成第N+2幀。

使用VSYNC同步可以保證延遲的一致性,減少了app和SurfaceFlinger的錯誤,以及顯示在各個階段之間的偏移。然而,前提是app和SurfaceFlinger每幀時間的變化并不大。因此,從輸入到顯示的延遲至少有兩幀。
為了解決這個問題,您可以使用VSYNC偏移量來減少輸入到顯示的延遲,其方法為將app和SurfaceFlinger的合成信號與硬件的VSYNC關聯起來。因為通常app的合成耗時是小于兩幀的(33ms左右)。
VSYNC偏移信號細分為以下3種,它們都保持相同的周期和偏移向量:

  • HW_VSYNC_0:顯示器開始顯示下一幀。
  • VSYNC:app讀取輸入并生成下一幀。
  • SF VSYNC:SurfaceFlinger合成下一幀的。
    收到VSYNC偏移信號之后, SurfaceFlinger 才開始接收緩沖區的數據進行幀的合成,而app才處理輸入并渲染幀,這些操作都將在16.7ms完成。

Jank 掉幀

注意,當 VSync 信號發出時,如果 GPU/CPU 正在生產幀數據,此時不會發生復制操作。屏幕進入下一個刷新周期時,從 Frame Buffer 中取出的是“老”數據,而非正在產生的幀數據,即兩個刷新周期顯示的是同一幀數據。這是我們稱發生了“掉幀”(Dropped Frame,Skipped Frame,Jank)現象。

流暢性解決方案思路

  1. 從dumpsys SurfaceFlinger --latency中獲取127幀的數據
  2. 上面的命令返回的第一行為設備本身固有的幀耗時,單位為ns,通常在16.7ms左右
  3. 從第二行開始,分為3列,一共有127行,代表每一幀的幾個關鍵時刻,單位也為ns

第一列t1: when the app started to draw (開始繪制圖像的瞬時時間)
第二列t2: the vsync immediately preceding SF submitting the frame to the h/w (VSYNC信令將軟件SF幀傳遞給硬件HW之前的垂直同步時間),也就是對應上面所說的軟件Vsync
第三列t3: timestamp immediately after SF submitted that frame to the h/w (SF將幀傳遞給HW的瞬時時間,及完成繪制的瞬時時間)

  1. 將第i行和第i-1行t2相減,即可得到第i幀的繪制耗時,提取出每一幀不斷地dump出幀信息,計算出

一些計算規則

計算fps:

每dumpsys SurfaceFlinger一次計算匯總出一個fps,計算規則為:
frame的總數N:127行中的非0行數
繪制的時間T:設t=當前行t2 - 上一行的t2,求出所有行的和∑t
fps=N/T (要注意時間轉化為秒)

計算中一些細節問題

一次dumpsys SurfaceFlinger會輸出127幀的信息,但是這127幀可能是這個樣子:

...
0               0               0
0               0               0
0               0               0
575271438588    575276081296    575275172129
575305169681    575309795514    575309142441
580245208898    580250445565    580249372231
580279290043    580284176346    580284812908
580330468482    580334851815    580333739054 
0               0               0
0               0               0
...
575271438588    575276081296    575275172129
575305169681    575309795514    575309142441
 

  • 出現0的地方是由于buffer中沒有數據,而非0的地方為繪制幀的時刻,因此僅計算非0的部分數據

  • 觀察127行數據,會發現偶爾會出現9223372036854775808這種數字,這是由于字符溢出導致的,因此這一行數據也不能加入計算

  • 不能單純的dump一次計算出一個fps,舉個例子,如果A時刻操作了手機,停留3s后,B時刻再次操作手機,按照上面的計算方式,則t>3s,并且也會參與到fps的計算去,從而造成了fps不準確,因此,需要加入一個閥值判斷,當t大于某個值時,就計算一次fps,并且把相關數據重新初始化,這個值一般取500ms

  • 如果t<16.7ms,則直接按16.7ms算,同樣的總耗時T加上的也是16.7

計算jank的次數:

如果t3-t1>16.7ms,則認為發生一次卡頓

流暢度得分計算公式

設目標fps為target_fps,目標每幀耗時為target_ftime=1000/target_fps
從以下幾個維度衡量流暢度:

  • fps: 越接近target_fps越好,權重分配為40%
  • 掉幀數:越少越好,權重分配為40%
  • 超時幀:拆分成以下兩個維度
    • 超時幀的個數,越少越好,權重分配為5%
    • 最大超時幀的耗時,越接近target_ftime越好,權重分配為15%
end_time = round(last_frame_time / 1000000000, 2)
T = utils.get_current_time()
fps = round(frame_counts * 1000 / total_time, 2)

# 計算得分
g = fps / target
if g > 1:
  g = 1
if max_frame_time - kpi <= 1:
       max_frame_time = kpi
h = kpi / max_frame_time
 score = round((g * 50 + h * 10 + (1 - over_kpi_counts / frame_counts) * 40), 2)

參考文章:

http://windrunnerlihuan.com/2017/05/21/VSync%E4%BF%A1%E5%8F%B7/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容