Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android顯示機制

android顯示機制

編輯:關於Android編程

前言

本文是通過閱讀各種文章及代碼,總結出來的,其中難免有些地方理解得不對,歡迎大家批評指正。

顯示系統基礎知識

定義

在一個典型的顯示系統中,一般包括CPU、GPU、display三個部分, CPU負責計算數據,把計算好數據交給GPU,GPU會對圖形數據進行渲染,渲染好後放到buffer裡存起來,然後display(有的文章也叫屏幕或者顯示器)負責把buffer裡的數據呈現到屏幕上。很多時候,我們可以把CPU、GPU放在一起說,那麼就是包括2部分,CPU/GPU 和display(本文主要按後面這種分類來解釋)。

tearing: 一個屏幕內的數據來自2個不同的幀,畫面會出現撕裂感
jank: 一個幀在屏幕上連續出現2次
lag:從用戶體驗來說,就是點擊下去到呈現效果之間存在延遲
屏幕刷新頻率: 一秒內屏幕刷新多少次,一般為固定值

screen tearing

顯示過程,簡單的說就是CPU/GPU准備好數據,存入buffer,display去buffer裡取數據,然後顯示出來。如果只有一個buffer,那麼這個buffer既被GPU寫,也同時被display讀,如果讀的速度跟寫的速度一樣,那可以正常顯示。如果讀的比寫的快(顯示器刷新頻率略快於CPU/GPU准備緩存的速度),那也沒什麼問題。但是如果讀的比寫的慢的話,很可能有buffer裡的數據沒有被讀取,就被重寫了,這樣相當於一部分數據丟失了,這是不允許的。比如display在讀幀1的過程中(為了顯示幀1),CPU/GPU把幀2寫到了buffer裡 ,而display並不知道此時buffer裡已經是幀2了,那麼就會出現display讀的上半部分是幀1,下半部分是幀2的, 出現畫面“割裂”,這就叫tearing。

我覺得在buffer和顯示到屏幕上之間還存在一個display buffer.顯示的過程是CPU/GPU寫數據進buffer(耗時),display讀buffer(耗時)到display buffer,display讀完一個buffer之後將display buffer迅速顯示到屏幕上。

我看有篇文章用下面的圖解釋tearing,我表示不贊同,屏幕0.015秒刷一次,那我們怎麼看得到0.02秒時刻的東西,我們看到的東西應該是0.015秒的和0.03秒的,所以說0.02秒出現tearing是不對的。

\

老外還有副圖解釋tearing,下邊的panel就是display<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="" src="/uploadfile/Collfiles/20161019/201610190922381068.jpg" title="\" />

簡單的說就是display在顯示的過程中,buffer內數據被CPU/GPU修改,導致tearing
實際效果舉例如下。也有人說CPU/GPU的頻率高於display才會有tearing,這也是錯誤的。CPU/GPU的頻率低於display也可能產生tearing的,比如display為100Hz,CPU/GPU為80Hz,那麼在0.01時候顯示buffer裡的第一屏數據,底部會有留白,因為buffer沒填滿。而第二秒的時候,此時CPU/GPU,處理了1.6個buffer,所以此時buffer內的前60%是第二幀,後40%是第一幀,此時dislay來讀數據,那也是會tearing的。
只有CPU/GPU的頻率是display刷新頻率的整數倍或者1/N時才不會產生tearing。
比如60Hz的刷新頻率,那CPU/GPU的頻率得是60, 120, 30才可以.
但是但是,實際上display的刷新頻率是固定的,但是CPU/GPU寫buffer的時間是不定的,所以tearing的產生幾乎是必然的。

double-buffer

tearing發生的原因是display讀buffer時,buffer被修改,那麼多一個buffer是不是能解決問題,是的,事實上目前所有的顯示系統都是雙緩存的,單緩存存在於30年前。

雙緩沖技術,基本原理就是采用兩塊buffer。一塊back buffer用於CPU/GPU後台繪制,另一塊framebuffer則用於顯示,當back buffer准備就緒後,它們才進行交換。不可否認,doublebuffering可以在很大程度上降低screen tearing錯誤,但是它是萬能的嗎?

一個需要考慮的問題是我們什麼時候進行兩個緩沖區的交換呢?假如是back buffer准備完成一幀數據以後就進行,那麼如果此時屏幕還沒有完整顯示上一幀內容的話,肯定是會出問題的。看來只能是等到屏幕處理完一幀數據後,才可以執行這一操作了。

我們知道,一個典型的顯示器有兩個重要特性,行頻和場頻。行頻(Horizontal ScanningFrequency)又稱為“水平掃描頻率”,是屏幕每秒鐘從左至右掃描的次數; 場頻(Vertical Scanning Frequency)也稱為“垂直掃描頻率”,是每秒鐘整個屏幕刷新的次數。由此也可以得出它們的關系:行頻=場頻*縱坐標分辨率。

當掃描完一個屏幕後,設備需要重新回到第一行以進入下一次的循環,此時有一段時間空隙,稱為VerticalBlanking Interval(VBI)。大家應該能想到了,這個時間點就是我們進行緩沖區交換的最佳時間。因為此時屏幕沒有在刷新,也就避免了交換過程中出現 screentearing的狀況。VSync(垂直同步)是VerticalSynchronization的簡寫,它利用VBI時期出現的vertical sync pulse來保證雙緩沖在最佳時間點才進行交換。

所以說V-sync這個概念並不是Google首創的,它在早些年前的PC機領域就已經出現了。不過Android 4.1給它賦予了新的功用,稍後就可以看到。

android

為了優化顯示性能,android 4.1版本對Android Display系統進行了重構,實現了Project Butter,引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer。

