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使用.






1 則留言: