1. 程式人生 > >GUI系統之SurfaceFlinger(12)VSync訊號的產生和處理

GUI系統之SurfaceFlinger(12)VSync訊號的產生和處理

文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。
轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/

GUI系統之SurfaceFlinger章節目錄:
blog.csdn.net/uiop78uiop78/article/details/8954508

前面小節ProjectButter中我們學習了Android 4.1顯示系統中的新特性,其中一個就是加入了VSync同步。我們從理論的角度分析了採用這一機制的必要性和運作機理,那麼SurfaceFlinger具體是如何實施的呢?

先來想一下有哪些東西要考慮:

·          VSync訊號的產生和分發

如果有硬體主動發出這一訊號,那是最好的了;否則就得通過軟體定時模擬來產生

·          VSync訊號的處理

當訊號產生後,SurfaceFlinger如何在最短的時間內響應,具體處理流程是怎麼樣子的

在Android原始碼surfaceflinger目錄下有一個displayhardware資料夾,其中HWComposer的主要職責之一,就是用於產生VSync訊號。

/*frameworks/native/services/surfaceflinger/displayhardware/HWComposer.cpp*/

HWComposer::HWComposer(const sp<SurfaceFlinger>& flinger,EventHandler& handler, nsecs_t refreshPeriod)

: mFlinger(flinger), mModule(0), mHwc(0), mList(0), mCapacity(0),mNumOVLayers(0),

 mNumFBLayers(0), mDpy(EGL_NO_DISPLAY),mSur(EGL_NO_SURFACE),

      mEventHandler(handler),mRefreshPeriod(refreshPeriod),

      mVSyncCount(0),mDebugForceFakeVSync(false)

{

    charvalue[PROPERTY_VALUE_MAX];

   property_get("debug.sf.no_hw_vsync", value, "0"); //系統屬性

    mDebugForceFakeVSync =atoi(value);

    bool needVSyncThread =false;//是否需要軟體模擬VSync

    int err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule);//載入HAL模組

    if (err == 0) {

        err = hwc_open(mModule, &mHwc);//開啟module

        if (err == 0) {

if(mHwc->registerProcs) { //註冊硬體裝置事件回撥

                mCBContext.hwc= this;

               mCBContext.procs.invalidate = &hook_invalidate;

               mCBContext.procs.vsync = &hook_vsync;

                mHwc->registerProcs(mHwc, &mCBContext.procs);

               memset(mCBContext.procs.zero, 0, sizeof(mCBContext.procs.zero));

            }

            if(mHwc->common.version >= HWC_DEVICE_API_VERSION_0_3) {

                if(mDebugForceFakeVSync) {//用於除錯

                   mHwc->methods->eventControl(mHwc, HWC_EVENT_VSYNC, 0);

                }

            } else {//有可能支援VSync的硬體模組是這個版本以後才加入的,老版本仍然需要軟體模擬

               needVSyncThread = true;

            }

        }

    } else {

        needVSyncThread =true; //硬體模組開啟失敗,只能用軟體模擬

    }

    if (needVSyncThread) {

        mVSyncThread = new VSyncThread(*this);//建立一個產生VSync訊號的執行緒

    }

}

這個函式的核心就是決定VSync的“訊號發生源”——硬體或者軟體模擬。

假如當前系統可以成功載入HWC_HARDWARE_MODULE_ID=“hwcomposer”,並且通過這個庫模組能順利開啟裝置(hwc_composer_device_t),其版本號又大於HWC_DEVICE_API_VERSION_0_3的話,我們就採用“硬體源”(此時needVSyncThread為false),否則需要建立一個新的VSync執行緒來模擬產生訊號。

(1)硬體源

如果mHwc->registerProcs不為空的話,我們註冊硬體回撥mCBContext.procs。定義如下:

    struct cb_context{

        callbacksprocs;

        HWComposer*hwc;

    };

呼叫registerProcs()時,傳入的引數是&mCBContext.procs。後期當有事件產生時,比如vsync或者invalidate,硬體模組將分別通過procs.vsync和procs.invalidate來通知HWComposer。

void HWComposer::hook_vsync(struct hwc_procs* procs, int dpy,int64_t timestamp) {

   reinterpret_cast<cb_context *>(procs)->hwc->vsync(dpy,timestamp);

}

上面這個函式中,procs即前面的&mCBContext.procs,從指標地址上看它和&mCBContext是一致的,因而我們可以強制型別轉換為cb_context來進行操作,並由此訪問到hwc中的vsync實現:

void HWComposer::vsync(int dpy, int64_t timestamp) {

   mEventHandler.onVSyncReceived(dpy, timestamp);

}

HWComposer將VSync訊號直接通知給mEventHandler,這個Handler由HWComposer構造時傳入,換句話說,我們需要看下是誰建立了HWComposer。

/*frameworks/native/services/surfaceflinger/displayhardware/DisplayHardware.cpp*/

void DisplayHardware::init(uint32_t dpy)

{…

mHwc = newHWComposer(mFlinger, *this, mRefreshPeriod);

從這裡可以看出來,HWComposer中的mEventHandler就是DisplayHardware物件,所以後者必須要繼承自HWComposer::EventHandler,以此處理產生的onVSyncReceived事件。

(2)軟體源

軟體源和硬體源的最大區別是它需要啟動一個新執行緒VSyncThread,其執行優先順序與SurfaceFlinger的工作執行緒是一樣的,都是-9。從理論的角度講,任何通過軟體定時來實現的機制都不可能是100%可靠的,即使優先順序再高也可能出現延遲和意外。不過如果“不可靠”的機率很小,而且就算出現意外時不至於是致命錯誤,那麼還是可以接受的。所以說VSyncThread從實踐的角度來講,的確起到了很好的作用。

bool HWComposer::VSyncThread::threadLoop() {

   /*Step1. 系統是否使能了VSync訊號發生機制*/

    { // 自動鎖控制範圍

        Mutex::Autolock_l(mLock);

        while (!mEnabled) {//VSync訊號開關

           mCondition.wait(mLock);

        }

    }

    /*Step2. 計算產生VSync訊號的時間*/

    const nsecs_t period = mRefreshPeriod;//訊號的產生間隔

    const nsecs_t now =systemTime(CLOCK_MONOTONIC);

    nsecs_t next_vsync =mNextFakeVSync;//產生訊號的時間

    nsecs_t sleep = next_vsync- now; //需要休眠的時長

    if (sleep < 0) {//已經過了時間點

        sleep = (period - ((now - next_vsync) %period));

        next_vsync = now +sleep;

    }

    mNextFakeVSync =next_vsync + period; //再下一次的VSync時間

    struct timespec spec;

    spec.tv_sec  = next_vsync / 1000000000;

    spec.tv_nsec = next_vsync% 1000000000;

    int err;

    do {

        err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);//進入休眠

    } while (err<0&& errno == EINTR);

    if (err == 0) {

        mHwc.mEventHandler.onVSyncReceived(0, next_vsync);//和硬體源是一樣的回撥

    }

    return true;

}

[email protected] VSyncThread::threadLoop. 關於自動鎖的使用我們已經分析過很多次了,不再贅述。這裡要注意的是mEnabled這個變數,它是用於控制是否產生VSync訊號的一個使能變數。當系統希望關閉VSync訊號發生源時,呼叫VSyncThread::setEnabled(false),否則傳入true。假如mEnabled為false時,VSyncThread就處於等待狀態,直到有人再次使能這個執行緒。

[email protected] VSyncThread::threadLoop. 接下來的程式碼用於真正產生一個VSync訊號。可以想象一下,無非就是這些步驟:

·          計算下一次產生VSync訊號的時間

·          進入休眠

·          休眠時間到了後,就代表應該發出VSync訊號了,通知感興趣的人

·          迴圈往復

變數mRefreshPeriod指定了產生VSync訊號的間隔。它是在DisplayHardware::init中計算出來的:

mRefreshPeriod = nsecs_t(1e9 / mRefreshRate);

如果mRefreshRate為60Hz的話,mRefreshPeriod就差不多是16ms。

因為mNextFakeVSync代表的是“下一次”產生訊號的時間點,所以首先將next_vsync=mNextFakeVSync。接著計算sleep,也就是離產生訊號的時間點還有多長(同時也是需要休眠的時間)。那麼如果sleep的結果小於0呢?代表我們已經錯過了這一次產生訊號的最佳時間點,這是有可能發生的。在這種情況下,就計算下一次最近的VSync離現在還剩多少時間,公式如下:

sleep = (period - ((now - next_vsync) % period));

我們以下圖來表述下采用這個公式的依據:


圖 11‑33 休眠時間推算簡圖

這個圖的前提是now超時時間不超過一個period。因而公式中還要加上%period。

計算完成sleep後,mNextFakeVSync= next_vsync +period。這是因為mNextFakeVSync代表的是下一次threadLoop需要用到的時間點,而next_vsync是指下一次(最近一次)產生VSync的時間點。

如何在指定的時間點再產生訊號呢?有兩種方法,其一是採用定時器回撥,其二就是採用休眠的形式主動等待,這裡使用的是後一種。

可想而知這裡的時間要儘可能精準,單位是nanosecond,即納秒級。函式clock_nanosleep的第一個入參是CLOCK_MONOTONIC,這種時鐘更加穩定,且不受系統時間的影響。

當休眠時間到了後,表示產生訊號的時刻到了。根據前面的分析,就是通過mEventHandler.onVSyncReceived()回撥來通知對訊息感興趣的人,這個做法軟硬體都一樣。

一次訊號產生完成後,函式直接返回true,似乎沒有看到迴圈的地方?這是因為當threadLoop返回值為“真”時,它將被系統再一次呼叫,從而迴圈起來。不清楚的可以參閱一下Thread類的實現。

接下來看下DisplayHardware如何處理這個VSync訊號的。

中間過程很簡單,我們就不一一解釋。在DisplayHardware::onVSyncReceived中,它又再次呼叫內部mVSyncHandler的onVSyncReceived(),將訊息向上一層傳遞。這個變數由EventThread在onFirstRef時通過DisplayHardware::setVSyncHandler()設定,代表的是EventThread物件本身,如下:

