1. 程式人生 > >Android 4.4 Graphic系統詳解(2) VSYNC的生成

Android 4.4 Graphic系統詳解(2) VSYNC的生成

VSYNC 的概念

VSYNC(Vertical Synchronization)是一個相當古老的概念,對於遊戲玩家,它有一個更加大名鼎鼎的中文名字—-垂直同步。
“垂直同步(vsync)”指的是顯示卡的輸出幀數和螢幕的垂直重新整理率相同,這完全是一個CRT顯示器上的概念。其實無論是VSYNC還是垂直同步這個名字,因為LCD根本就沒有垂直掃描的這種東西,因此這個名字本身已經沒有意義。但是基於歷史的原因,這個名稱在圖形影象領域被沿襲下來。
在當下,垂直同步的含義我們可以理解為,使得顯示卡生成幀的速度和螢幕重新整理的速度的保持一致。舉例來說,如果螢幕的重新整理率為60Hz,那麼生成幀的速度就應該被固定在1/60 s。

Android中的VSYNC — 黃油計劃

從Android 4.1開始,谷歌致力於解決Android系統中最飽受詬病的一個問題,滑動不如iOS流暢。因谷歌在4.1版本引入了一個重大的改進—Project Butter,也即是黃油計劃。
Project Butter對Android Display系統進行了重構,引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer。關於後面兩個概念我們會在後面開專題講解,這裡我們重點講解VSYNC的作用。
玩過大型PC遊戲的玩家都知道,VSYNC最重要的作用是防止出現畫面撕裂(screentearing)。所謂畫面撕裂,就是指一個畫面上出現了兩幀畫面的內容,如下圖。
此處輸入圖片的描述

為什麼會出現這種情況呢?這種情況一般是因為顯示卡輸出幀的速度高於顯示器的重新整理速度,導致顯示器並不能及時處理輸出的幀,而最終出現了多個幀的畫面都留在了顯示器上的問題。這也就是我們所說的畫面撕裂。

提到垂直同步這裡就多提一句,其實我認為對於PC上的大型遊戲來說,只有配置足夠高,高到顯示卡輸出幀率可以穩定的高於顯示器的重新整理頻率,才有開啟垂直同步的必要。因為只有這個時候,畫面撕裂才會真正成為一個問題。而對於很多情況下主機效能不足導致遊戲輸出幀率低於顯示器的重新整理頻率的情況下,尤其是幀率穩定在40~60之間時,開啟垂直同步可能會導致幀率倍數級的下降(具體原因我們在Graphic架構一文中提到過,當幀生成速度不及VSync速度時,幀率的下降不是平緩的,而且很可能是倍數級的。當然這在android系統上並非嚴重問題,因為android上很少有高速的複雜場景的頻繁切換。事實上,在Android的普通應用場景下,VSync的使用不僅不會降低幀率,還可以有效解決卡頓問題)。

回到正文中來,那麼VSync除了可以解決畫面的撕裂的問題,還可以解決別的什麼問題嗎?我們來看下圖:
此處輸入圖片的描述

這個圖中有三個元素,Display是顯示螢幕,GPU和CPU負責渲染幀資料,每個幀以方框表示,並以數字進行編號,如0、1、2等等。VSync用於指導雙緩衝區的交換。
以時間的順序來看下將會發生的異常:
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幀的渲染工作,以致“延誤軍機”。

其實總結上面的這個情況之所以發生,首先的原因就在於第二幀沒有及時的繪製(當然即使第二幀及時繪製,也依然可能出現Jank,這就是同時引入三重緩衝的作用。我們將在三重緩衝一節中再講解這種情況)。那麼如何使得第二幀即使被繪製呢?
這就是我們在Graphic系統中引入VSYNC的原因,考慮下面這張圖:

此處輸入圖片的描述

如上圖所示,一旦VSync出現後,立刻就開始執行下一幀的繪製工作。這樣就可以大大降低Jank出現的概率。另外,VSYNC引入後,要求繪製也只能在收到VSYNC訊息之後才能進行,因此,也就杜絕了另外一種極端情況的出現—-CPU(GPU)一直不停的進行繪製,幀的生成速度高於螢幕的重新整理速度,導致生成的幀不能被顯示,只能丟棄,這樣就出現了丟幀的情況—-引入VSYNC後,繪製的速度就和螢幕重新整理的速度保持一致了。