CPU/GPU根據VSYNC信號同步處理數據

Project Butter規定系統一旦收到vsync通知(16ms觸發一次),CPU和GPU就立刻開始工作把顯示數據寫入buffer。在這之前,CPU和GPU的寫buffer時機是比較隨意的,這麼做有什麼好處呢?

看下圖,這是4.1之前android繪制圖形的一個case,使用了雙緩沖
\
以時間的順序來看下將會發生的異常:
Step1. Display顯示第0幀數據,此時CPU和GPU渲染第1幀畫面,而且趕在Display顯示下一幀前完成
Step2. 因為渲染及時,Display在第0幀顯示完成後,也就是第1個VSync後,正常顯示第1幀
Step3. 由於某些原因,比如CPU資源被占用,系統沒有及時地開始處理第2幀,直到第2個VSync快來前才開始處理
Step4. 第2個VSync來時,由於第2幀數據還沒有准備就緒,顯示的還是第1幀。這種情況被Android開發組命名為“Jank”。
Step5. 當第2幀數據准備完成後,它並不會馬上被顯示,而是要等待下一個VSync。
所以總的來說,就是屏幕平白無故地多顯示了一次第1幀。原因大家應該都看到了,就是CPU沒有及時地開始著手處理第2幀的渲染工作,以致“延誤軍機”。 Android在4.1之前一直存在這個問題。
現在加入了這個規則,旦收到vsync通知(16ms觸發一次),CPU和GPU就立刻開始工作把顯示數據寫入buffer。效果如下所示。


CPU/GPU根據VSYNC信號同步處理數據,可以讓CPU/GPU有完整的16ms時間來處理數據,減少了jank。假如CPU/GPU的FPS(FramesPer Second)高於這個值,那麼這個方案是完美的,顯示效果將很好。
一句話總結,vync同步使得CPU/GPU充分利用了16ms時間,減少jank

Triple Buffer

在雙緩存的基礎上,android又引入了三緩存技術,這是拿來干嘛的呢?
舉個例子,如果界面比較復雜,CPU/GPU的處理時間較長,會出現如下情況

\
當CPU/GPU的處理時間超過16ms時,第一個VSync到來時,緩沖區B中的數據還沒有准備好,於是只能繼續顯示之前A緩沖區中的內容。而B完成後,又因為缺乏VSync pulse信號,它只能等待下一個signal的來臨。於是在這一過程中,有一大段時間是被浪費的。當下一個VSync出現時,CPU/GPU馬上執行操作,此時它可操作的buffer是A,相應的顯示屏對應的就是B。這時看起來就是正常的。只不過由於執行時間仍然超過16ms,導致下一次應該執行的緩沖區交換又被推遲了——如此循環反復,便出現了越來越多的“Jank”。
那麼有沒有規避的辦法呢?
很顯然,第一次的Jank看起來是沒有辦法的,除非升級硬件配置來加快FPS。我們關注的重點是被CPU/GPU浪費的時間段,怎麼才能充分利用起來呢?分析上述的過程,造成CPU/GPU無事可做的假象是因為當前已經沒有可用的buffer了。換句話說,如果增加一個buffer,情況會不會好轉呢?

我們來逐步分析下這個是否有效。首先和預料中的一致,第一次“Jank”無可厚非。不過讓人欣慰的是,當第一次VSync發生後,CPU不用再等待了,它會使用第三個buffer C來進行下一幀數據的准備工作。雖然對緩沖區C的處理所需時間同樣超過了16ms,但這並不影響顯示屏——第2次VSync到來後,它選擇buffer B進行顯示;而第3次VSync時,它會接著采用C,而不是像double buffering中所看到的情況一樣只能再顯示一遍B了。這樣子就有效地降低了jank。但是帶來了lag的問題,如上圖所示,C這一幀在第4個16ms才顯示,

三緩沖作用:
簡單的說在2個緩存區被GPU和display占據的時候,開辟一個緩沖區給CPU用,一般來說都是用雙緩沖,需要的時候會開啟3緩沖,三緩沖的好處就是使得動畫更為流程,但是會導致lag,從用戶體驗來說,就是點擊下去到呈現效果會有延遲。所以默認不開三緩沖,只有在需要的時候自動開啟
一句話總結三緩沖有效利用了等待vysnc的時間,減少了jank,但是帶來了lag

vsync請求與接收

vsync請求

vsync信號如何產生的可以參考http://shangjin615.iteye.com/blog/1775684,SurfaceFlinger進程收到Vsync,轉發到有畫圖請求的客戶App,所以說對一個app來說,onVsync並不是16ms來一次的,得有需求才會來。如果一個應用沒有請求VSyn事件,surfaceflinger不會給這個應用發VSync事件,那麼應用的畫圖代碼永遠也不會調用。所以應用必須向surfaceflinger請求VSync。

應用如何請求vsync呢?
當應用需要同步信號的時候,最終會調用到scheduleVsync函數,裡面調用到了nativeScheduleVsync函數。

舉個例子,invalidate或requestLayout會調用scheduleTraversals,scheduleTraversals -》scheduleVsync

過程是 scheduleTraversals調用 mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
然後調用postCallbackDelayedInternal-》scheduleFrameLocked-》scheduleVsyncLocked-》mDisplayEventReceiver.scheduleVsync();

vsync接收

SurfaceFlinger進程收到vsync信號後,轉發給請求過的應用,應用的socket接收到vsync,調用DisplayEventReceiver#dispatchVsync-》FrameDisplayEventReceiver#onVsync-》run-》Choreographer#doFrame-》doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos)-》
這裡各種time,日了狗了

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved