說到界面卡頓,基本上就是兩個原因:CPU耗時任務、GPU渲染耗時。
優化方案基本也是從這兩個方向入手。但是為什么耗時的操作會導致丟幀?以及撕裂是怎么出現的?單緩沖、二級緩沖、三緩沖又是什么?
我們知道在整個顯示過程中,需要 CPU、GPU、顯示屏 三個模塊協調工作,大致流程如下:
- CPU 負責計算數據,把計算好數據交給 GPU
- GPU 會對圖形數據進行渲染,渲染好后放到緩沖區 buffer 里存起來
- 顯示屏 以特定的 屏幕刷新率 把 buffer 里的數據呈現到屏幕上
屏幕發光原理
我們先了解一下,幾種屏幕的發光原理,方面后面理解屏幕刷新流程:
- CRT 顯示器由很多熒光點組成,發光管是一個射線管,靠電子束高速擊打熒光粉發光(據說離的太近,會有輻射)
- LED 顯示器是靠二極管發光,一直常亮的,自身有固定的刷新率(一般是 60HZ)
屏幕刷新過程
從發光原理來看,我們知道有一個電子束(類似掃描搶)的東西存在,屏幕的刷新就從這開始了:
- 從初始位置(第一行左上角)開始掃描,從左到右,進行水平掃描(
Horizontal Scanning
) - 每一行掃描完成,掃描線會切換到下一行起點,這個切換過程叫做水平消隱,簡稱 hblank(
horizontal blank interval
),並發送水平同步信號(horizontal synchronization
,又稱行同步) - 依次類推,整個屏幕(一個垂直周期)掃描完成后,顯示器就可以呈現一幀的畫面
- 屏幕最后一行(一個垂直周期)掃描完成后,需要重返左上角初始位置,這個過程叫垂直消隱,簡稱 vblank(
vertical blank interval
) - 掃描線回到初始位置之后,准備掃描下一幀,同時發出垂直同步信號(
vertical synchronization
,又稱場同步)。
這里解釋一下幾個名詞:
# 垂直消隱
完成一幀的掃描后,掃描點會回到初始點,准備掃描下一幀,這個過程會花一點時間,會有短暫的空白期。為了避免看到一個斜線顯示在屏幕上,需要把掃描點變blank,這個過程就是垂直消隱,也叫場消隱。
# 垂直同步信號
當掃描點回到初始點,在准備掃描下一幀的時候,同時發出垂直同步信號,告訴顯卡可以渲染下一幀了。這種情況下,顯卡的渲染能力會受到 屏幕刷新率 的制約。如果顯示器刷新頻率是60Hz,顯卡幀率最多只會達到60。對於高幀率的顯卡,開啟垂直同步自然會制約其性能發揮。
# 屏幕刷新頻率
即 Refresh Rate
或 Scanning Frequency
,是指屏幕刷新的頻率,單位赫茲/Hz,一般是 60hz。也就是以這個頻率發出 垂直同步信號,告訴 GPU 可以往 buffer 里寫數據了,即渲染下一幀。
CPU GPU工作流程
我們再回來看第一部分 CPU 和 GPU,他們是如何工作的呢?
- CPU 繪制 View 樹,計算好圖形數據,提交到系統內存中
- CPU提交完成以后,通知 GPU 計算完成,系統總線會把數據拷貝到 GPU 的顯存里
- GPU 開始處理數據,以特定的 顯卡幀率 把數據寫到顯卡的緩沖區里
- 視頻控制器收到 垂直同步信號 ,逐行讀取幀緩沖區的數據,交給顯示器
CPU、GPU 的計算和交互還是挺復雜的,涉及到虛擬內存地址映射,我們暫且不深入研究了,這里我們主要看第三步:
GPU 以特定的幀率把處理結果寫到顯卡的緩沖區里
這里我們就需要了解單緩沖、雙緩沖、垂直同步信號的概念了,我們一個一個來看:
#顯卡幀率
即 Frame Rate,單位 fps,是指 gpu 生成幀的速率,如 33 fps,60fps,越高越好。
#單緩沖
單緩沖,也就是只有一個緩沖區(buffer),GPU 向 buffer 中寫入數據,屏幕從 buffer 中取圖像數據、刷新后顯示,理想的情況是 顯卡幀率 和 屏幕刷新頻率 相等,每繪制一幀,屏幕顯示一幀。而實際情況是,二者之間沒有必然的大小關系,如果沒有同步機制,很容易出現問題。
- 例如,當顯卡幀率大於屏幕刷新頻率,屏幕准備刷新第2幀的時候,GPU 已經在生成第3幀了,就會覆蓋第2幀的部分數據。
- 當屏幕開始刷新第2幀的時候,緩沖區中的數據一部分是第3幀數據,一部分是第2幀的數據,顯示出來的圖像就會出現明顯的偏差,也就是撕裂(
tearing
)。
#雙緩沖
為了單緩沖的撕裂和效率問題,雙緩沖誕生了。
雙緩沖有兩個緩沖區:frame buffer
、back buffer
,GPU 向 back buffer
中寫數據,屏幕從 frame buffer
中讀數據。這樣不僅可以提升效率,而且可以避免因為幀率和刷新率不一致,導致圖像數據錯亂。
但是這兩個 buffer
怎么去同步呢?這里就需要 垂直同步信號 了
當開啟垂直同步后,就會變成這樣:
- GPU 會等待 垂直同步信號 發出后,復制
back buffer
的數據到frame buffer
里(交換兩個緩沖區的內存地址) - 渲染下一幀數據,寫到緩沖區里
這樣看來,幀率大於刷新頻率時,幀率就會被迫跟刷新頻率保持同步,從而避免撕裂現象。
需要注意的是,雙緩沖 + 垂直同步信號仍然不能完全保證正常顯示,比如說:
- 收到垂直同步信號時,如果 GPU 正在往緩沖區里寫數據,CPU、GPU 繪制一幀的時間超過16ms,也就是一個 屏幕刷新周期 還沒有准備完,這時候兩個緩沖區不會發生復制。
- 當屏幕進入下一個刷新周期時,從
frame buffer
中取出的是上一幀數據,即兩個刷新周期顯示的是同一幀數據,也就是掉幀(Jank
)。
為此,引入了 三緩沖,但是仍然避免不了卡頓和延遲的現象,這里就不詳細介紹了,可以自行查閱相關資料。
參考鏈接
iOS 保持界面流暢的技巧(ibireme)
理解 VSync
Android 屏幕刷新顯示機制
https://laoqingcai.com/ios-screen-refresh/