void EventThread::onFirstRef() {

   mHw.setVSyncHandler(this);//this指標代表EventThread物件

所以VSync訊號被進一步遞交到了EventThread中。顯然,它也不是終點。

void EventThread::onVSyncReceived(int, nsecs_t timestamp) {

    Mutex::Autolock _l(mLock);

    mVSyncTimestamp =timestamp;

    mCondition.broadcast();//有人在等待事件的到來

}

等待VSync事件的地方很多,其中最重要的是EventThread::threadLoop(),這個函式將負責對VSync進行分發,決定誰有權利來最終處理這一事件。

這個函式的主體邏輯還是比較簡單的,不過因為很長,內部又夾雜著多個迴圈體,顯得不好理解,因此我們只摘選最重要的一部分來加快大家的閱讀。

bool EventThread::threadLoop() {

    nsecs_t timestamp;

   DisplayEventReceiver::Event vsync;

    Vector<wp<EventThread::Connection> > displayEventConnections;

    do {//Step1. 第一個迴圈體

        Mutex::Autolock_l(mLock);

        do {…//Step2. 第二個迴圈體,決定是否上報VSync

        } while(true);

        //跳出迴圈,接下來就要準備分發VSync了

        mDeliveredEvents++;

        mLastVSyncTimestamp =timestamp;

        const size_t count =mDisplayEventConnections.size();

        for (size_t i=0 ;i<count ; i++) {

            …//Step3. 第三個迴圈中逐個判斷各connection是否需要上報

            if (reportVsync) {

                displayEventConnections.add(connection);

            }

        }

    } while(!displayEventConnections.size());//只要size不等於0就可以退出迴圈了

    // 終於開始分發了。。。

    vsync.header.type =DisplayEventReceiver::DISPLAY_EVENT_VSYNC;

    vsync.header.timestamp =timestamp;

vsync.vsync.count =mDeliveredEvents;

const size_t count =displayEventConnections.size();

    for (size_t i=0 ;i<count ; i++) {//Step4. 第四個迴圈體,分發事件

        sp<Connection>conn(displayEventConnections[i].promote());

        if (conn != NULL) {

            status_t err =conn->postEvent(vsync);//通知connection發起者

            if (err == -EAGAIN|| err == -EWOULDBLOCK) {

                //這兩個錯誤是指對方當前不接受事件,有可能是暫時性的

            } else if (err< 0) {

                //發生了致命錯誤,一律移除

               removeDisplayEventConnection(displayEventConnections[i]);

            }

        } else {//connection已經死了,將它移除

           removeDisplayEventConnection(displayEventConnections[i]);

        }

    }

   …

    return true;

}

一共有四個迴圈體,看起來很亂,我們先以虛擬碼的形式來重新表述一遍:

do {//第一個迴圈體

   do {

//第二個迴圈體,判斷當前系統是否允許上報VSync

   } while(true);

   for (size_t i=0 ; i<count ; i++) {

      //第三個迴圈體,逐個計算需要上報的connection個數

   }

} while (!displayEventConnections.size());/*一旦需要上報的連線數超過0,

就可以退出迴圈了*/

for (size_t i=0 ; i<count ; i++) {

/*第四個迴圈,開始實際的分發。這時要先考慮connection是否死亡,然後就是判斷分發後是否有異常返回,比如EWOULDBLOCK等等。對於暫態的錯誤,理論上是要再重發的,不過當前系統還沒有這麼做。的確,一方面這將使程式邏輯變得複雜,另一方面,即便丟一兩個VSYNC,也無傷大局。所以從原始碼註釋來看,將來這部分也不會改善。*/

}

相信大家結合這段虛擬碼再來對照原始碼,就比較清楚了。

對VSYNC訊號感興趣的人,可以通過registerDisplayEventConnection()來與EventThread建立一個連線。搜尋程式碼可以發現,當前系統中建立了連線的物件是MessageQueue,具體程式碼在MessageQueue::setEventThread()中。

我們以下圖來總結本小節的內容:


圖 11‑34 VSYNC訊號的產生與分發

整體邏輯關係相對複雜,建議大家在做原始碼分析時,以下面兩條線索進行:

l  VSync訊號的傳遞流向

l  各個類的靜態依賴關係。比如DisplayHardware持有一個HWComposer物件,同時這個物件的mEventHandler成員變數又指向DisplayHardware


相關推薦

GUI系統SurfaceFlinger(12)VSync訊號產生處理

文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne

android Gui系統SurfaceFlinger(5)---Vsync(2)

9.Vsync第二部分 在上一篇中我們講到,檢視的重新整理需要很多步驟, void SurfaceFlinger::handleMessageRefresh() { ATRACE_CALL(); preComposition();  //合成前的準備 rebui

android Gui系統SurfaceFlinger(4)---Vsync(1)

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

android Gui系統SurfaceFlinger(3)---SurfaceFlinger

7.SurfaceFlinger SurfaceFlinger在前面的篇幅了,多有涉及。 SurfaceFlinger是GUI重新整理UI的核心,所以任何關於SurfaceFlinger的改進都會對android UI系統有重大影響。 SurfaceFlinger主要分為4個部分 1

android Gui系統SurfaceFlinger(2)---BufferQueue

6 BufferQueue 上一篇已經說到,BufferQueue是SurfaceFlinger管理和消費surface的中介,我們就開始分析bufferqueue。 每個應用 可以由幾個BufferQueue? 應用繪製UI 所需的記憶體從何而來? 應用和SurfaceFlinge

android Gui系統SurfaceFlinger(1)---SurfaceFlinger概論

  GUI 是任何系統都很重要的一塊。 android GUI大體分為4大塊。 1)SurfaceFlinger 2)WMS 3)View機制 4)InputMethod 這塊內容非常之多,但是理解後,可以觸類旁通,其實現在主流的系統,包括andorid,ios

android Gui系統SurfaceFlinger(1)---SurfaceFlinger概論【轉】

轉自:https://www.cnblogs.com/deman/p/5584198.html 閱讀目錄 1.OpenGL & OpenGL ES 2.Android的硬體介面HAL 3.Android顯示裝置:Gralloc &  FrameBuff

GUI系統SurfaceFlinger(18)postFramebuffer

文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne

GUI系統SurfaceFlinger(15)handlePageFlip

文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne

【Linux】程序間通訊訊息佇列、訊號共享儲存

訊息佇列、訊號量和共享儲存是IPC(程序間通訊)的三種形式,它們功能不同,但有相似之處,下面先介紹它們的相似點,然後再逐一說明。 1、相似點 每個核心中的IPC結構(訊息佇列、訊號量和共享儲存)都用一個非負整數的識別符號加以引用,與檔案描述符不同,當一個

IPC通訊訊息佇列、訊號共享記憶體

    有三種IPC我們稱作XSI IPC,即訊息佇列,訊號量以及共享儲存器。XSI IPC源自System V的IPC功能。由於XSI IPC不使用檔案系統的名稱空間,而是構造了它們自己的名字空間,

linux系統網路防火牆(firewalld服務iptables服務)

一.對於防火牆的理解 防火牆,其實就是用於實現Linux下訪問控制的功能的,它分為硬體的和軟體的兩種。 無論是在哪個網路中,防火牆工作的地方一定是在網路的邊緣。 而我們的任務就是需要去定義到底防火牆如何工作,這就是防火牆的策略,規則, 以達到讓它對出入網路的IP、資料進行檢

GUI開發AWT、SWING、SWTJFACE的比較

 AWT Abstract Windows Toolkit(AWT)是最原始的 Java GUI 工具包。在任何一個 Java 執行環境中都可以使用它。 AWT 是一個非常簡單的具有有限 GUI 元件、佈局管理器和事件的工具包.有些經常使用的元件,例如表、樹、進度條等

複習筆記11 異常的產生處理

1 異常體系&異常處理 1.1 異常概述   我們在寫程式碼的時候,經常的出現一些小問題,那麼為了方便我們處理這些問題,java為我們提供了異常機制 在Java中,把異常資訊封裝成了一個類。當出現了問題時,就會建立異常類物件並丟擲異常相關的資訊(如異常出現的位置、原因等)。 在Java中使用E

監控系統Nagios系列(八) 抖動(falpping)檢測處理

所謂抖動,是指狀態在一定時間內變化過於頻繁。如果某個物件處於抖動狀態,那麼這時候的狀態變化也是無意義的。 Nagios提供了抖動狀態的檢查以及針對抖動狀態的處理。 1.判定抖動狀態 抖動是由於狀態變化過於頻繁導致,但是該如何定義“頻繁”?每次檢查得到的狀態都與上次不一樣?還是一定時間內狀態變化達到某

Linux學習路-Linux自動化系統安裝【12】---20171230

repos exec auth bdc u盤啟動 eth 本地 ner 微型linux 安裝程序 CentOS系統安裝 系統啟動流程:bootloader-->kernel(initramfs)-->rootfs-->/sbin/initanaco

2018-10-18讀文獻總結DCB分碼多重進接、零基線、訊號產生

---恢復內容開始---   今天心血來潮,想開始把自己讀文獻的過程和每篇文獻的收穫總結一下,不知道CSDN怎麼回事,一直登陸不進去,搞得我註冊了一個部落格園的賬戶,部落格園新註冊的還需要認證,但是很快,所以我就來這邊了。文筆不好,主要是一些流水賬,用來自己看看。 前兩天一直搞不清DCB怎麼消除,看了一些文

【 MATLAB 】訊號處理工具箱的訊號產生函式 sawtooth 函式簡記

 sawtooth 函式 x = sawtooth(t) generates a sawtooth wave with period 2π for the elements of the time

ZR高速寬頻訊號產生回放系統

西安慕雷電子釋出全球頂級高速資料採集卡,取樣率4GSPS,模擬頻寬2GHZ,記錄儲存頻寬高達 6GB/S! 慕雷電子圍繞雷達、通訊、導航、電子對抗與偵查等領域,提供國內領先的訊號測試產 品與服務:訊號採集、處理、記錄與回放,用於試驗資料實時記錄、事後分析與外場

數字訊號產生泊松分佈的隨機數

uniform.h #pragma once class uniform { private:  double a, b, generate_num;  int * seed;  int s;  int M, N, i, j; public:  uniform()  {