2015年1月28日 星期三

DispSync

如果你曾經打開過systrace的檔案並觀察SurfaceFlinger的process, 你可能看過下圖中用紅色標起來的區域. 這四個代表的是VSync相關的information.


HW_VSYNC_ON_0代表Primary Display的VSync被enable或disable. 0這個數字代表的是display的編號, 0是Primary Display, 如果是External monitor, 就會是HW_VSYNC_ON_1. 當SF要求HWComposer將Display的VSync打開或關掉時, 這個event就會記錄下來.

HW_VSYNC_0代表Primary Display的VSync發生時間點, 0代表display編號. 以下圖為例, A, B, C, D分別代表VSync發生的時間, 這個event並沒有記錄VSync結束的時間, 所以, 不能把它解讀為在A時間點VSync開始, 然後在B結束, C開始, D結束. 正確的解讀是是A-B代表的是一個frame, A是VSync發生點, B是下一個VSync開始的時間. 如果refresh rate是60Hz的話, A-B, B-C, C-D的長度應該在16.6ms左右 (會有latency), 之所以有0與1的變化是為了讓人比較容易讀, 但有時候也會造成解讀錯誤.


通常為了避免Tearing的現象, 畫面更新(Flip)的動作通常會在VSync開始的時候才做, 因為在VSync開始到它結束前, Display不會抓framebuffer資料顯示在display上, 所以在這段時間做Flip可以避免使用者同時看到前後兩個部份畫面的現象.

目前user看到畫面呈現的過程是這樣子的, app更新它的畫面後, 它需要透過BufferQueue通知SF, SF再將更新過的app畫面與其它的App或Status Bar/Navigation Bar組合後, 再顯示在使用者面前. 在這個過程裡, 有3個component牽涉進來, 分別是App, SF, 與Display. 以目前Android的設計, 這三個Component都是在VSync發生的時候才開始做事. 我們將它們想成一個有3個stage的pipeline, 這個pipeline的clock就是HW_VSYNC_0.
  1. T = 0時, App正在畫N, SF與Display都沒資料可用
  2. T = 1時, App正在畫N+1, SF組合N, Display沒Buffer可顯示
  3. T = 2時, App正在畫N+2, SF組合N+1, Display顯示N
  4. T = 3時, App正在畫N, SF組合N+2, Display顯示N+1
  5. ...

如果按照這個步驟, 當user改變一個畫面時, 要等到2個VSync後, 畫面才會顯示在user面前, latency大概是33ms (2個frame的時間). 但是對大部份的操作來講, 可能app加SF畫的時間一個frame (16.6ms)就可以完成. 因此, Android就從HW_VSYNC_0才產生出兩個VSync, VSYNC-app是給App用的, VSYNC-sf是給SF用的, Display則是使用HW_VSYNC_0. VSYNC-app與VSYNC-sf可以分別給定一個phase, 簡單的說
  • VSYNC-app = HW_VSYNC_0 + phase_app
  • VSYNC-sf = HW_VSYNC_0 + phase_sf


也就是說, 如果phase_app與phase_sf設定的好的話, 可能大部份user使用的狀況, App+SF可以在一個frame裡完成, 然後在下一個HW_VSYNC_0來的時候, 顯示在display上.
理論上透過VSYNC-sf與VSYNC-app的確是可以有機會在一個frame裡把App+SF做完, 但是實際上不是一定可以在一個frame裡完成. 因為有可能因為CPU被搶走, 或memory不夠, 以致App或SF沒辦法及時做完. 如果我們觀察systrace可以發現不是每次Render的時間都很穩定. 下圖是systrace裡Launcher3在畫一個frame所花的時間, 從每一段的長度可以看出時間其實每個frame有某種程度的變動. 但是除非有不正常的狀況, 不然其實變動的範圍不會太大.



如果不能在一個frame裡完成, 就會變成latency可能會有比較大的變化, 一旦latency不是很穩定, 對人的視覺系統而言, 就容易感覺不舒服, 可能比delay兩個VSync還差. 所以在調整這個參數時, 只要能將latency維持在一個固定的範圍, 應該可以改善user experience, 比方說將latency調整到1.5個frame.

