[Android禪修之路] 解讀SurfaceFlinger中的BufferQueue
theme: channing-cyan highlight: androidstudio
解讀BufferQueue
前言
在之前的文章中, 我們介紹了合成的過程中, 用到的生產者和消費者的設計模式, 並且還提到了一個緩衝區, 這篇文章, 就來詳細說明一下這個緩衝區, 究竟是一個什麼東西。
首先,我們先看官方提供的說明圖。
眾所周知, SurfaceFlinger 使用了生產者和消費者模型, 那麼這個生產者和消費者之間的資料是如何封裝和傳遞的呢? 答案就是 BufferQueue , 接下來我們就來聊聊什麼是 BufferQueue 和它究竟有什麼作用,最後再研究一下這個生產者和消費者模型是如何運作的。
首先我們看生產者的緩衝區是如何提供出來的, 之前在SurfaceFlinger 合成中的工作我們看到了獲取緩衝區的邏輯, 是呼叫 Surface.cpp 中的GraphicBufferProducer 的 dequeueBuffer, 而這裡的 GraphicBufferProducer 其實是一個 BpGraphicBufferProducer, 它用到了 Binder 進行程序通訊, 今天我們就來看看它的具體實現
一 BufferQueue是什麼
首先,我們看看這個 BufferQueue 是什麼。當然,要看它是什麼,最先看的當然是看這個類的定義和官方提供的註釋。
```cpp class BufferQueue { public: // Buffer 的最大繫結數 enum { NUM_BUFFER_SLOTS = BufferQueueDefs::NUM_BUFFER_SLOTS };
// 當 Slot 沒有和繫結時的列舉值
enum { INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT };
// IGraphicBufferConsumer.h 標頭檔案中的別名
enum {
NO_BUFFER_AVAILABLE = IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
PRESENT_LATER = IGraphicBufferConsumer::PRESENT_LATER,
};
// 在非同步模式下,保留兩個 Slot 以保證生產者和消費者可以非同步執行。
enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };
typedef ::android::ConsumerListener ConsumerListener;
// 一個 ConsumerListener 的實現, 它保持了一個 ConsumerListener 的弱引用
// 它可用將呼叫轉發到消費者物件
// 使用它可用避免 BufferQueue 和生產者 Consumer 的迴圈引用
class ProxyConsumerListener : public BnConsumerListener {
public:
explicit ProxyConsumerListener(const wp<ConsumerListener>& consumerListener);
~ProxyConsumerListener() override;
void onDisconnect() override;
void onFrameAvailable(const BufferItem& item) override;
void onFrameReplaced(const BufferItem& item) override;
void onBuffersReleased() override;
void onSidebandStreamChanged() override;
void addAndGetFrameTimestamps(
const NewFrameEventsEntry* newTimestamps,
FrameEventHistoryDelta* outDelta) override;
private:
// IConsumerListener 的弱引用
wp<ConsumerListener> mConsumerListener;
};
// 生產者和消費者使用的 Slot 是由 BufferQueue 進行管理的, 而緩衝區的分配則是由 allocator 進行的
static void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer,
bool consumerIsSurfaceFlinger = false);
}; ```
BufferQueue 這個類比較簡單,不過還是先介紹一下這裡及以後出現的一些名詞或者概念。
- NUM_BUFFER_SLOTS:BufferSlot 陣列的最大長度,數量是32。(BufferSlot 對應的翻譯統一為緩衝槽)
- INVALID_BUFFER_SLOT:BufferSlot 繫結狀態的列舉值,預設是 INVALID_BUFFER_SLOT,也就是沒有對應的緩衝槽。
- ConsumerListener:消費者的監聽器,它用於將呼叫轉發給消費者。
因為 BufferQueue 中只有一個 createBufferQueue 函式,所以接下來我們就來看一下 createBufferQueue 函式的實現。
1.1 createBufferQueue
```cpp
void BufferQueue::createBufferQueue(sp
sp<BufferQueueCore> core(new BufferQueueCore());
sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core, consumerIsSurfaceFlinger));
sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
*outProducer = producer;
*outConsumer = consumer;
} ```
這個函式也是非常的簡單, 它就建立了三個物件
- BufferQueueCore: 這個就是 BufferQueue 中真正工作的物件
- BufferQueueProducer: 生產者物件
- BufferQueueConsumer: 消費者物件
1.2 BufferQueueCore
如果我們看過生產者 BufferQueueProducer::dequeueBuffer 中的程式碼, 我們就會見到 mCore 這個物件, 它就是 BufferQueueCore
對於 BufferQueueCore 的定義,這裡就不放程式碼了,不過 BufferQueueCore 中比較重要的一處就是它裡面維護了4個數組,現在我們就來看一下 BufferQueueCore 中儲存的這些 buffer 陣列物件, 還有它們的作用
1.3 BufferQueueCore 維護的陣列
BufferQueueCore中維護了4個數組, 這些陣列儲存的物件是 BufferSlot, 它們分別如下
cpp
// mFreeSlots 包含所有 Free 狀態的且當前沒有繫結 buffer 的 slots
std::set<int> mFreeSlots;
// mFreeBuffers 包含所有 Free 狀態的且當前綁定了 buffer 的 slots
std::list<int> mFreeBuffers;
// mUnusedSlots 包含所有 Free 狀態且沒有繫結 buffer 且未使用的 slots.
std::list<int> mUnusedSlots;
// mActiveBuffers 包含了當前所有綁定了非 Free 狀態 buffer 的 slots
std::set<int> mActiveBuffers;
這裡我們只是簡單列舉出這4個數組,後續用到它們的時候,我們在進一步說明。接下來我們來看看和這些陣列對應的緩衝槽 BufferSlot 物件。
二 BufferSlot
BufferSlot 這個物件的結構體比較短, 這裡就全部列舉出來了
```cpp struct BufferSlot {
BufferSlot()
: mGraphicBuffer(nullptr), // 一個緩衝槽對應一個圖形緩衝區 GraphicBuffer
mEglDisplay(EGL_NO_DISPLAY),
mBufferState(),
mRequestBufferCalled(false),
mFrameNumber(0),
mEglFence(EGL_NO_SYNC_KHR),
mFence(Fence::NO_FENCE),
mAcquireCalled(false),
mNeedsReallocation(false) {
}
// mGraphicBuffer 指向為此 Slot 分配的 GraphicBuffer, 如果沒有分配則為 NULL
sp<GraphicBuffer> mGraphicBuffer;
// EGLDisplay , 用於建立 EGLSyncKHR
EGLDisplay mEglDisplay;
// 當前 buffer slot 的狀態
BufferState mBufferState;
// 告知 procducer 是否被呼叫 requestBuffer
// 用於 debug 和查詢bug
bool mRequestBufferCalled;
// mFrameNumber 是此 slot 的 Frame 佇列編號, 這是用於 buffer 佇列的 LRU排序
// 因為 buffer 可能這發出 release fence 訊號之前 release
uint64_t mFrameNumber;
// mEglFence是EGL sync物件,它關聯的 slot 必須在 dequeue 之前發出訊號
// 在建立 buffer 的時候它會被初始化為 EGL_NO_SYNC_KHR , 並且它可以設定為 releaseBuffer 中的新同步物件
EGLSyncKHR mEglFence;
// mFence 說一個圍欄, 當之前的 buffer 的所有者完成啟動工作時, 它就會發出訊號
// 當這個 buffer 是 Free 狀態時, 這個圍欄會指示消費者何時完成 buffer 的讀取工作
// 或者指示當生產者何時已經完成寫入, 如果它在寫入資料資料後呼叫了 cancelBuffer
// 當這個 buffer 是 QUEUED 狀態時, 它指示生產者何時完成 buffer 的填充
// 當這個 buffer 是 DEQUEUED/ACQUIRED 時, 這個圍欄會和 buffer 一起傳遞給生產者或者消費者,並且設定為 NO_FENCE
sp<Fence> mFence;
// 指示消費者是否看到了此緩衝區
bool mAcquireCalled;
// Indicates whether the buffer was re-allocated without notifying the producer.
If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when dequeued to prevent the producer from using a stale cached buffer.
如果是這樣,它需要在退出佇列時設定緩衝區需要重新分配標誌,以防止生產者使用過時的快取緩衝區。
// 指示是否在沒有通知生產者的情況下重新分配了 buffer
// 如果是的, 那麼當這個 buffer 出隊時它需要設定為 BUFFER_NEEDS_REALLOCATION , 以防止生產者使用了一個過時的 buffer
bool mNeedsReallocation;
}; ```
BufferSlot 這個類裡面定義的東西雖然比較多,但是都很容易理解,這裡也不需要全部記住,如果後續遇到,再回過頭重新檢視即可。唯獨有一個需要說明的是這裡面和 Fence 相關物件,
Fence 是 Android 的圖形系統中,用於處理緩衝區同步用的,之前已經說過了,Android 的圖形系統中,用到了生產者和消費者模型,所以必定涉及到生產者和消費者之前的同步,並且因為這裡的生產者和消費者模式不在同一個程序,甚至涉及到 CPU 和 GPU 的同步,所以弄了一套 Fence 機制,關於 Fence 機制,這裡就不多介紹了,詳細的可以看 [解讀Fence]-(未完成) 。這裡我們假設已經瞭解了 Fence 機制,當然不瞭解也沒有關係,看完本篇文章之後,再去了解 Fence 機制,然後再回來驗證一遍,會更加理解 BufferQueue 的工作原理。
這裡我們先只關注裡面的 BufferState 這個物件。
2.1 BufferState
首先, 我們先看 BufferState 的結構體定義, 裡面的方法比較簡單, 主要就是定義了5個狀態值。
```cpp // BufferState 是跟蹤緩衝區 slot 執行的狀態。 struct BufferState {
// 所有的 slot 最初都是 Free 狀態
BufferState()
: mDequeueCount(0),
mQueueCount(0),
mAcquireCount(0),
mShared(false) {
}
uint32_t mDequeueCount;
uint32_t mQueueCount;
uint32_t mAcquireCount;
bool mShared;
// 一個緩衝區可用處於5種狀態, 它們分別是
//
// | mShared | mDequeueCount | mQueueCount | mAcquireCount |
// --------|---------|---------------|-------------|---------------|
// FREE | false | 0 | 0 | 0 |
// DEQUEUED| false | 1 | 0 | 0 |
// QUEUED | false | 0 | 1 | 0 |
// ACQUIRED| false | 0 | 0 | 1 |
// SHARED | true | any | any | any |
// FREE : 標識這個緩衝區是可以出隊給生產者使用, 此緩衝區對應的 slot 屬於 BufferQueue ,
// 當它被呼叫 dequeueBuffer 時, 狀態會轉變為 DEQUEUED
// DEQUEUED : 表示此緩衝區已經通過生產者出隊, 但是還沒有入隊或者取消, 一旦發出相關釋放圍欄的訊號,生產者就可以修改緩衝區的內容。
// 此緩衝區對應的 slot 所有者是生產者, 當呼叫 queueBuffer/attachBuffer 時狀態就會變成 QUEUED ,
// 當呼叫 cancelBuffer/detachBuffer 時,狀態就會變成 FREE
// QUEUED : 表示緩衝區已經被生產者填充, 可以入隊提供給消費者使用
// 緩衝區的內容可能會繼續改變, 所以這相關的 fence 訊號發出前,此緩衝區對應 slot 的擁有者是 BufferQueue
// 當呼叫 acquireBuffer 時狀態會轉變為 ACQUIRED ,
// 或者在非同步模式下另一個 buffer 入隊時, 此 buffer 的狀態會轉變為 FREE
// ACQUIRED : 表示這個 buffer 已經被消費者獲得, 但是和 QUEUED 狀態一樣,在獲取 fence 訊號之前,消費者不得訪問內容。
// 這個緩衝區對應的 slot 的所有者是消費者, 在呼叫 releaseBuffer/detachBuffer 時會轉變為 FREE
// 一個獨立的緩衝區也可以通過呼叫 attachBuffer 進入 ACQUIRED 狀態
// SHARED : 表示此緩衝區處於共享模式中, 它可以和其他的幾種狀態組合(但是不能和FREE狀態組合),
// 它也可以多次的入隊/出隊/獲取
inline bool isFree() const {
return !isAcquired() && !isDequeued() && !isQueued();
}
inline bool isDequeued() const {
return mDequeueCount > 0;
}
inline bool isQueued() const {
return mQueueCount > 0;
}
inline bool isAcquired() const {
return mAcquireCount > 0;
}
inline bool isShared() const {
return mShared;
}
inline void reset() {
*this = BufferState();
}
const char* string() const;
inline void dequeue() {
mDequeueCount++;
}
inline void detachProducer() {
if (mDequeueCount > 0) {
mDequeueCount--;
}
}
inline void attachProducer() {
mDequeueCount++;
}
inline void queue() {
if (mDequeueCount > 0) {
mDequeueCount--;
}
mQueueCount++;
}
inline void cancel() {
if (mDequeueCount > 0) {
mDequeueCount--;
}
}
inline void freeQueued() {
if (mQueueCount > 0) {
mQueueCount--;
}
}
inline void acquire() {
if (mQueueCount > 0) {
mQueueCount--;
}
mAcquireCount++;
}
inline void acquireNotInQueue() {
mAcquireCount++;
}
inline void release() {
if (mAcquireCount > 0) {
mAcquireCount--;
}
}
inline void detachConsumer() {
if (mAcquireCount > 0) {
mAcquireCount--;
}
}
inline void attachConsumer() {
mAcquireCount++;
}
}; ```
對照著Google官方提供的 BufferQueue 緩衝區的流轉圖,接下來簡單說明一下一個 BufferSlot 的一生
- BufferSlot 的出生, 每個 BufferSlot 出生時都是 FREE 狀態, 它的所有者是 BufferQueue
- 生產者呼叫 dequeue , BufferSlot 出隊, 此時 BufferSlot 的狀態轉變為 DEQUEUED , 它的所有者是生產者
- DEQUEUED 狀態下, 生產者會將內容填充到 BufferSlot 對應的緩衝區中
- 生產者也可以呼叫 cancelBuffer/detachBuffer , 之後緩衝區的狀態就會轉變為 FREE
- 生產者填充完資料後,呼叫 queue 將 BufferSlot 入隊, 此時 BufferSlot 的狀態轉變為 QUEUED , BufferSlot 的所有者為 BufferQueue
- 消費者呼叫 acquire 獲取 BufferQueue 中的 BufferSlot, 此時 BufferSlot 的狀態轉變為 ACQUIRED , BufferSlot 的所有者為消費者
- 消費者讀取完資料後,呼叫 release 釋放 BufferSlot, 之後 BufferSlot 的狀態又會重新變成 FREE , BufferSlot 的所有者為 BufferQueue
注意:這裡所有的緩衝區流轉,我們只說了所有權,在圖形系統中,緩衝區除了所有權,還有一個使用權。我們可以把他理解為一個圖書館,不同的人來借書,借書的人有使用權,但是沒有書的所有權。
在理解了這些概念之後,我們順著生產者和消費者模型的順序,看一遍 BufferQueue 的工作流程
三 dequeueBuffer
首先從生產者向 BufferQueue 申請 BufferSlot 開始,這個函式是 BufferQueueProducer::dequeueBuffer。因為這個函式較長,所以我們將它拆成幾個部分看。
3.1 dequeueBuffer 的第一部分
```cpp
status_t BufferQueueProducer::dequeueBuffer(int outSlot, sp
{
std::lock_guard<std::mutex> lock(mCore->mMutex);
mConsumerName = mCore->mConsumerName;
// 首先判斷緩衝區的狀態是否棄用,mIsAbandoned 初始化的值為 false
// 在 consumerDisconnect 中它會被修改為 true
if (mCore->mIsAbandoned) {
return NO_INIT;
}
// 接著判斷緩衝區是否和 Surface 建立了連線, 如果沒有也直接返回
if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
return NO_INIT;
}
}
// 判斷生產者需要的寬高是否合法,要麼全為0,要麼全不為0
if ((width && !height) || (!width && height)) {
return BAD_VALUE;
}
// 設定引數的預設值
status_t returnFlags = NO_ERROR;
EGLDisplay eglDisplay = EGL_NO_DISPLAY;
EGLSyncKHR eglFence = EGL_NO_SYNC_KHR;
bool attachedByConsumer = false;
{
std::unique_lock<std::mutex> lock(mCore->mMutex);
// 如果當前沒有空閒的 Buffer , 但是當前正在分配, 則等待分配完成, 而不去重複分配
if (mCore->mFreeBuffers.empty() && mCore->mIsAllocating) {
mDequeueWaitingForAllocation = true;
mCore->waitWhileAllocatingLocked(lock);
mDequeueWaitingForAllocation = false;
mDequeueWaitingForAllocationCondition.notify_all();
}
if (format == 0) {
format = mCore->mDefaultBufferFormat;
}
// 開啟消費者請求的標誌
usage |= mCore->mConsumerUsageBits;
//如果寬和高都是0, 則使用預設的寬高
const bool useDefaultSize = !width && !height;
if (useDefaultSize) {
width = mCore->mDefaultWidth;
height = mCore->mDefaultHeight;
}
int found = BufferItem::INVALID_BUFFER_SLOT;
....
} ```
注意,這裡系統只是判斷了 mFreeBuffer 陣列,如果 mFreeBuffer 陣列為空,並且 mIsAllocating 為 true,就會呼叫 waitWhileAllocatingLocked 陷入等待。
SurfaceFlinger 中使用的緩衝區是有數量限制的,如果當前處於正在分配緩衝區的狀態,就會導致阻塞。而 mIsAllocating 表示是否正在分配,如果是正在分配,則不會重複進行分配,而是進入阻塞。
接下來我們看第二部分的程式碼。
3.2 dequeueBuffer 的第二部分
```cpp
status_t BufferQueueProducer::dequeueBuffer(int outSlot, sp
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
// 正常情況是不能執行到此處的程式碼
return -EBUSY;
}
// 注意,將找到的 BufferSlot 對應的圖形緩衝區賦值給了 buffer
// 我們之前在介紹 BufferSlot 時說過,一個 BufferSlot 對應一個圖形緩衝區(GraphicBuffer)
// 但是並沒有說這個緩衝區現在已經分配好了,也就是說這個緩衝區還可能並沒有真正分配
const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
// 如果我們現在不允許分配新的緩衝區
if (!mCore->mAllowAllocation) {
// buffer 又需要一個緩衝區
if (buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
// 如果是共享緩衝區模式就直接返回了,一般不是此模式
if (mCore->mSharedBufferSlot == found) {
return BAD_VALUE;
}
// 找到的 found 沒有緩衝區,但是現在又要緩衝區,那咋辦嘛,重新找唄
// 只好把 found 放回去,再重新找吧。注意,這裡放進的是 mFreeSlots 陣列
mCore->mFreeSlots.insert(found);
mCore->clearBufferSlotLocked(found);
found = BufferItem::INVALID_BUFFER_SLOT;
continue;
}
}
}
// 執行完 while 迴圈,那麼 found 要麼已經有了緩衝區,要麼是可以分配緩衝區
// 還是老樣子,不管有沒有,先放進去
// 把 while 中找到的 mGraphicBuffer 的地址賦值給 buffer
const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
if (mCore->mSharedBufferSlot == found &&
buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
// 還是先判斷共享緩衝區模式
return BAD_VALUE;
}
// 現在找到的這個 found 肯定是我們要用的了,先放到 mActiveBuffers 陣列
// [1.3]中介紹了4個數組分別存放什麼型別的 BufferSlot
if (mCore->mSharedBufferSlot != found) {
mCore->mActiveBuffers.insert(found);
}
// 將找到的結果儲存
*outSlot = found;
} ```
第二部分主要就是 mSlots 的查詢過程,這部分程式碼已經加了詳細的註釋。查詢過程就是通過呼叫 waitForFreeSlotThenRelock 實現的,這個查詢的具體過程我們後面再看。先看 dequeueBuffer 的第三部分的程式碼
3.3 dequeueBuffer 的第三部分
```cpp ... *outSlot = found;
attachedByConsumer = mSlots[found].mNeedsReallocation;
mSlots[found].mNeedsReallocation = false;
// 修改找到 Buffer 的狀態,還記得 Google 的官方圖嗎
mSlots[found].mBufferState.dequeue();
// 現在看這個 buffer 是已經有了,還是沒有需要重新分配
if ((buffer == nullptr) ||
buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))
{
// 如果是沒有,需要重新分配
mSlots[found].mAcquireCalled = false;
mSlots[found].mGraphicBuffer = nullptr;
mSlots[found].mRequestBufferCalled = false;
mSlots[found].mEglDisplay = EGL_NO_DISPLAY;
mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
mSlots[found].mFence = Fence::NO_FENCE;
mCore->mBufferAge = 0;
mCore->mIsAllocating = true;
// returnFlags 標誌開啟需要重新分配的開關
returnFlags |= BUFFER_NEEDS_REALLOCATION;
} else {
// 設定 mBufferAge,這將是該緩衝區排隊時的幀編號
mCore->mBufferAge = mCore->mFrameCounter + 1 - mSlots[found].mFrameNumber;
}
eglDisplay = mSlots[found].mEglDisplay;
eglFence = mSlots[found].mEglFence;
// 不要在共享緩衝區模式下返回 Fence,第一幀除外
// 在查詢 BufferSlot 的時候,還會通過返回引數拿到一個 Fence,
// 這個 Fence 決定了這個 BufferSlot 的上一任主人有沒有處理完事情,也就是說當前的主人有沒有使用權
*outFence = (mCore->mSharedBufferMode &&
mCore->mSharedBufferSlot == found) ?
Fence::NO_FENCE : mSlots[found].mFence;
mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
mSlots[found].mFence = Fence::NO_FENCE;
// 如果啟用了共享緩衝區模式,快取的第一個出隊的slot,標記為共享緩衝區
if (mCore->mSharedBufferMode && mCore->mSharedBufferSlot ==
BufferQueueCore::INVALID_BUFFER_SLOT) {
mCore->mSharedBufferSlot = found;
mSlots[found].mBufferState.mShared = true;
}
} // Autolock scope ```
第三部分是查詢到了 mSlots 之後,對 mSlots 狀態的更新。如果查詢到的 BufferSlot 需要重新分配緩衝區,那麼就會初始化 BufferSlot 的成員變數
3.4 dequeueBuffer 的第四部分
```cpp
// 如果 BUFFER_NEEDS_REALLOCATION 的標誌開啟,就建立一個新的 GraphicBuffer 物件
if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
sp
status_t error = graphicBuffer->initCheck();
{
std::lock_guard<std::mutex> lock(mCore->mMutex);
if (error == NO_ERROR && !mCore->mIsAbandoned) {
graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
// 將建立的緩衝區放進 BufferSlot
mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
}
mCore->mIsAllocating = false;
// 還記得第一部分的阻塞嗎,如果正在分配,就阻塞,這裡進行喚醒操作
mCore->mIsAllocatingCondition.notify_all();
if (error != NO_ERROR) {
mCore->mFreeSlots.insert(*outSlot);
mCore->clearBufferSlotLocked(*outSlot);
return error;
}
if (mCore->mIsAbandoned) {
mCore->mFreeSlots.insert(*outSlot);
mCore->clearBufferSlotLocked(*outSlot);
return NO_INIT;
}
}
}
if (attachedByConsumer) { returnFlags |= BUFFER_NEEDS_REALLOCATION; }
if (eglFence != EGL_NO_SYNC_KHR) { EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, 0, 1000000000); // 如果出現問題,記錄錯誤,但返回緩衝區而不同步訪問它。 此時中止出隊操作為時已晚。 eglDestroySyncKHR(eglDisplay, eglFence); }
if (outBufferAge) { // 返回幀編號 *outBufferAge = mCore->mBufferAge; } // addAndGetFrameTimestamps(nullptr, outTimestamps);
return returnFlags; ```
第四部分的程式碼也比較簡單,唯一需要注意的就是 GraphicBuffer 的建立和記憶體分配。不過 GraphicBuffer 的記憶體分配其實是一個非常複雜的過程,這裡就不深入了,詳細的可以看[解讀GraphicBuffer] todo
接下來,就來看看 BufferSlot 的查詢過程 waitForFreeSlotThenRelock
四 BufferSlot 的查詢
4.1 waitForFreeSlotThenRelock
waitForFreeSlotThenRelock 這個函式比較簡單, 它主要通過兩個函式來獲取緩衝區
- getFreeBufferLocked: 它是從 FreeBuffer 陣列中獲取緩衝區
- getFreeSlotLocked: 它是從 FreeSlot 陣列中獲取緩衝區
```cpp
status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
std::unique_lock
int dequeuedCount = 0;
int acquiredCount = 0;
// 統計 ActiveBuffers 中 DEQUEUED 和 ACQUIRED 的 BufferSlot 的數量
for (int s : mCore->mActiveBuffers) {
if (mSlots[s].mBufferState.isDequeued()) {
++dequeuedCount;
}
if (mSlots[s].mBufferState.isAcquired()) {
++acquiredCount;
}
}
// 當緩衝區入隊時,檢查生產者的 Dequeue 的 BufferSlot 數量是否超過了 mMaxDequeuedBufferCount
if (mCore->mBufferHasBeenQueued &&
dequeuedCount >= mCore->mMaxDequeuedBufferCount) {
return INVALID_OPERATION;
}
// 上面都是統計和一些引數判斷,現在才真正開始查詢
// 還是老樣子,初始化一個值
*found = BufferQueueCore::INVALID_BUFFER_SLOT;
// 如果我們快速斷開和重新連線,我們可能會處於一種狀態,即slot是空的,
// 但佇列中有很多緩衝區。這可能導致我們在超過使用者時耗盡記憶體。如果看起來有太多緩衝區在排隊,則等待
// 先拿到最大的 Buffer 數量,一般是雙緩衝的2,或者三緩衝的3
const int maxBufferCount = mCore->getMaxBufferCountLocked();
// 如果入隊的 BufferSlot 太多,就會 tooManyBuffers
bool tooManyBuffers = mCore->mQueue.size()
> static_cast<size_t>(maxBufferCount);
if (tooManyBuffers) {
} else {
// 如果是共享緩衝區模式並且存在共享緩衝區,則使用共享緩衝區
if (mCore->mSharedBufferMode && mCore->mSharedBufferSlot !=
BufferQueueCore::INVALID_BUFFER_SLOT) {
*found = mCore->mSharedBufferSlot;
} else {
if (caller == FreeSlotCaller::Dequeue) {
// 如果呼叫的是出隊 Dequeue , 則優先使用 FreeBuffer
// 因為 FreeBuffer 中的 Slot 已經和 buffer 進行過繫結
// 這樣就不需要重新分配 buffer
int slot = getFreeBufferLocked();
if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
*found = slot;
} else if (mCore->mAllowAllocation) {
*found = getFreeSlotLocked();
}
} else {
// 如果呼叫的是 attach , 則優先使用 FreeSlot
int slot = getFreeSlotLocked();
if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
*found = slot;
} else {
*found = getFreeBufferLocked();
}
}
}
}
// 如果沒有找到緩衝區, 或者是 tooManyBuffers, 就再試一次
tryAgain = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) ||
tooManyBuffers;
if (tryAgain) {
if ((mCore->mDequeueBufferCannotBlock || mCore->mAsyncMode) &&
(acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
// 如果是非阻塞模式則直接返回錯誤(生產者和消費者由應用程式控制)
return WOULD_BLOCK;
}
// 等待鎖, 如果超時就返回, 如果沒有超時, 就重試
if (mDequeueTimeout >= 0) {
std::cv_status result = mCore->mDequeueCondition.wait_for(lock,
std::chrono::nanoseconds(mDequeueTimeout));
if (result == std::cv_status::timeout) {
return TIMED_OUT;
}
} else {
// 陷入等待,等待喚醒
mCore->mDequeueCondition.wait(lock);
}
}
} // while (tryAgain)
return NO_ERROR;
} ```
雖然 waitForFreeSlotThenRelock 呼叫的是 BufferQueueProducer , 但是真正的實現是 BufferQueueCore。具體的查詢過程可以分為以下幾步
- dequeueBuffer() 優先從 mFreeBuffers 中獲取一個 BufferSlot。
- 如果 mFreeBuffers 為空,則從 mFreeSlots 中獲取 BufferSlot,併為它分配一塊指定大小的 buffer。
- 將其對應的 BufferSlot 狀態從 FREE 修改為 DEQUEUED,然後將該 slot 從 mFreeSlots 遷移到 mActiveBuffers 中。
- 將獲取到的 slot 作為出參返回給呼叫者。如果該 BufferSlot 繫結的 GraphicBuffer 是重新分配的,則返回值為 BUFFER_NEEDS_REALLOCATION,否則為 NO_ERROR
當我們使用 dequeueBuffer 獲取 BufferSlot 的時候,會優先從 FreeBuffer 陣列中獲取緩衝區,因為它裡面是 Slot 已經和 buffer 進行了繫結,這樣我們就可以省去 buffer 的申請和繫結的過程,當然,如果 FreeBuffer 陣列為空,或者沒有可用的 buffer,那麼就會從 FreeSlot 陣列中申請,這種情況下,還會進行一個 GraphicBuffer 的申請和繫結的過程,這些邏輯在 dequeueBuffer 已經看到了。
另外,在 waitForFreeSlotThenRelock 中,我們還看到一種情況,就是 tooManyBuffers,那麼什麼情況下回出現 tooManyBuffers 呢?就拿比較常用的三緩衝來說吧,一個緩衝區用來給生產者寫,一個緩衝區用來給消費者顯示,還有一個緩衝區用來給 SurfaceFlinger 合成。那麼如果這時候生產者使勁的申請緩衝區寫內容,並且將寫好的 GraphicBuffer 放進 BufferQueue,而消費者根本來不及消費,就會導致 tooManyBuffers 出現了。
當然,正常情況一般是生產者生成內容不及時,繪製時間過長,導致 GraphicBuffer 沒有及時傳遞給 BufferQueue,導致顯示丟幀,但是看系統原始碼我們發現,生產者生成過慢不行,生成過快同樣也是不行的。
4.2 getFreeBufferLocked和getFreeSlotLocked
在 waitForFreeSlotThenRelock 中,我們還看到了 getFreeBufferLocked 和 getFreeSlotLocked 這兩個函式。不過它們都比較簡單, 這裡就不過多介紹了
```cpp int BufferQueueProducer::getFreeBufferLocked() const { if (mCore->mFreeBuffers.empty()) { return BufferQueueCore::INVALID_BUFFER_SLOT; } int slot = mCore->mFreeBuffers.front(); mCore->mFreeBuffers.pop_front(); return slot; }
int BufferQueueProducer::getFreeSlotLocked() const { if (mCore->mFreeSlots.empty()) { return BufferQueueCore::INVALID_BUFFER_SLOT; } int slot = *(mCore->mFreeSlots.begin()); mCore->mFreeSlots.erase(slot); return slot; } ```
到這裡,dequeueBuffer 的基本流程就已經介紹完了,至於生產者拿到了 BufferSlot 做了哪些,並不是本文的重點,接下來我們再看看當生產者完成生產之後,BufferSlot 的入隊操作。
五 queueBuffer
queueBuffer 是生產者完成了生產之後,將 BufferSlot 轉交給 BufferQueue 的過程。這個函式我們也它拆成幾個部分看。
5.1 queueBuffer 的第一部分
```cpp status_t BufferQueueProducer::queueBuffer(int slot, const QueueBufferInput &input, QueueBufferOutput *output) {
int64_t requestedPresentTimestamp;
bool isAutoTimestamp;
android_dataspace dataSpace;
Rect crop(Rect::EMPTY_RECT);
int scalingMode;
uint32_t transform;
uint32_t stickyTransform;
sp<Fence> acquireFence;
bool getFrameTimestamps = false;
// input 是輸入引數,這裡將 input 中的變數取出,儲存到 requestedPresentTimestamp 等等的傳入引數中
input.deflate(&requestedPresentTimestamp, &isAutoTimestamp, &dataSpace,
&crop, &scalingMode, &transform, &acquireFence, &stickyTransform,
&getFrameTimestamps);
const Region& surfaceDamage = input.getSurfaceDamage();
const HdrMetadata& hdrMetadata = input.getHdrMetadata();
if (acquireFence == nullptr) {
return BAD_VALUE;
}
auto acquireFenceTime = std::make_shared<FenceTime>(acquireFence);
// 縮放模式,就是當 Buffer 內容的大小和螢幕的代銷不符的時候,如何處理,如果沒有設定則直接返回
switch (scalingMode) {
case NATIVE_WINDOW_SCALING_MODE_FREEZE:
case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
break;
default:
return BAD_VALUE;
}
// 定義了一些變數
sp<IConsumerListener> frameAvailableListener;
sp<IConsumerListener> frameReplacedListener;
int callbackTicket = 0;
uint64_t currentFrameNumber = 0;
// 定義一個 BufferItem
BufferItem item;
} ```
第一部分的程式碼也比較簡單
- 定義一系列變數準備給後面用
- input 是一個 QueueBufferInput 結構體,裡面封裝了 BufferSlot 相關的一系列變數,這裡將 input 中的變數取出來
- scalingMode 是 BufferSlot 的縮放模式,就是當 Buffer 內容的大小和螢幕的代銷不符的時候,如何處理,這個需要設定,如果沒有設定會直接返回 BAD_VALUE
- 定義了一個 BufferItem 物件
5.2 queueBuffer 的第二部分
```cpp
status_t BufferQueueProducer::queueBuffer(int slot,
const QueueBufferInput &input, QueueBufferOutput *output) {
BufferItem item;
{
std::lock_guard
// 如果剛剛啟用了共享緩衝區模式,緩衝區的第一個緩衝槽已入隊,就將其標記為共享緩衝區
if (mCore->mSharedBufferMode && mCore->mSharedBufferSlot ==
BufferQueueCore::INVALID_BUFFER_SLOT) {
mCore->mSharedBufferSlot = slot;
mSlots[slot].mBufferState.mShared = true;
}
// 將 BufferSlot 的 GraphicBuffer 儲存到 graphicBuffer
const sp<GraphicBuffer>& graphicBuffer(mSlots[slot].mGraphicBuffer);
// 拿到 GraphicBuffer 的寬高
Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
// 定義裁剪區域
Rect croppedRect(Rect::EMPTY_RECT);
// 裁剪 bufferRect 中處於裁剪區域的內容
crop.intersect(bufferRect, &croppedRect);
// 裁剪之後 croppedRect 就是 crop
if (croppedRect != crop) {
return BAD_VALUE;
}
// 如果 dataSpace 為 Unknown 則設定為預設值
if (dataSpace == HAL_DATASPACE_UNKNOWN) {
dataSpace = mCore->mDefaultBufferDataSpace;
}
// 傳遞 Fence,後續消費者根據這個來判斷是否有使用權
mSlots[slot].mFence = acquireFence;
// 修改 BufferSlot 的狀態為 QUEUE
mSlots[slot].mBufferState.queue();
// 增加幀計數器並將其本地版本儲存在 mCore->mMutex 上的鎖外部使用
++mCore->mFrameCounter;
currentFrameNumber = mCore->mFrameCounter;
mSlots[slot].mFrameNumber = currentFrameNumber;
// 將引數封裝進 BufferItem
item.mAcquireCalled = mSlots[slot].mAcquireCalled;
item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
item.mCrop = crop;
item.mTransform = transform &
~static_cast<uint32_t>(NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
item.mTransformToDisplayInverse =
(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) != 0;
item.mScalingMode = static_cast<uint32_t>(scalingMode);
item.mTimestamp = requestedPresentTimestamp;
item.mIsAutoTimestamp = isAutoTimestamp;
item.mDataSpace = dataSpace;
item.mHdrMetadata = hdrMetadata;
item.mFrameNumber = currentFrameNumber;
item.mSlot = slot;
item.mFence = acquireFence;
item.mFenceTime = acquireFenceTime;
item.mIsDroppable = mCore->mAsyncMode ||
(mConsumerIsSurfaceFlinger && mCore->mQueueBufferCanDrop) ||
(mCore->mLegacyBufferDrop && mCore->mQueueBufferCanDrop) ||
(mCore->mSharedBufferMode && mCore->mSharedBufferSlot == slot);
item.mSurfaceDamage = surfaceDamage;
item.mQueuedBuffer = true;
item.mAutoRefresh = mCore->mSharedBufferMode && mCore->mAutoRefresh;
item.mApi = mCore->mConnectedApi;
mStickyTransform = stickyTransform;
...
```
第二部分程式碼也比較簡單,就是構造了一個 BufferItem,將傳遞進來的 input 裡的值設定到 BufferItem 中。這裡唯一需要注意的是對 GraphicBuffer 內容進行的裁剪,因為 GraphicBuffer 有可能超出要顯示的區域,所以需要裁剪出滿足條件的內容。
cpp
bool Rect::intersect(const Rect& with, Rect* result) const {
result->left = max(left, with.left);
result->top = max(top, with.top);
result->right = min(right, with.right);
result->bottom = min(bottom, with.bottom);
return !(result->isEmpty());
}
5.3 queueBuffer 的第三部分
```cpp status_t BufferQueueProducer::queueBuffer(int slot, const QueueBufferInput &input, QueueBufferOutput *output) { ...
mStickyTransform = stickyTransform;
// 快取共享緩衝區資料,以便可以重新建立 BufferItem
if (mCore->mSharedBufferMode) {
mCore->mSharedBufferCache.crop = crop;
mCore->mSharedBufferCache.transform = transform;
mCore->mSharedBufferCache.scalingMode = static_cast<uint32_t>(
scalingMode);
mCore->mSharedBufferCache.dataspace = dataSpace;
}
output->bufferReplaced = false;
// 將 BufferItem 入隊,這裡分為幾種情況
if (mCore->mQueue.empty()) {
// 當 BufferQueue 佇列為空,我們可以忽略 MDEQUEUEBUFERCANNOTBLOCK,將此緩衝區入隊
mCore->mQueue.push_back(item);
frameAvailableListener = mCore->mConsumerListener;
} else {
// 當佇列不為空時,我們需要檢視佇列中的最後一個緩衝區,看看是否需要替換它
const BufferItem& last = mCore->mQueue.itemAt(
mCore->mQueue.size() - 1);
if (last.mIsDroppable) {
if (!last.mIsStale) {
mSlots[last.mSlot].mBufferState.freeQueued();
// After leaving shared buffer mode, the shared buffer will still be around. Mark it as no longer shared if this operation causes it to be free.
// 離開共享緩衝區模式後,共享緩衝區仍然存在。如果此操作導致其空閒,則將其標記為不再共享
if (!mCore->mSharedBufferMode &&
mSlots[last.mSlot].mBufferState.isFree()) {
mSlots[last.mSlot].mBufferState.mShared = false;
}
// 不要將共享緩衝區放在空閒列表上
if (!mSlots[last.mSlot].mBufferState.isShared()) {
mCore->mActiveBuffers.erase(last.mSlot);
mCore->mFreeBuffers.push_back(last.mSlot);
output->bufferReplaced = true;
}
}
// 如果最後一個緩衝區是可覆蓋的,則用傳入緩衝區覆蓋它
mCore->mQueue.editItemAt(mCore->mQueue.size() - 1) = item;
frameReplacedListener = mCore->mConsumerListener;
} else {
// 如果最後一個緩衝區不是可覆蓋的,則直接入隊
mCore->mQueue.push_back(item);
frameAvailableListener = mCore->mConsumerListener;
}
}
// 修改 BufferQueue 的變數,傳送通知
mCore->mBufferHasBeenQueued = true; // BufferQueue 是否已經有 Buffer 入隊
mCore->mDequeueCondition.notify_all(); // 通知 mDequeueCondition 鎖相關的執行緒
mCore->mLastQueuedSlot = slot; // 儲存 slot 到 mLastQueuedSlot
// 將要返回的引數儲存到 output
output->width = mCore->mDefaultWidth;
output->height = mCore->mDefaultHeight;
output->transformHint = mCore->mTransformHint;
// 現在待消費的 Buffer
output->numPendingBuffers = static_cast<uint32_t>(mCore->mQueue.size());
// 下一幀的編號
output->nextFrameNumber = mCore->mFrameCounter + 1;
mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
// 獲取回撥函式的票證
callbackTicket = mNextCallbackTicket++;
}
```
第三部分則是將建立的 BufferItem 入隊。這裡入隊會分為幾種情況分別處理
- BufferQueue 為空,則直接入隊
- BufferQueue 不為空
- 最後一幀的內容是否可覆蓋,如果是可覆蓋的,就丟棄最後一幀的內容,將新的 BufferItem 入隊
- 如果最後一幀的內容不可覆蓋,則將新 BufferItem 入隊
然後修改 BufferQueue 的變數,併發出通知。還記得在 dequeueBuffer 中陷入等待的幾種情況嗎[見3.3],如果找不到 BufferSlot 或者出現 tooManyBuffer 的情況就會讓執行緒陷入等待。說到底就是生產者申請 Buffer 太快了。
5.4 queueBuffer 的第四部分
```cpp status_t BufferQueueProducer::queueBuffer(int slot, const QueueBufferInput &input, QueueBufferOutput *output) { ... // 當使用者是 SurfaceFinger 時,可以不清除 GraphicBuffer, // 因為可以保證 BufferQueue 在 SurfaceFinger 的程序內,並且不會有繫結器呼叫 if (!mConsumerIsSurfaceFlinger) { item.mGraphicBuffer.clear(); }
// 回撥雖然沒有持有沒有主緩衝佇列鎖,但是持有對應的回撥鎖,所以也可以確保回撥是有序的
int connectedApi;
sp<Fence> lastQueuedFence;
{
std::unique_lock<std::mutex> lock(mCallbackMutex);
while (callbackTicket != mCurrentCallbackTicket) {
mCallbackCondition.wait(lock);
}
// 通知監聽器有 BufferSlot 可以進行消費了
if (frameAvailableListener != nullptr) {
frameAvailableListener->onFrameAvailable(item);
} else if (frameReplacedListener != nullptr) {
frameReplacedListener->onFrameReplaced(item);
}
connectedApi = mCore->mConnectedApi;
lastQueuedFence = std::move(mLastQueueBufferFence);
mLastQueueBufferFence = std::move(acquireFence);
mLastQueuedCrop = item.mCrop;
mLastQueuedTransform = item.mTransform;
++mCurrentCallbackTicket;
mCallbackCondition.notify_all();
}
// 更新並獲取FrameEventHistory
nsecs_t postedTime = systemTime(SYSTEM_TIME_MONOTONIC);
NewFrameEventsEntry newFrameEventsEntry = {
currentFrameNumber,
postedTime,
requestedPresentTimestamp,
std::move(acquireFenceTime)
};
// 做一個記錄
addAndGetFrameTimestamps(&newFrameEventsEntry,
getFrameTimestamps ? &output->frameTimestamps : nullptr);
// 無鎖等待
if (connectedApi == NATIVE_WINDOW_API_EGL) {
// 在此處等待允許兩個已滿的緩衝區入隊,但不允許第三個緩衝區入隊。
lastQueuedFence->waitForever("Throttling EGL Production");
}
return NO_ERROR;
} ```
第四部分主要就是進行一些回撥和通知了,並且定了了一些 mLast 相關的變數。
5.5 queueBuffer 總結
相比於 dequeueBuffer,queueBuffer 的邏輯簡單太多了,就是將生產者傳入的一些引數解析後,建立一個對應的 BufferItem 入隊到 BufferQueue 中,然後根據最後一幀是否可覆蓋分別處理,然後修改一些狀態。最後回撥通知。
六 acquireBuffer
看完生產者 BufferQueueProducer 的兩個函式,接下來我們再看看消費者 BufferQueueConsumer 的兩個函式,首先是 acquireBuffer。
6.1 acquireBuffer 的第一部分
```cpp status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, nsecs_t expectedPresent, uint64_t maxFrameNumber) {
int numDroppedBuffers = 0;
sp<IProducerListener> listener;
{
std::unique_lock<std::mutex> lock(mCore->mMutex);
// 先計算已經 Acquired 的 Buffer
int numAcquiredBuffers = 0;
for (int s : mCore->mActiveBuffers) {
if (mSlots[s].mBufferState.isAcquired()) {
++numAcquiredBuffers;
}
}
// 如果已經 Acquired 的 Buffer 超出最大數 mMaxAcquiredBufferCount 的限制就返回
// 這裡的最大數允許超出1個,是為了消費者可以使用新的 Buffer 替換舊的 Buffer
if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
return INVALID_OPERATION;
}
bool sharedBufferAvailable = mCore->mSharedBufferMode &&
mCore->mAutoRefresh && mCore->mSharedBufferSlot !=
BufferQueueCore::INVALID_BUFFER_SLOT;
// 在非同步模式下,列表保證是一個緩衝區深,而在同步模式下,我們使用最舊的緩衝區。
if (mCore->mQueue.empty() && !sharedBufferAvailable) {
return NO_BUFFER_AVAILABLE;
}
// 獲取 BufferQueue 佇列的迭代器
BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
} ```
第一部分的邏輯比較簡單
- 首先判斷當前 acquire 的緩衝區是否已經超過了可以 acquire 的最大緩衝區數量,一般情況下這個數量是1,不過這裡會在這個數量的基礎上再加1,因為為了讓消費者可以用新的緩衝區替換舊的緩衝區
- 非共享模式,如果緩衝區佇列是空,就返回沒有可用緩衝區
6.2 acquireBuffer 的第二部分
```cpp status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, nsecs_t expectedPresent, uint64_t maxFrameNumber) { ... // 如果指定了 expectedPresent,我們可能還不想返回緩衝區。 // 如果它被指定並且有多個緩衝區排隊,我們可能想要刪除一個緩衝區。 // 如果我們處於共享緩衝區模式並且佇列為空,則跳過此步驟,因為在這種情況下我們將只返回共享緩衝區。 if (expectedPresent != 0 && !mCore->mQueue.empty()) { // expectedPresent 引數表示這個 buffer 是期望被什麼時候顯示到螢幕上 // 如果 buffer 的 desiredPresent 時間早於 expectedPresent 時間,那麼這個 buffer 就會按時顯示或者儘快顯示 // 如果想顯示它,就 acquire 並且返回它,如果在 expectedPresent 時間之前都不想顯示它,就返回 PRESENT_LATER // 注意:程式碼假定來自系統時鐘的單調時間值為正。
// 首先檢查我們是否可以丟幀。 如果時間戳是由 Surface 自動生成的,我們將跳過此檢查。
// 如果應用程式沒有明確生成時間戳,則它可能不希望基於它們的時間戳丟棄幀。
// mIsAutoTimestamp 代表這個緩衝區是否有自動生成的時間戳
while (mCore->mQueue.size() > 1 && !mCore->mQueue[0].mIsAutoTimestamp) {
const BufferItem& bufferItem(mCore->mQueue[1]);
// 如果刪除 entry[0] 會給我們留下一個消費者尚未準備好的緩衝區,請不要刪除它。
if (maxFrameNumber && bufferItem.mFrameNumber > maxFrameNumber) {
break;
}
// 如果 entry[1] 是及時的,則刪除 entry[0](並重復)。 我們在這裡應用了一個額外的標準:如果我們的 desiredPresent 在 expectedPresent 的 +/- 1 秒內,我們只刪除較早的緩衝區。
// 否則,虛假的 desiredPresent 時間(例如,0 或較小的相對時間戳),通常意味著“忽略時間戳並立即獲取”,將導致我們丟幀。
// 如果 entry[1] 的柵欄尚未發出訊號,則不要刪除較早的緩衝區。
if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
desiredPresent > expectedPresent) {
// 這個緩衝區設定為在不久的將來顯示,或者 desiredPresent 是垃圾。 無論哪種方式,我們都不想為了更快地將其顯示在螢幕上而丟棄前一個緩衝區。
break;
}
if (!front->mIsStale) {
// 隊首的緩衝區仍在 mSlots 中,因此將插槽標記為空閒
mSlots[front->mSlot].mBufferState.freeQueued();
// 離開共享緩衝區模式後,共享緩衝區仍然存在。 如果此操作導致其免費,則將其標記為不再共享。
if (!mCore->mSharedBufferMode &&
mSlots[front->mSlot].mBufferState.isFree()) {
mSlots[front->mSlot].mBufferState.mShared = false;
}
// 不要將共享緩衝區放在空閒列表中
if (!mSlots[front->mSlot].mBufferState.isShared()) {
mCore->mActiveBuffers.erase(front->mSlot);
mCore->mFreeBuffers.push_back(front->mSlot);
}
listener = mCore->mConnectedProducerListener;
++numDroppedBuffers;
}
mCore->mQueue.erase(front);
front = mCore->mQueue.begin();
}
// 檢視是否準備好獲取前端緩衝區
nsecs_t desiredPresent = front->mTimestamp;
bool bufferIsDue = desiredPresent <= expectedPresent ||
desiredPresent > expectedPresent + MAX_REASONABLE_NSEC;
bool consumerIsReady = maxFrameNumber > 0 ?
front->mFrameNumber <= maxFrameNumber : true;
if (!bufferIsDue || !consumerIsReady) {
return PRESENT_LATER;
}
}
```
第二部分主要是判斷舊的緩衝區是否可以被新緩衝區替代。如果舊的緩衝區有自動生成的時間戳,並且新緩衝區也馬上就要顯示了,那麼這種情況下,會直接使用新的緩衝區,如果新的緩衝區還要很久才會被用到,那麼就使用舊的緩衝區。
6.3 acquireBuffer 的第三部分
```cpp status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer, nsecs_t expectedPresent, uint64_t maxFrameNumber) {
int slot = BufferQueueCore::INVALID_BUFFER_SLOT;
if (sharedBufferAvailable && mCore->mQueue.empty()) {
// 在獲取緩衝區之前確保緩衝區已完成分配
mCore->waitWhileAllocatingLocked(lock);
slot = mCore->mSharedBufferSlot;
// 從上次排隊時快取的資料為共享緩衝區重新建立 BufferItem。
outBuffer->mGraphicBuffer = mSlots[slot].mGraphicBuffer;
outBuffer->mFence = Fence::NO_FENCE;
outBuffer->mFenceTime = FenceTime::NO_FENCE;
outBuffer->mCrop = mCore->mSharedBufferCache.crop;
outBuffer->mTransform = mCore->mSharedBufferCache.transform &
~static_cast<uint32_t>(
NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
outBuffer->mScalingMode = mCore->mSharedBufferCache.scalingMode;
outBuffer->mDataSpace = mCore->mSharedBufferCache.dataspace;
outBuffer->mFrameNumber = mCore->mFrameCounter;
outBuffer->mSlot = slot;
outBuffer->mAcquireCalled = mSlots[slot].mAcquireCalled;
outBuffer->mTransformToDisplayInverse =
(mCore->mSharedBufferCache.transform &
NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) != 0;
outBuffer->mSurfaceDamage = Region::INVALID_REGION;
outBuffer->mQueuedBuffer = false;
outBuffer->mIsStale = false;
outBuffer->mAutoRefresh = mCore->mSharedBufferMode &&
mCore->mAutoRefresh;
} else {
slot = front->mSlot;
*outBuffer = *front;
}
if (!outBuffer->mIsStale) {
mSlots[slot].mAcquireCalled = true;
// 如果 BufferItem 之前不在佇列中,則不要減少佇列計數。 當佇列為空並且上面建立了 BufferItem 時,會在共享緩衝區模式下發生這種情況。
if (mCore->mQueue.empty()) {
mSlots[slot].mBufferState.acquireNotInQueue();
} else {
mSlots[slot].mBufferState.acquire();
}
mSlots[slot].mFence = Fence::NO_FENCE;
}
// 如果緩衝區先前已被消費者獲取,則將 mGraphicBuffer 設定為 NULL 以避免在消費者端不必要地重新對映此緩衝區
if (outBuffer->mAcquireCalled) {
outBuffer->mGraphicBuffer = nullptr;
}
mCore->mQueue.erase(front);
// 我們可能在丟棄舊緩衝區時釋放了一個 Slot,或者生產者可能被阻塞
mCore->mDequeueCondition.notify_all();
mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
}
// 回撥
if (listener != nullptr) {
for (int i = 0; i < numDroppedBuffers; ++i) {
listener->onBufferReleased();
}
}
return NO_ERROR;
} ```
6.4 acquireBuffer 總結
和生產者想必,消費者的邏輯要簡單很多,acquireBuffer 的主要工作如下
- 首先判斷已經處於 ACQUIRED 狀態緩衝區的數量,如果數量超出了限制,就不會返回新的緩衝區。不過這裡會在最大數量的基礎上加1,為了做新舊緩衝區的替換
- 判斷緩衝區是否有自動生成的時間戳,這個時間戳用於丟棄舊的緩衝區。然後基於這個時間戳再判斷舊緩衝區是否可以直接使用新緩衝區替代
- 將確定好的緩衝區返回出去,並且變更 Slot 的狀態為 ACQUIRED,通知其他阻塞的執行緒,然後呼叫回撥函式。
七 releaseBuffer
```cpp
status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
const sp
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
releaseFence == nullptr) {
// slot 不合法或者 releaseFence 為空
return BAD_VALUE;
}
sp<IProducerListener> listener;
{
std::lock_guard<std::mutex> lock(mCore->mMutex);
// 如果幀的序號因為緩衝區重新分配而改變,我們可以忽略舊緩衝區 releaseBuffer。
// 對於共享緩衝區,請忽略這一點,其中由於緩衝區正在排隊並同時獲取,幀號很容易不同步。
if (frameNumber != mSlots[slot].mFrameNumber &&
!mSlots[slot].mBufferState.isShared()) {
return STALE_BUFFER_SLOT;
}
// 判斷 Slot 的狀態,Slot 的狀態必須是 ACQUIRED
if (!mSlots[slot].mBufferState.isAcquired()) {
return BAD_VALUE;
}
mSlots[slot].mEglDisplay = eglDisplay;
mSlots[slot].mEglFence = eglFence;
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();
// 離開共享緩衝區模式後,共享緩衝區仍然存在。 如果此操作導致其 FREE,則將其標記為不再共享。
if (!mCore->mSharedBufferMode && mSlots[slot].mBufferState.isFree()) {
mSlots[slot].mBufferState.mShared = false;
}
// 不要將共享緩衝區放在 FREE 列表中
if (!mSlots[slot].mBufferState.isShared()) {
mCore->mActiveBuffers.erase(slot);
mCore->mFreeBuffers.push_back(slot);
}
listener = mCore->mConnectedProducerListener;
mCore->mDequeueCondition.notify_all();
}
// 無鎖回撥
if (listener != nullptr) {
listener->onBufferReleased();
}
return NO_ERROR;
} ```
releaseBuffer 就是消費者使用完緩衝區後,釋放緩衝區的過程。在呼叫 releaseBuffer 函式之後,mSlot 的狀態就會變成 FREE 狀態。
八 總結
SurfaceFlinger 中 BufferQueue 的工作流程和原理到此就基本理清了,聯絡之前的 SurfaceFlinger 合成的工作流程,SurfaceFlinger 合成工作中,最關鍵的流程已經基本學習完畢了。當然,對於 SurfaceFlinger 工作來說,這連冰山一角都算不上,前路漫漫。
- Activity啟動原始碼解析(Android12)
- 從MediaServer看Binder的使用方式(一)
- 從MediaServer看Binder的使用方式(二)
- [Android禪修之路] 解讀Layer
- [Android禪修之路] Android圖形系統,從Activity到Surface
- [Android禪修之路] 解讀 GraphicBuffer 之 Framework 層
- [Android禪修之路] 解讀SurfaceFlinger中的BufferQueue
- [Android禪修之路] SurfaceFlinger 合成中的工作
- [Android禪修之路] SurfaceFlinger 中的一些物件
- [Android禪修之路] SurfaceFlinger 合成前的預處理
- [Android禪修之路] SurfaceFlinger合成總覽
- [Android禪修之路] SurfaceFlinger的啟動過程
- [Android禪修之路] Android 圖形系統開篇