屏幕顯示系統基礎概念
幀--->視頻由連續的靜態圖像連續快速切換播放形成,每一張靜態圖像稱為視頻的一幀。
利用人眼的“視覺暫留現象”只要連續圖像播放大于24幀/秒,即感受到的就是連續的動態圖像。
幀率(fps) ---> 每秒屏幕播放的幀數,幀率低于24人眼會感覺卡頓不流暢,越高越流暢
顯示器垂直分辨率:顯示器垂直方向上可顯示的像素數目
顯示器水平分辨率:顯示器水平方向上可顯示的像素數目
屏幕刷新率(HZ)-->又稱為場頻(垂直掃描頻率)
最初的CRT(陰極射線管)技術時代的屏幕,屏幕刷新像素都是從左上角開始從左到右,從上到下逐行掃描;所以完成一次垂直掃描即可完成整屏幕的刷新故而場頻也稱屏幕刷新率.
行頻--->水平掃描率,每秒在屏幕上掃描的行數
行頻 = 場頻 * 垂直分辨率 * K(一個常數)
為什么會有一個常數?答案也很簡單,由于每次掃描到最后一個像素后,下次又要重定位到左上角,存在一個性能損耗,所以“行頻”會比“場頻*垂直分辨率”大,即K一般大于1。
典型現代屏幕顯示系統角色分工:
構建視圖內容: 遍歷所有視圖,將需要繪制的操作緩存下來,交給單獨的Render線程使用GPU進行硬件加速渲染。(通常在主線程中使用CPU構建)
繪制階段 : 調用OpenGL(即使用GPU)對構建好的視圖進行繪制渲染,繪制的內容保存在Graphic Buffer 并交由 SurfaceFlinger 顯示。(Android 5.0+ 使用Render Thread線程,專門負責 UI 渲染和動畫顯示。)
顯示“卡頓”類型
Tearing --> "圖像撕裂",屏幕呈現的圖像由兩幀以上構成,畫面出現撕裂感(當前幀還未完全由屏幕更新到屏幕,即被下一幀的數據替換)
Jank --> 同一個幀在屏幕上連續出現2次即通常說的卡頓
Lag --> 屏幕延時,從用戶輸入到屏幕呈現效果之間存在延遲
Screen Tearing原因
屏幕顯示圖像本質是一個粗略的生產-消費者模型:CPU&GPU生產圖像數據到幀Buffer,顯卡從buffer獲取數據顯示到屏幕。
CPU & GPU生產幀數據時間是不定的,而屏幕刷新率基本由硬件(顯卡)決定通常60Hz。顯示控制器從Buffer獲取幀數據更新到屏幕有一個時間間隔(CRT技術的屏幕都是從屏幕左上從左到右,從上到下依次刷新屏幕像素)。
在屏幕的一個刷新周期內,buffer數據被cpu/gpu更改,就出現了圖像前部分是A幀,后半部分是B幀的“撕裂Tearing”.
由于幀buffer是一個競爭性資源,CPU/GPU 與 屏幕display會爭搶buffer,不同步就會導致Tearing.
顯示出現tearing的本質原因是,屏幕刷新率與CPU/GPU生成圖像的幀率不同步, 加入同步lock即可解決;每次屏幕或者GPU只能有一方在操作buffer.
雙緩沖+VSync同步機制
單buffer+同步雖然能解決tearing,但是效率太低,GPU和屏幕競爭同一塊buffer大部分時間都在互相等待中浪費了資源。
于是雙buffer機制適時出現(事實上單bufer機制只存在于30年前了):
backBuffer用于CPU/GPU后臺繪制,frameBuffer則用于顯示,當backBuffer準備就緒后,它們才進行交換。
雙緩沖解決了效率問題,但是如何解決同步問題---> 何時交換backBuffer和frameBuffer?
當掃描完一個屏幕后,設備需要重新回到第一行以進入下一次的循環,此時有一段時間空隙,稱為VerticalBlanking Interval(VBI)。這個時間點就是我們進行緩沖區交換的最佳時間,因為此時屏幕沒有在刷新,也就避免了交換過程中出現 screentearing的狀況。VSync(垂直同步)是VerticalSynchronization的簡寫,它利用VBI時期出現的vertical sync pulse來保證雙緩沖在最佳時間點才進行交換。
雙緩沖+Vsync工作模型:
在這種模型下,只有當 VSync 信號產生時,交換back & frame buffer,CPU/GPU 才會開始繪制。當幀率大于刷新頻率時,幀率就會被迫跟刷新頻率保持同步,從而避免“tearing”現象。開啟VSync的本質就是強制拉平我們的GPU每秒繪制的幀數和屏幕的刷新頻率。
Google在Android的實踐
為改善Android上的UI體驗, Google在2012年的I/O大會上宣布了Project Butter,并在Android 4.1中正式搭載了實現機制。
從Android4.1版本開始,Android對顯示系統進行了重構,引入了三個核心元素:VSYNC, Tripple Buffer和Choreographer。VSYNC是Vertical Synchronized的縮寫,是一種定時中斷;Tripple Buffer是顯示數據的緩沖區;Choreographer起調度作用,將繪制工作統一到VSYNC的某個時間點上,使應用的繪制工作有序進行。
那么Triple buffer是干啥的?
看下上面的方案,在CPU/GUP幀率大于屏幕刷新的情況下皆大歡喜,萬一幀率低于屏幕刷新率呢?使用A、B代表兩塊buffer看下圖不同情況下的工作情況:
若以60fps為屏幕刷新率,當CPU/GPU的處理時間超過16ms時,第一個VSync到來時,緩沖區B中的數據還沒有準備好,于是只能繼續顯示之前A緩沖區中的內容。而B完成后,又因為缺乏VSync pulse信號,它只能等待下一個signal的來臨。于是在這一過程中,有一大段時間是被浪費的。當下一個VSync出現時,CPU/GPU馬上執行操作,此時它可操作的buffer是A,相應的顯示屏對應的就是B。這時看起來就是正常的。只不過由于執行時間仍然超過16ms,導致下一次應該執行的緩沖區交換又被推遲了——如此循環反復,便出現了越來越多的“Jank”。
Triple Buffering是MultipleBuffering的一種,指的是系統使用3個緩沖區用于顯示工作。當第一次VSync發生后,CPU不用再等待了,它會使用第三個buffer C來進行下一幀數據的準備工作,有效地降低了系統顯示錯誤的機率。
那是Buffer的數量越多越好?那肯定是不科學的,triple buffer是為了充分發揮CPU/GPU的工作效率,降低等待是充分利用流水線思路;各項任務的處理能力上限觸頂使用多的buffer也只是增加額外的buffer管理調度任務。
Choreographer的作用
上面提到了硬件會產生Vsync信號作為觸發標志,如果幀率是60ms那如何統一16.6ms內的任務等待Vsync到來才執行呢?
Choreographer就是用來統一協調任務的,動畫、繪制和輸入事件的任務會先進入Choreographer的隊列;由Choreographer申請硬件的Vsync信號,收到Vsync信號后統一回調任務隊列內的任務。