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

圖形顯示過程

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

image.png

Image Stream Producers(圖像生產(chǎn)者)

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

Image Stream Consumers(圖像消費者)

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

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

Window Manager

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

Hardware Composer

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

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

  1. startActivity啟動Activity;
  2. 為Activity創(chuàng)建一個window(PhoneWindow),并在WindowManagerService中注冊這個window;
  3. 切換到前臺顯示時,WindowManagerService會要求SurfaceFlinger為這個window創(chuàng)建一個surface用來繪圖。SurfaceFlinger創(chuàng)建一個”layer”(surface)。(以想象一下C/S架構(gòu),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是由系統(tǒng)進行去render,因為不是普通app的組成部分嘛。而app的UI部分對應的layer當然是自己去render,所以就有了第4條中的所有l(wèi)ayer進行“合成”。

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發(fā)送給HWC處理。

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

hw.png

關于幀率

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

VSync

安卓系統(tǒng)中有 2 種 VSync 信號:

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

單層緩沖引發(fā)“畫面撕裂”問題

single.png

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

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

雙緩沖

double.png

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

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

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

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

VSYNC 偏移

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

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

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

Jank 掉幀

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

流暢性解決方案思路

  1. 從dumpsys SurfaceFlinger --latency中獲取127幀的數(shù)據(jù)
  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出幀信息,計算出

一些計算規(guī)則

計算fps:

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

計算中一些細節(jié)問題

一次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
 

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

  • 觀察127行數(shù)據(jù),會發(fā)現(xiàn)偶爾會出現(xiàn)9223372036854775808這種數(shù)字,這是由于字符溢出導致的,因此這一行數(shù)據(jù)也不能加入計算

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

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

計算jank的次數(shù):

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

流暢度得分計算公式

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

  • fps: 越接近target_fps越好,權重分配為40%
  • 掉幀數(shù):越少越好,權重分配為40%
  • 超時幀:拆分成以下兩個維度
    • 超時幀的個數(shù),越少越好,權重分配為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/

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容