又卡了~從王者榮耀看Android屏幕刷新機制
- 2021 年 6 月 2 日
- 筆記
前言
正在帶妹子上分的我,團戰又卡了,我該怎麼向妹子解釋?在線等。
「卡」的意思
不管是端游還是手游,我們都會時不時遇到「卡」的時候,一般這個卡有兩種含義:
- 掉幀
- 畫面撕裂
那麼問題來了,這些情況到底是什麼原因導致的?又該怎麼解決?
掉幀
首先,要知道幀
是什麼,幀率
又是什麼。
幀,就是影像動畫中最小單位的單幅影像畫面,相當於電影膠片上的每一格鏡頭。 一幀就是一幅靜止的畫面,連續的幀就形成動畫,如電視圖象等。
幀率(每秒幀數),簡單地說,就是在1秒鐘時間裏傳輸的圖片的幀數,也可以理解為圖形處理器每秒鐘能夠刷新幾次,通常用fps(Frames Per Second)表示
這下大家應該知道了,幀就是一個靜止畫面,很多個幀一起就組成了視頻、電影、遊戲畫面。
而幀率就是我們遊戲常見到的fps
,指一秒鐘繪製出現的幀數,單位為「赫茲」(Hz)
。
這裡簡單科普下,一般要求連貫性的話,幀數至少要高於每秒約10至12幀的時候,人眼才會認為是連貫的,此現象叫做「視覺暫留現象」
,是由人眼的生物構造決定的。通過這個現象,早期的無聲電影通過手搖驅動,將畫面快速播放,就能讓人感覺在播放完整連續的視頻。
按照我們的認知,這個幀率一般是越大越連貫,就越不卡。但同時,帶來的消耗也就越多,比如電影需要更多的膠捲,電腦需要更好的硬件支持。所以電影一般通用的幀率為24Hz,而電腦、手機一般幀率為60Hz,這樣就能保證正常條件下能讓人舒服得觀看和使用。
那掉幀
的意思就很明顯了,本來遊戲的fps為60,突然降到了20,也就是一秒只有20幀了,那能不卡嗎?
那麼,掉幀原因到底是啥呢?
其實原因大家都知道,不信你繼續看…
硬件原因
「我這個手機玩遊戲卡死了」
「你那啥破手機啊,趕快換一個~」
這個對話應該時常發生,所以大家也都清楚,硬件的好壞
一定程度上決定了玩遊戲「卡不卡」
,配置高的硬件玩遊戲就能保證遊戲的流暢。
軟件原因
「你這啥App啊,做的啥遊戲啊,這麼卡,我這手機配置這麼高,就玩你這個卡」
「額,可能是遊戲優化沒做好,」
第二個原因,就是因為遊戲/軟件
自身的優化就沒做好,圖片弄的很大,布局嵌套太深,那麼幀 的計算和渲染就更耗時間,就會有可能導致掉幀。
網絡原因
「不行了,太卡了,我這ping都快1000了,怎麼玩啊」
「快換流量啊,團戰要輸了,少個人怎麼打」
對了,第三個原因就是網絡原因
,這也是最常發生的原因了,網絡的波動會影響 畫面 的傳輸,所以就會掉幀。
屏幕刷新機制
上述三個原因,其實都涉及到屏幕刷新
的基本機制。
在典型的顯示系統中,不管是手機還是電腦,一般都涉及到三個部分:
CPU
,中央處理器。用於計算數據,信息處理。GPU
,圖形處理器。用於處理圖像圖形,也就是俗稱的顯卡。display
,顯示屏幕。用於展示畫面,也就是我們的手機屏幕、電腦顯示器。
整個顯示過程就是:
CPU
計算屏幕需要的數據,然後交給GPU。GPU
對圖像進行處理繪製,然後存到緩存區。display
再從這個緩存區讀取數據,顯示出來。
每一幀都是重複這個工作,也就是1秒中需要60次這樣循環操作,每次操作需要的時間就約等於16.6ms
。也就是我們常說的Android系統中,會每隔16.6ms刷新一次屏幕。
關於屏幕刷新機制,有一張很經典的圖片:
可以看到,16.6ms一到,系統就發送了VSync
信號,然後屏幕會從緩存區獲取了新的一幀圖像並顯示出來,與此同時,CPU也開始了下一幀數據的計算,然後計算好交給GPU,最後放到緩存區,等待下一次VSync
信號。
VSync
信號是啥呢?我們暫且按下不表,待會再說,可以先理解它為一種同步刷新信號
,同步CPU和屏幕。當信號來的時候,屏幕開始切換畫面,CPU開始下一幀計算。
為了方便理解,我做了個小動畫:
通過上面的解釋,我們知道了一幀顯示的時間是16.6ms
,在這個時間內,CPU和GPU必須把數據處理好並放到緩存區(buffer)
中。
如果在某次的16.6ms中,CPU和GPU沒有繪製好下一幀數據,那麼display就無法更新下一幀數據了,這就是掉幀
。
所以才有了以上三個原因會導致掉幀,再來回顧下:
1、硬件原因
。硬件比較差也就是CPU和GPU計算不給力,導致一幀的數據沒準備好,所以掉幀。2、軟件原因
。在硬件夠用的情況下,App或者遊戲的一幀數據計算繁雜,嵌套太多或者圖太大,也會導致下一幀數據不能在16.6ms中被準備好,導致掉幀。3、網絡原因
。在硬件軟件都正常情況下,由於網絡波動,CPU的計算數據都沒有從網絡上獲取到,那麼肯定會導致CPU數據的準備延遲,最終導致掉幀。
那麼掉幀之後,屏幕刷新機制會怎麼處理後續的幀呢?
- 如果是
遊戲
的話,因為即時性比較重要,所以丟失的幀就不會再去管了,而是直接準備當前時間應該顯示的內容,最終顯示到屏幕。所以這種情況掉的幀就真的掉了。 - 如果是
應用
的話,可能對數據的完整性比較重要,所以如果第2幀掉了,那麼屏幕就繼續顯示第1幀,而CPU和GPU繼續準備第2幀的數據,如果能在下一個16.6ms內完成第2幀數據,那麼屏幕就會接着顯示第二幀了。比如有時候手機卡的時候,我們去操作App,操作會延遲,就是掉幀了。這種情況幀並不是真的掉了,而是延遲了。
畫面撕裂
接下來就看看畫面撕裂,為什麼一幀中會出現兩幀的畫面呢?
首先理解一個概念:「逐行掃描」
「逐行掃描」就是說,顯示器顯示畫面並不是「蹭」一下就打出一張畫面來,而是從上到下一行一行顯示出來的,只不過是顯示得比較快所以肉眼看不出來而已。
之前說了屏幕的數據是從緩存區Buffer
中取的,如果在屏幕取數據並逐行掃描顯示畫面的過程中,Buffer
中的數據變了,那麼就有可能導致畫面撕裂。
最明顯的例子就是:顯卡的fps
是180,而顯示器的fps
是60。也就是顯卡一秒鐘能產生180
張畫面,而顯示器一秒鐘只能讀取60
張畫面。
那麼顯示器從Buffer中讀取數據逐行掃描的過程中,本來需要1/60 秒顯示完一張畫面,但是在1/180的時間點,顯卡就把下一張畫面的數據存到Buffer了,結果顯示器的下半截就顯示的是第二張畫面的內容了。也就造成了畫面撕裂。
再來個動畫解釋下:
所以為了防止這種狀況,一般顯示系統會加入一個雙緩存+垂直同步
的概念:
- 首先,開啟
垂直同步
,就會將GPU的fps限制為和顯示器的fps一樣。
系統會在顯示器繪製完一幀之後發送一個垂直同步信號,然後CPU和GPU就準備下一幀的內容,等待顯示器下一幀繪製完,又會發送一個垂直同步信號。如此反覆,就限制了顯卡的fps,按照顯示器的標準來繪製圖像。
這個垂直同步信號就叫做 VSync信號
。
玩遊戲的朋友應該都知道,很多遊戲內設置頁都有 垂直同步
的開啟選項,為的就是將顯卡的fps和顯示器的fps適配,防止畫面撕裂。
- 其次,通過
雙緩存
保證一幀數據的連貫性。
1、緩存區backBuffer
用於CPU/GPU圖形處理
2、緩存區frameBuffer
用於顯示器顯示
這樣分工明確之後,屏幕只會讀取framebuffer
的內容,是一幀完整的畫面。而CPU/GPU計算的新一幀內容會放到backbuffer中,不會影響到framebuffer
的內容。
只有當屏幕繪製完一幀內容之後,才會將CPU/GPU計算好的新一幀內容也就是backbuffer
內容和framebuffer
進行交換。
這樣就保證了一幀數據的完整連貫。
這裡的交換其實就是交換內存地址,兩塊緩存區A和B,A在第一次充當framebuffer
的角色,B充當backbuffer
的角色。屏幕完成一幀繪製之後,將AB內存地址置換。
當新的一輪VSync信號來的時候,A就充當了backbuffer
的角色,而B就變成了framebuffer
的角色。
小結:當屏幕掃描完第1幀畫面之後,系統發送VSync信號
,這時會發生三件事:
- 1、交換兩個緩存區(framebuffer、backbuffer)內容。
- 2、顯示器開始顯示第2幀內容,也就是交換後的framebuffer內容。
- 3、CPU/GPU開始計算處理第三幀的內容,並在處理好內容後放到backbuffer中。
再來個動畫:
Android Project Butter(黃油計劃)
問題都解決了嗎?並沒有。
加入VSync信號之後,掉幀問題變得更嚴重了:
可以發現,加入了VSync
信號後,雖然統一了CPU處理的時間點,但是掉幀問題可能會被再一次放大,從掉一幀直接變成掉兩幀。因為中間的16.6ms
被浪費了。
怎麼辦呢?在保留VSync信號的同時有可能最大利用上CPU/GPU嗎?
三緩存來了:
1、緩存區backBuffer
用於CPU/GPU圖形處理
2、緩存區TripleBuffer
用於CPU/GPU圖形處理
3、緩存區frameBuffer
用於顯示器顯示
剛才說的情況導致的原因就是因為在第二個VSync信號來的時候,因為backBuffer
被GPU佔用,所以CPU無法去開始新一幀的計算。
加入了第三個緩存區,那麼在第二個VSync信號來的時候,CPU就可以利用TripleBuffer
開始新一幀的計算,而無視正在被GPU佔用的backBuffer
。
你可以理解為 CPU、GPU、Display
每個人都有一個緩存區,這樣三個就能同時做自己的事而互不影響,最大化利用每個模塊。
三緩存和上面說到的Vsync同步信號都是 Android 4.1
發佈的一個Project Butter(黃油計劃)
中提出的,為的是就是讓Android能讓黃油/奶油般順滑。
最後貼個三緩存機制下的刷新機製圖:
小結
今天了解了Android系統的刷新機制,雖然沒有代碼,但是面試中也是常常被問到的,再次總結下:
1、為了解決畫面撕裂
問題,引入了垂直同步信號VSync信號
和雙緩存
。
- 每次VSync信號到達的時候,屏幕進行畫面切換,
CPU/GPU
開始準備下一幀內容。 CPU/GPU
每次準備好數據後,放到一個單獨的緩存區backBuffer,當屏幕準備好之後,將backBuffer
數據和frameBuffer
數據交換,屏幕只讀取frameBuffer
緩存區的數據,保證了數據的完整連續性。
2、為了解決VSync信號下CPU/GPU
無法最大化利用的問題,引入了三緩存。
當VSync
信號來的時候,即使GPU還沒處理好上一幀數據,backBuffer
還不空閑,但是CPU也可以利用第三個緩存區正常開始處理下一幀的數據,最大化利用CPU/GPU
,保證垂直同步機制的同時不浪費資源。
3、掉幀的根本原因是因為在一幀時間內(一般為16.6ms),CPU/GPU無法把下一幀的數據準備好。
即使引用了三緩存和垂直同步
,但是掉幀的情況該發生還是會發生,我們作為App軟件開發者,能做的就是儘可能優化布局,減少嵌套,減少CPU/GPU
計算畫面數據的時間,讓每一幀時間內正常準備好下一幀圖像數據。
至於刷新機制在Android
源碼中到底是怎麼實現的呢?這就涉及到Choreographer
類的解析。
參考
屏幕刷新機制
為什麼【垂直同步】技術往往不被玩家推崇
Android Project Butter分析
FrameBuffer初探
拜拜
感謝大家的閱讀,有一起學習的小夥伴可以關注下我的公眾號——碼上積木❤️❤️
每日一個知識點,積少成多,建立知識體系架構。
這裡有一群很好的Android小夥伴,歡迎大家加入~