如果你想使用這個功能, 只要在Makefile裡設定VSYNC_EVENT_PHASE_OFFSET_NS和SF_VSYNC_EVENT_PHASE_OFFSET_NS就可以了. Google是建議放在BoardConfig.mk裡. 前者代表VSYNC-app, 後者代表VSYNC-sf.

這樣子看起來在SF裡, 只要收到硬體VSYNC, 再等一些offset, 就可以觸發VSYNC-app與VSYNC-sf了. 但實際並不是這樣. 除非使用者在玩game或播影片, 在一般使用情況下, 畫面並不會一直更新, 比方我們在看一篇文章, 大部份時間, 畫面是維持一樣的, 如果我們在每個硬體VSYNC來的時候都把SF或APP叫起來, 就會浪費時間與增加Power的使用. 所以在Android裡, 通常SF與APP都會利用requestNextVsync()來告訴系統我要在下一個VSYNC被叫起來做事情. 也就是說, 雖然系統可能每秒有60個硬體VSYNC, 但不代表Software每個VSYNC都要做事情. 因此, 在Android裡, 我們是根據Software VSYNC來做事. Software VSYNC是根據硬體VSYNC過去發生的時間, 推測未來會發生的時間. 因此, 當APP或SF利用requestNextVsync時, Software VSYNC才會觸發VSYNC-sf或VSYNC-app. DispSync這個class就是Android使用的Software VSYNC.

底下是SurfaceFlinger裡EventThread::requestNextVsync(), 做的事很簡單, 只是檢查connection的counter, 小於0就將它設成0, 代表只要下一個VSYNC通知它就好 (one-shot)
     void EventThread::requestNextVsync(
             const sp& connection) {
         Mutex::Autolock _l(mLock);
         if (connection->count < 0) {
             connection->count = 0;
             mCondition.broadcast();
         }
     }

因為它connection的counter >= 0時, EventThread的threadLoop就會認為這個connection需要SW_VSYNC, 相關的code可以參考EventThread::waitForEvent()
     Vector< sp > EventThread::waitForEvent(
             DisplayEventReceiver::Event* event)
     {
         ...
         do {
             size_t count = mDisplayEventConnections.size();
             for (size_t i=0; i
                 if (connection != NULL) {
                     bool added = false;
                     if (connection->count >= 0) {
                         waitForVSync = true;
                         ...
                         if (connection->count == 0) {
                             connection->count = -1;
                             signalConnections.add(connection);
                             added = true;
                         }
                         ...
                     }
                 }
             }
         } while (signalConnections.isEmpty());
     }


我們可以用下圖來了解DispSync的架構, DispSync像是一個PLL, 它的input是硬體VSYNC的時間, 它的feedback是Present Fence的時間, 它的輸出就是SW_VSYNC, SW_VSYNC再根據SF與APP的phase offset做調整, 分別輸出VSYNC-sf與VSYNC-app.


當SF從HWComposer收到VSYNC時, 它會利用DispSync::addResyncSample將新的VSYNC時間交給DispSync. addResyncSample會決定是否還需要HW_VSYNC的輸入, 如果不需要, 就會將硬體VSYNC關掉.

     void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
         bool needsHwVsync = false;

         { // Scope for the lock
             Mutex::Autolock _l(mHWVsyncLock);
             if (type == 0 && mPrimaryHWVsyncEnabled) {
                 needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
             }
         }

         if (needsHwVsync) {
             enableHardwareVsync();
         } else {
             disableHardwareVsync(false);
         }
     }

在SurfaceFlinger::postComposition()裡, 會將Present Fence的時間透過addPresentFence交給DispSync, 這用來檢查SW_VSYNC是否需要校正, 如果需要, 就會將硬體VSYNC打開.
     void SurfaceFlinger::postComposition()
     {
         ....
         const HWComposer& hwc = getHwComposer();
         sp presentFence = hwc.getDisplayFence(HWC_DISPLAY_PRIMARY);

         if (presentFence->isValid()) {
             if (mPrimaryDispSync.addPresentFence(presentFence)) {
                 enableHardwareVsync();
             } else {
                 disableHardwareVsync(false);
             }
         }
         ...
     }

DispSync是利用最近的硬體VSYNC來做預測, 最少要3個, 最多是32個, 實際上要用幾個則不一定, DispSync拿到3個VSYNC後就會計算出SW_VSYNC, 只要收到的Present Fence沒有超過誤差, 硬體VSYNC就會關掉, 不然會繼續接收硬體VSYNC計算SW_VSYNC的值, 直到誤差小於threshold. 它計算的Model是這樣的 (DispSync::updateModelLocked)

  1. 計算目前收到VSYNC間隔, 取平均值 (AvgPeriod)
  2. 將每個收到的VSYNC時間與AvgPeriod算出誤差. (Delta = Time % AvgPeriod)
  3. 將Delta轉換成角度(DeltaPhase), 如果AvgPeriod是360度, DeltaPhase = 2*PI*Delta/AvgPeriod.
  4. 從DeltaPhase可以得到DeltaX與DeltaY (DeltaX = cos(DeltaPhase), DeltaY = sin(DeltaPhase))
  5. 將每個收到的VSYNC的DeltaX與DeltaY取平均, 可以得到AvgX與AvgY
  6. 利用atan與AvgX, AvgY可以得到平圴的phase (AvgPhase)
  7. AvgPeriod + AvgPhase就是SW_VSYNC.


當DispSync收到addPresentFence時 (最多記錄8個sample), 每一個fence的時間算出 (Time % AvgPeriod)的平方當作誤差, 將所有的Fence誤差加總起來如果大於某個Threshold, 就表示需要校正 (DispSync::updateErrorLocked). 校正的方法是呼叫DispSync::beginResync()將所有的VSYNC清掉, 等至少3個VSYNC再重新計算.










2015年1月23日 星期五

BufferQueue

Producer與Consumer是一個常見的design pattern, Producer負責產生資料, Consumer負責利用產生好的資料做進一步的處理. 很常見的一種case是video codec做bitstream的decode, decode完的資料利用3D(像是OpenGL或Direct3D)顯示出來, 這種use case在Windows與Android都可以看的到. BufferQueue是Android針對Producer/Consumer的一種Implementation. 在Android裡, 還有另一種是每個使用者都會看到的, 當電源打開時, 每個App, 甚至Status Bar或Navigation Bar都是透過BufferQueue將它所畫的東西(放在GraphicBuffer)傳送到SurfaceFlinger, SF再將它組合到使用者面前. Producer與Consumer可以在同一個process裡, 也可以在不同process裡, BQ這個設計基本可以同時使用在這兩個case裡, 而且Producer與Consumer並不需要知道對方是否與它是同一個process, 因為Android Binder搞定這件事.


Basic Use Model

BQ一開始的use model很直接, Producer利用dequeueBuffer從BQ要一塊GraphicBuffer, 資料寫入後, 利用queueBuffer將該Buffer放進BQ. Consumer需要資料時, 利用acquireBuffer從BQ取後一塊GraphicBuffer做處理, 處理完後, 利用releaseBuffer將該Buffer還給BQ.

更進一步想, Producer與Consumer兩個產生與消耗Buffer的速度不一樣, 而且Buffer的數量也是有限, 不可能需要就Allocate, 因此有些限制就出現. 因此當Producer產生資料的速度比較快時, 就會遇到全部的Buffer都寫完資料, 但是Consumer還沒消耗完任何一個, 正常的情況Producer必須等Consumer釋放出Buffer才能繼續做事. 如果是Consumer比較消耗速度比較快, Consumer就必須等Producer. 我們可以用下面的圖來說明BQ基本的use model.




每一個GraphicBuffer在BQ裡都有一個Slot用來儲存該Buffer的位置, 而每一個Slot都有一個state, state有以下4種
  • FREE
這個Slot是沒有在使用, Producer可以利用dequeueBuffer取得該Slot的GraphicBuffer, 一旦dequeueBuffer成功, 該Slot的狀態會變成DEQUEUED.
  • DEQUEUED
Slot裡面的Buffer已經交給Producer, 當Producer使用完後, 它會利用queueBuffer將該Buffer還給BQ, 而該Slot的狀態會變成QUEUED. 但是Producer也可以不使用這塊Buffer, 利用cancelBuffer將該Buffer還給BQ, 但該Slot的狀態會設成FREE.
  • QUEUED