VSYNC訊號的生成

那麼VSYNC訊號是如何生成的呢?
Android系統中VSYNC訊號分為兩種,一種是硬體生成的訊號,一種是軟體模擬的訊號。
硬體訊號是由HardwareComposer提供的,HWC封裝了相關的HAL層,如果硬體廠商提供的HAL層實現能定時產生VSYNC中斷,則直接使用硬體的VSYNC中斷,否則HardwareComposer內部會通過VSyncThread來模擬產生VSYNC中斷(其實現很簡單,就是sleep固定時間,然後喚醒)。

回到我們上一節中講到的SurfaceFlinger的啟動過程inti函式上來,上一節我們提到init函式內會建立一個HWComposer物件。

  1. HWComposer::HWComposer(  
  2.         const sp<SurfaceFlinger>& flinger,  
  3.         EventHandler& handler)  
  4.     : mFlinger(flinger),  
  5.       mFbDev(0), mHwc(0), mNumDisplays(1),  
  6.       mCBContext(new cb_context),  
  7.       mEventHandler(handler),  
  8.       mDebugForceFakeVSync(false)  
  9. {  
  10. ...  
  11.     //首先是一些和VSYNC有關的資訊的初始化
  12.     //因為在硬體支援的情況下,VSYNC的功能就是由HWC提供的
  13.     for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) {  
  14.         mLastHwVSync[i] = 0;  
  15.         mVSyncCounts[i] = 0;  
  16.     }  
  17.     //根據配置來看是否需要模擬VSYNC訊息
  18.     char value[PROPERTY_VALUE_MAX];  
  19.     property_get("debug.sf.no_hw_vsync", value, "0");  
  20.     mDebugForceFakeVSync = atoi(value);  
  21.     ...  
  22.     // don't need a vsync thread if we have a hardware composer
  23.     needVSyncThread = false;  
  24.     // always turn vsync off when we start,只是暫時關閉訊號,後面會再開啟
  25.     eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0);      
  26.     //顯然,如果需要模擬VSync訊號的話,我們需要執行緒來做這個工作
  27.     if (needVSyncThread) {  
  28.         // we don't have VSYNC support, we need to fake it
  29.         //VSyncThread類的實現很簡單,無非就是一個計時器而已,定時傳送訊息而已
  30.         //TODO VSYNC專題
  31.         mVSyncThread = new VSyncThread(*this);  
  32.     }  
  33. ...  
  34. }  
  35. HWComposer::HWComposer(  
  36.         const sp<SurfaceFlinger>& flinger,  
  37.         EventHandler& handler)  
  38.     : mFlinger(flinger),  
  39.       mFbDev(0), mHwc(0), mNumDisplays(1),  
  40.       mCBContext(new cb_context),  
  41.       mEventHandler(handler),  
  42.       mDebugForceFakeVSync(false)  
  43. {  
  44. ...  
  45.     //首先是一些和VSYNC有關的資訊的初始化
  46.     //因為在硬體支援的情況下,VSYNC的功能就是由HWC提供的
  47.     for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) {  
  48.         mLastHwVSync[i] = 0;  
  49.         mVSyncCounts[i] = 0;  
  50.     }  
  51.     //根據配置來看是否需要模擬VSYNC訊息
  52.     char value[PROPERTY_VALUE_MAX];  
  53.     property_get("debug.sf.no_hw_vsync", value, "0");  
  54.     mDebugForceFakeVSync = atoi(value);  
  55.     ...  
  56.     // don't need a vsync thread if we have a hardware composer
  57.     needVSyncThread = false;  
  58.     // always turn vsync off when we start,只是暫時關閉訊號,後面會再開啟
  59.     eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0);      
  60.     //顯然,如果需要模擬VSync訊號的話,我們需要執行緒來做這個工作
  61.     if (needVSyncThread) {  
  62.         // we don't have VSYNC support, we need to fake it
  63.         //VSyncThread類的實現很簡單,無非就是一個計時器而已,定時傳送訊息而已
  64.         //TODO VSYNC專題
  65.         mVSyncThread = new VSyncThread(*this);  
  66.     }  
  67. ...  
  68. }  

