2018年1月11日 星期四

從 BootAnimation 探索 SurfaceFlinger (四) 等這場戰鬥結束以後,我就要把Buffer畫出來了

在開始描述 BufferQueue 的行為之前, 有必要先了解幾個元素:

1.) BufferQueueDefs
namespace android {
    class BufferQueueCore;
    
    namespace BufferQueueDefs {
        // BufferQueue will keep track of at most this value of buffers.
        // Attempts at runtime to increase the number of buffers past this
        // will fail.
        enum { NUM_BUFFER_SLOTS = 64 };
    
        typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
    } // namespace BufferQueueDefs
} // namespace android

BufferQueueDefs 是一個 namespace, 用來保護 gui 資料夾下的 BufferSlot 定義
在 Surface.h 之中, 也有一個名為 BufferSlot 的結構, 但它只是單純的指向一塊 GraphicBuffer

BufferQueueDefs::BufferSlot 則稍微複雜一些, 後面我們會看到完整的結構
這裡將 BufferSlot 重新命名為 SlotsType
使用 SlotsType 宣告時表示這是一個有 64 個 BufferSlot 的 array
使用場景如下:
// BufferQueueCore.h
BufferQueueDefs::SlotsType mSlots; // 等同於 BufferSlot[64]

2.) BufferState
BufferState 是在 BufferSlot 裡面標記狀態的 enum
我們看一下它的描述:

enum BufferState {
    FREE = 0,
    /*
        FREE 表示這塊 buffer 可以被 producer dequeue,
        這塊 buffer 可能會被 consumer 在一段時間內繼續使用,
        因此這塊 buffer 一定不能被修改, 直到對應的 fence 被觸發.
        
        這個 slot 被 BufferQueue 所持有, 當呼叫 dequeueBuffer 後會轉換為 DEQUEUED    
    */

    DEQUEUED = 1,
    /*
        DEQUEUED 表示這塊 buffer 已經被 producer dequeued 了,
        而它還沒有被重新 queue 或者 cancel.
        Producer 在觸發 fence 之後就可以開始修改 buffer 內容.
        
        這個 slot 被 producer 所持有, 當呼叫 queueBuffer 後會轉為 QUEUED
        當呼叫 cancelBuffer 後會回到 FREE
    */

    QUEUED = 2,
    /*
        QUEUED 表示這塊 buffer 已經被 producer 所填充,
        並且 queue 進去通知 consumer 可以消費了.
        這塊 buffer 的內容可能會在一段時間內被修改,
        因此需要等到對應的 fence 觸發後才能開始存取.
        
        這個 slot 被 BufferQueue 所持有,
        當呼叫 acuquireBuffer 後會轉為 ACQUIRED,
        或者在另一塊 buffer 以非同步方式 queue 進後轉回 FREE 
    */

    ACQUIRED = 3
    /*
        ACQUIRED 表示這塊 buffer 被 consumer 獲取了.
        與其他人相同, 這塊 buffer 必須等到 fence 被觸發後才能存取
        
        這個 slot 是被 consumer 所持有,
        等到呼叫 releaseBuffer 後會轉回 FREE 狀態
    */
};

* 注意: 上述的 BufferState 標記的是 Slot 的狀態

我們可以看到敘述中出現了 buffer 跟 slot 這兩個對象.
而這裡的 buffer 指的是 GraphicBuffer.
slot 就好比是文件夾一樣, 而 buffer 就是放置在文件夾內的文件.

你可以想成是櫃子裏面有許多的文件夾,
這些文件夾可能有人使用了, 可能沒有人使用.
沒有人使用的文件夾, 就是 FREE.
有人拿走了文件夾想要寫入新的文件, 就是 DEQUEUED
他寫完文件後放回櫃子, 就是 QUEUED
有人想要讀取文件把它取走, 就是 ACQUIRED
slot 內一開始是沒有 buffer 的.
當 Producer 呼叫 dequeueBuffer 去獲取 Buffer 時,
BufferQueue 會尋找一塊 FREE 的 slot,
如果 slot 內部沒有 buffer 的話, 那麼 allocator 就會獲取一塊 GraphicBuffer 放進去.
一開始文件夾是空的,
所以你跟總務要了一疊紙
接著就可以開始寫了
使用 buffer 時必須跟一個叫做 fence 的傢伙進行連動.
當我們要執行繪圖的動作時, 會從 CPU 端下指令給 GPU
由於 Command 操作是 asynchronous 的形式, 並不能保證 GPU 已經完成了工作.
有一些阻塞的 API 可以呼叫, 例如 glFinish() 會等到 GPU 做完再返回
但這樣形同浪費  CPU 的 resource.
這個 fence 就是一個鎖的概念, GPU 使用完成之後, 會釋放 fence 使得上層可以存取, 反之亦然.
這使得 CPU 可以繼續執行, 等到真的要使用到 GraphicBuffer 時才去等待 GPU.