在這個狀態下, BQ擁有這個Slot與Buffer, 只有Consumer可以利用acquireBuffer取得這個Slot與Buffer的所有權, 然後使用它. 當Consumer取得所有權後, 狀態會變成ACQUIRED.
  • ACQUIRED
Consumer用完這個Buffer後, 必須將它還給BQ, 稍後Producer才能再利用這塊Buffer寫入資料. Consumer可以利用releaseBuffer將它的所有權交還給BQ, 它的狀態也會變成FREE供Producer稍後使用.
 
 

Sync Model

Basic Sync Model的運作有一個問題, 也就是當Producer或Consumer將GraphicBuffer還給BQ時, 必須確保Buffer已經處理完了, 但是其實這會造成performance的問題. 以GLES(OpenGL ES)當作Producer為例, CPU下完GLES command後, 不代表GPU完成了所有的動作, 從CPU端, 我們可以利用glFinish或其它方式等GPU做完才利用queueBuffer將Buffer交給BQ, 但這其實是在浪費CPU的resource, 我們可以延後這個等待的動作, 延後到Consumer真的拿到這塊Buffer時再做. 對Consumer也是一樣, 當Consumer利用releaseBuffer將Buffer還給BQ時, 它不用等這塊Buffer真的沒在用, 它可以先還給BQ, 當Producer拿到Buffer時再等待Buffer真的沒有使用後再寫入新的資料. Sync Model利用fence來做到這個功能. 在sync model裡, 每一個Slot都有一個fence物件, 當一個Slot的狀態是QUEUED時, 這個fence可以稱為acquire fence, 當它的狀態是FREE時, 可稱為release fence. Acquire fence指的是當Consumer利用acquireBuffer取得Buffer時, 需要等待fence完成才能使用, 而release fence指的是當Producer利用dequeueBuffer取得Buffer時, 需要等待fence完成才能開始寫入資料.



Attach/Detach Model

在現今的BQ interface裡, 我們可以觀察到有多出幾個function, 分別是attachBuffer, detachBuffer, 這兩個function不論在Producer或Consumer都可以觀察到. attachBuffer會從BQ裡找到一個FREE的Slot, 並與Producer或Consumer提供的GraphicBuffer聯結. 這與一般的dequeueBuffer不同, 在dequeueBuffer時, 如果FREE的Slot本身沒有GraphicBuffer的話, dequeuBuffer會利用allocator去產生, 但是attachBuffer則是Producer/Consumer提供一個已經存在的Buffer, 它的目的是要將一個GraphicBuffer放入BQ裡, 這個GraphicBuffer可以是其它BQ所產生, 也可以是Producer/Consumer利用allocator產生的. AOSP裡有一個StreamSplitter, 將接收到的GraphicBuffer, 交給多個Consumer去使用. StreamSplitter本身是一個Consumer, 但同時也是一個Producer. 當Consumer被通知有新的Buffer放入BQ時, 它會做以下的事

    mInput->acquireBuffer(&acquiredBuffer);
    mInput->detachBuffer(acquiredBufer.mBuf);

    foreach producer in all downstream BQ {
        output->attachBuffer(&slot, acquiredBuffer.mGraphicBuffer);
        output->queueBuffer(slot)
    }
mInput就是StreamSplitter的Consumer端, 利用acquireBuffer取得可使用的Buffer後, 而利用detachBuffer將它從upstream BQ移除(Slot的狀態會從ACQUIRED變成FREE), upstream BQ就沒這個GraphicBuffer的存在. StreamSplitter再針對每個downstream BQ的producer端, 將取得的GraphicBuffer利用attachBuffer放入BQ裡的一個Slot, 這時候該Slot的狀態會從FREE變成ACQUIRED, 再利用queueBuffer放入downstream BQ裡. 利用attachBuffer/detachBuffer就可以簡單的將一個GraphicBuffer提供給多個BQ使用.

BQ Sample In SF


