[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 圖形系統開篇