流程如下:
第一次呼叫 dequeueBuffer 時 GraphicBuffer 初次生成, 此時是沒有 fence 的.
填入 buffer 並 queueBuffer 時, 會生成一個 acquireFence, 因為 GPU 可能還沒有完成動作.
CPU queue 進以後, consumer 會被通知到有新的資料,
它藉由 acquireBuffer 來拿到這個 slot, 此時就要等待 acquireFence 觸發 (真的寫完了) 才能真的取出 buffer 來用.
反之, 當 releaseBuffer 時, 會生成一個 releaseFence.
下一次 producer 呼叫 dequeueBuffer 時, 就要等待這個 releaseFence 觸發 (真的讀完了) 後才能存取 buffer.
順帶一提, 這裡還有兩個操作跟整個 dequeue - queue 的行為比較脫鉤,
他們是 detachBuffer 跟 attachBuffer
detachBuffer 會將 GraphicBuffer 從 slot 之中取出
attachBuffer 則是把 GraphicBuffer 放進一個 slot 裡面去
就像是你可以從文件夾裡面把文件整個拿走
再放到另外一個文件夾之中
3.) BufferSlot
現在可以完整的看一下 BufferSlot 裡面究竟定義了哪些東西:

struct BufferSlot {
    // 指向這個 slot 所關聯到的 buffer, 初始值為 NULL
    sp<GraphicBuffer> mGraphicBuffer;

    // EGLDisplay 會使用它來創造 EGLSyncKHR 的物件, 進一步的產生 fence
    EGLDisplay mEglDisplay;

    // 取得這個 slot 的當前狀態名稱
    static const char* bufferStateName(BufferState state);

    // Slot 的當前狀態
    BufferState mBufferState;

    // 標註 producer 有沒有呼叫 requestBuffer, 主要用於 debug 
    bool mRequestBufferCalled;

    // 標註這個 slot 裡面有多少已經 queued 的 frame 數.
    // 這個變數通常用以讓 buffer 可以依照 LRU (Least Recently Used) 次序來 dequeue 
    uint64_t mFrameNumber;

    // 這是一個 EGL sync object,
    // 它必須在這個 buffer slot 關聯到的 buffer 被 dequeue 之前觸發
    // 初始值為 EGL_NO_SYNC_KHR, 並且當 releaseBuffer 時會生成一個新的 sync 物件
    // 有了 mFence 支援後捨棄不用 (This is deprecated in favor of mFence, below.)
    EGLSyncKHR mEglFence;
    
    // mFence 用來通知 "前一個使用 buffer 的人" 已經結束工作了
    // 1.) 當 buffer 現在在 FREE 狀態, 則會在前一個 consumer 完成讀取時觸發 fence,
    //     或者是在前一個 producer 已經寫了一些東西但是呼叫了 cancelBuffer 後觸發
    // 2.) 當 buffer 現在在 QUEUED 狀態, 則會在 producer 完成寫入後觸發 fence
    // 3.) 當 buffer 現在在 DEQUEUED 或者 ACQUIRED 狀態,
    //     此時會將 fence 轉交給 consumer 或者是 producer, 此時 mFence 設置為 NO_FENCE
    sp<Fence> mFence;

    // 標註這塊 buffer 已經被給 consumer 看見了沒
    // (在 BufferQueueConsumer::acquireBuffer 會修改到)
    bool mAcquireCalled;    

    // 標註在 Consumer 使用完畢後是否需要被清除 (cleaned up)
    // 如果呼叫 freeBuferLocked 時這塊 buffer 處於 ACQUIRED 的狀態, 就會舉起這個 flag
    // 使得 releaseBuffer 時回傳 STALE_BUFFER_SLOT (表示 consumer 剛剛清掉這塊 buffer)
    bool mNeedsCleanupOnRelease;
    
    // 標註這塊 buffer 是否已經 attach 給 consumer 了
    // 如果是的話, 則 dequeue 時會設置 BUFFER_NEEDS_REALLOCATION 這個 flag,
    // 以防止 producer 使用了前一塊 stale 的 cached buffer 
    bool mAttachedByConsumer;
}

大致上的變數都在這裡提到了, 接下來我們回到 BootAnimation 來看看實際上的操作.

在上一篇文章中, 我們生成了一個 EGLSurface 對象, 帶入了我們的 Surface 結構,
並且在 eglMakeCurrent 的時候呼叫 dequeueBuffer 將 GraphicBuffer 指給了了 EGLSurface 的 buffer 參數.
經過了一連串的 EGL 操作後, 最後會呼叫到 eglSwapBuffers
在 eglSwapBuffers 中, 將會執行到 nativeWindow->queueBuffer(nativeWindow, buffer, -1);
以及 nativeWindow->dequeueBuffer(nativeWindow, &buffer, &fenceFd)
就是把前一張畫好的 buffer 給 queue 進去, 並且再 dequeue 一張新的 buffer 出來使用.

調用流程如下:

eglSwapBuffers (EGL API)
  -> eglSwapBuffersWithDamageKHR
    -> eglSwapBuffers (OEM EGL library)
      -> nativeWindow->queueBuffer (Surface::hook_queueBuffer)
        -> queueBuffer (Surface)
          -> queueBuffer (MonitoredProducer)
            -> queueBuffer (BufferQueueProducer)
              -> frameAvailableListener->onFrameAvailable (Layer)
                -> mFlinger->signalLayerUpdate() (SurfaceFlinger)

休息一下, 下一篇要再度進入到 SurfaceFlinger 的世界了

沒有留言:

張貼留言

不定參數印 log

From the UNIXProcess_md.c #ifdef DEBUG_PROCESS   /* Debugging process code is difficult; where to write debug output? */ static void deb...