下面這張圖是SF裡BQ的使用情形. 在Android裡, 每一個ViewRootImpl代表的是一個Activity畫面,畫在一個GraphicBuffer裡, 這個GraphicBuffer並不是由ViewRootImpl直接管理的. 每一個ViewRootImpl都有一個WindowStateAnimator物件, 負責本身畫面的動畫, 而這個GraphicBuffer就是由WindowStateAnimator負責管理的. WindowStateAnimator會透過SurfaceComposerClient與SF溝通, 要求SF產生一個layer, 每個layer會產生一個BQ, SF端是Consumer, WindowStateAnimtor端則是Producer. 每一個ViewRootImpl都透過自已的BQ將畫完的GraphicBuffer交給SF.



Android的每個monitor都會有一個EGLSurface, 這個EGLSurface是一個GraphicBuffer的Producer, SF會記錄每個monitor要顯示的Layer, 將每一個monitor的Layer做Composition後(可以用GLES或overlay), 通過BQ交給FramebufferSurface, FramebufferSurface收到新的畫面, 再通知HWComposer顯示在使用者面前.


GraphicBuffer

GraphicBuffer用來儲存Producer與Consumer之間要交換的資料, 它是ANativeWindowBuffer的subclass, 記錄著由gralloc HAL傳回的handle. gralloc HAL負責從Graphic Memory裡分配一塊記憶體, 傳回的handle代表這塊memory. Handle的type是native_handle_t, 基本上這個type是很多class的base class.
    typedef struct
    {
        int version;
        int numFds;
        int numInts;
        int data[0];
    } native_handle_t;

ANativeWindowBuffer記錄Buffer基本的屬性, 像是width, height, stride, format等, 最重要的是它記錄著一個native_handle_t的pointer.

    typedef struct ANativeWindowBuffer
{
        struct android_native_base_t common;    // for reference counting

        int width;
        int height;
        int stride;
        int format;
        int usage;

        void* reserved[2];
    
        buffer_handle_t handle;    // alias of native_handle_t
        ...
    }

而GraphicBuffer則是繼承自ANativeWindowBuffer與RefBase的class, 它除了簡化ANativeWindowBuffer的使用外, 也提供了對gralloc HAL的interface, 像是alloc, free與lockXXX/unlockXXX等等.

GraphicBuffer用了兩個helper class來使用gralloc HAL, 分別是GraphicBufferAllocator與GraphicBufferMapper. Allocator負責使用HAL的alloc/free, Mapper則是使用lockXXX/unlockXXX.

BufferSlot

在BQ裡, 最多可以有64個Slot, 每個Slot有自已的State, 分別是FREE, QUEUED, DEQUEUED, ACQURED. 這些State是跟著Slot, 而不是GraphicBuffer. 當Consumer或Producer透過acquireBuffer或dequeueBuffer取得一個GraphicBuffer時, BQ也會提供一個Slot Index, 這個時候Slot會有相對應的GraphicBuffer, 當releaseBuffer, cancelBuffer或queueBuffer將Slot還給BQ時, GraphicBuffer也是跟著BufferSlot還給BQ. 但是利用attachBuffer/detachBuffer, 我們將跟Slot與GraphicBuffer的聯連移除. detachBuffer將GrahicBuffer從Slot中移除, Slot的狀態會變成FREE, 但下次這個Slot被dequeueBuffer選上時, 會再allocate一個GraphicBuffer. 被detach的GraphicBuffer可以繼續使用.


BufferQueue Creation

AOSP提供了一個Helper class來幫助Programmer寫Producer與Consumer, 在開始研究這些Helper Class之前, 我們可以先了解一下如何使用. BufferQueue::createBufferQueue是一個static function, 它會產生BufferQueueConsumer與BufferQueueProducer的object, 並以IGraphicBufferConsumer與IGraphicBufferProducer的type傳回. BufferQueueConsumer/Producer也是Helper class, 用來簡化Programmer的effort.

    sp producer;
    sp consumer;
    BufferQueue::createBufferQueue(&producer, &consumer, 
        new GraphicBufferAlloc());
    myConsumer c = new myConsumer(customer, ...);
    myProducer p = new myProducer(producer, ...);

要注意的是, BufferQueueConsumer/Producer的用意並不是要我們去寫它們兩個的subclass, 而是用它們. 對Consumer而言, AOSP還提供ConsumerBase與GLConsumer可以使用, 一般來講, Consumer會繼承自這個這兩個Class. GLConsumer是ConsumerBase的subclass, 它可以將acqure的GraphicBuffer當作GLES的texture使用.