我們來看下上面這段程式碼。
首先mDebugForceFakeVSync是為了調製,可以通過這個變數設定強制使用軟體VSYNC模擬。
然後針對不同的螢幕,初始化了他們的mLastHwVSync和mVSyncCounts值。
如果硬體支援,那麼就把needVSyncThread設定為false,表示不需要軟體模擬。
接著通過eventControl來暫時的關閉了VSYNC訊號,這一點將在下面講解eventControl時一併講解。
最後,如果需要軟體模擬Vsync訊號的話,那麼我們將通過一個單獨的VSyncThread執行緒來做這個工作(fake VSYNC是這個執行緒唯一的作用)。我們來看下這個執行緒。

軟體模擬

  1. bool HWComposer::VSyncThread::threadLoop() {  
  2.     const nsecs_t period = mRefreshPeriod;  
  3.     //當前的時間
  4.     const nsecs_t now = systemTime(CLOCK_MONOTONIC);  
  5.     //下一次VSYNC到來的時間
  6.     nsecs_t next_vsync = mNextFakeVSync;  
  7.     //為了等待下個時間到來應該休眠的時間
  8.     nsecs_t sleep = next_vsync - now;  
  9.     //錯過了VSYNC的時間
  10.     if (sleep < 0) {  
  11.         // we missed, find where the next vsync should be
  12.         //重新計算下應該休息的時間
  13.         sleep = (period - ((now - next_vsync) % period));  
  14.         //更新下次VSYNC的時間
  15.         next_vsync = now + sleep;  
  16.     }  
  17.     //更新下下次VSYNC的時間
  18.     mNextFakeVSync = next_vsync + period;  
  19.     struct timespec spec;  
  20.     spec.tv_sec  = next_vsync / 1000000000;  
  21.     spec.tv_nsec = next_vsync % 1000000000;  
  22.     int err;  
  23.     do {  
  24.         //納秒精度級的休眠
  25.         err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);  
  26.     } while (err<0 && errno == EINTR);  
  27.     if (err == 0) {  
  28.         //休眠之後,到了該發生VSYNC的時間了
  29.         mHwc.mEventHandler.onVSyncReceived(0, next_vsync);  
  30.     }  
  31.     returntrue;  
  32. }  

這個函式其實很簡單,無非就是一個簡單的時間計算,計算過程我已經寫在了程式註釋裡面。總之到了應該發生VSYNC訊號的時候,就呼叫了mHwc.mEventHandler.onVSyncReceived(0, next_vsync)函式來通知VSYNC的到來。

我們注意到mEventHandler實際上是在HWC建立時被傳入的,我們來看下HWC建立時的程式碼.

  1. mHwc = new HWComposer(this,  
  2.         *static_cast<HWComposer::EventHandler *>(this));  
  3. class SurfaceFlinger : public BnSurfaceComposer,  
  4.                    private IBinder::DeathRecipient,  
  5. 相關推薦

    Android 4.4 Graphic系統2 VSYNC生成

    VSYNC 的概念 VSYNC(Vertical Synchronization)是一個相當古老的概念,對於遊戲玩家,它有一個更加大名鼎鼎的中文名字—-垂直同步。 “垂直同步(vsync)”指的是顯示卡的輸出幀數和螢幕的垂直重新整理率相同,這完全是一個CRT顯

    CentOS 7.4 Tengine安裝配置

    tengine nginx 一、安裝配置Tengine:Tengine是由淘寶網發起的Web服務器項目。它在Nginx的基礎上,針對大訪問量網站的需求,添加了很多高級功能和特性。Tengine的性能和穩定性已經在大型的網站如淘寶網,天貓商城等得到了很好的檢驗。它的最終目標是打造一個高效、穩定、安全、易

    CentOS 7.4 Tengine安裝配置

    tengine、虛擬主機、IP、訪問控制三、配置虛擬主機:1、配置基於端口的虛擬主機:(1)在http{}配置段中新增如下server:server {listen 8000;server_name localhost;access_log /usr/local/tengine/logs/localhost8

    CentOS 7.4 Tengine安裝配置

    location、echo、fancy九、根據HTTP響應狀態碼自定義錯誤頁:1、未配置前訪問一個不存在的頁面:http://192.168.1.222/abc/def.html,按F12後刷新頁面2、在server{}配置段中新增如下location:server {listen 80;server_nam

    CentOS 7.4 Tengine安裝配置

    tengine nginx https 十四、配置Tengine支持HTTPS1、演示環境:IP操作系統角色 192.168.1.222 CentOS 7.4 Tengine服務器 192.168.1.145 CentOS 6.9 自建CA服務器備註:Teng

    CentOS 7.4 Tengine安裝配置

    tengine 反向代理 十五、反向代理:1、演示環境:IP操作系統節點角色192.168.1.222CentOS 7.4node1Tengine服務器192.168.1.144CentOS 6.9node2Apache服務器2、node2安裝Apache服務,並創建測試頁:# yum -y inst

    CentOS 7.4 Tengine安裝配置

    tengine cache purge 十六、緩存及緩存清理1、修改node1配置文件nginx.conf:(1)在http配置段中增加如下代碼:proxy_cache_path /usr/local/tengine/cache levels=1:1:2 keys_zone=mycache:200

    Glide 4.7.1 使用

    目錄 前言  使用方法 過度選項 變換 載入gif 總結 前言        圖片載入框架目前用的比較多的是picasso和glide, 其中谷歌官方也比較推薦glide, 在前文中已經分析了picasso的原理,在這裡我們就開始分析

    Android編譯系統

    ++++++++++++++++++++++++++++++++++++++++++ 本文系本站原創,歡迎轉載! 轉載請註明出處: ++++++++++++++++++++++++++++++++++++++++++ Android的優勢就在於其開源,手機和平板

    Android編譯系統

    ++++++++++++++++++++++++++++++++++++++++++本文系本站原創,歡迎轉載! 轉載請註明出處:++++++++++++++++++++++++++++++++++++++++++前面兩節講解了自定義Android編譯項和建立Product產品

    Android 動畫】View Animation

    安卓平臺目前提供了兩大類動畫,在Android 3.0之前,一大類是View Animation,包括Tween animation(補間動畫),Frame animation(幀動畫),在android3.0中又引入了一個新的動畫系統:property ani

    Android 5.X 新特性——主題、Palette、陰影、著色和裁剪

    Android 5.X 系列開始使用新的設計風格Material Design來統一整個Android系統的介面設計風格。 Material Design 主題 Material Design 現在有三種預設的主題可以設定,程式碼如下: @andr

    Android程式設計之DialogFragment原始碼

    DialogFragment是Fragment家族成員之一,如果你把它簡單的理解成Dialog,那就錯了。它的確可以做作dialog顯示,還可以顯示出自己定義的Dialog或者AlertDialog,但它同時也是一個Fragment。 按照官方的話來理解就是,你既可以把它當

    Android中聯絡人和通話記錄2

      在文章Android中聯絡人和通話記錄詳解(1)中對通話記錄進行了分析,本章將對聯絡人的資料庫表、欄位以及Insert,Query,Delelte,Update四大基本資料操作進行分析。   與聯

    Android事件分發機制流程

    前言:上一篇我們已經從事件分發執行流程入手,一起來了解並分析了事件分發的經過,大家應該從分析中能對事件分發的有個總體的認識,並且我相信應該也能自己分析出事件會如何執行,其實就那麼點東西,弄明白了就不難了,但是今天我們還是要來看看activity,viewg

    android ViewId自動註解使用ViewInject

    Annotation就是註解了,JDK1.5新增加功能,該功能可用於類,構造方法,成員變數,方法,引數等的宣告中。 api版本23下,android studio裡是直接可以使用的,不用新增依賴庫,以

    Android 5.X 新特性——Material Design 動畫效果

    Ripple效果 在Android 5.X 中,Material Design 大量使用了Ripple效果,即點選後的波紋效果。可以通過如下程式碼設定波紋的背景。 //有界波紋 android:background="?android:attr/sele

    canvas特效代碼2

    text pre javascrip css png tco border src null canvas是一個就基於像素的畫圖h5元素。 利用canvas做一個如下描述所示的動態圖形:當鼠標點下去時開始繪圖,在鼠標結束時完成一個矩形,當再一次點擊時重復第一次的繪圖步驟

    經典算法2尋找數組中的次大數

    etc n) esp arr else second include 尋找 char 題目:10個互不相等的整數,求其中的第2大的數字,要求數組不能用排序,設計的算法效率越高越好。 1 #include<iostream> 2 3 using name