Buffer狀態
對于生產者這邊,BufferQueue的流程基本講完了。簡單說來,首先提需求,告訴BufferQueue需要什么樣的Buffer,大小,格式,usage等等;然后dequeue Buffer出來,往Buffer里面繪制顯示數據;繪制完成后,queue到BufferQueue里面,并通知消費者進行消費。如此不斷的的dequeue,繪制,queue。
消費者這邊的流程,我們還沒有講到。對于消費者來說,收到通知后,將從BufferQueue里面取queue過來的Buffer進行合成,合成完的Buffer再釋放掉,這里的釋放,是概念上的,并沒有真正釋放內存,只是讓其返回隊列,可以被再次dequeue。消費者這邊也是不斷的接通知,取buffer合成,然后釋放,不斷循環。
此圖是Android官網對BufferQueue通信過程的描述,這很好的描述這個過程。
在Android 6.0及之前的版本,在這些通信過程中,都將Buffer的狀態標記為具體的狀態。這四個過程Buffer分別對應不同的四個狀態:
DEQUEUED 狀態
Producer dequeue一個Buffer后,這個Buffer就變為DEQUEUED狀態,release Fence發信號后,Producer就可以修改Buffer的內容,我們稱為release Fence。此時Buffer被Producer占用。DEQUEUED狀態的Buffer可以遷移到 QUEUED 狀態,通過queueBuffer或attachBuffer流程。也可以遷移到FREE裝,通過cancelBuffer或detachBuffer流程。QUEUED 狀態
Buffer繪制完后,queue到BufferQueue中,給Consumer進行消費。此時Buffer可能還沒有真正繪制完成,必現要等對應的Fence發信號出來后,才真正完成。此時Buffer是BufferQueue持有,可以遷移到ACQUIRED狀態,通過acquireBuffer流程。而已可以遷移到FREE狀態,如果另外一個Buffer被異步的queue進來。ACQUIRED 狀態
Buffer已經被Consumer獲取,但是也必須要等對應的Fence發信號才能被Consumer讀寫,找個Fence是從Producer那邊,queueBuffer的時候傳過來的。我們將其稱為acquire fence。此時,Buffer被Consumer持有。狀態可以遷移到FREE狀態,通過releaseBuffer或detachBuffer流程。除了從acquireBuffer流程可以遷移到ACQUIRED狀態,attachBuffer流程也可以遷移到ACQUIRED狀態。FREE 狀態
FREE狀態,說明Buffer被BufferQueue持有,可以被Producer dequeue,它將遷移到DEQUEUED狀態,通過dequeueBuffer流程。SHARED狀態
SHARED狀態是一個特殊的狀態,SHARED的Buffer并不參與前面所說的狀態遷移。它說明Buffer被用與共享Buffer模式。除了FREE狀態,它可以是其他的任何狀態。它可以被多次dequeued, queued, 或者 acquired。這中共享Buffer的模式,主要用于VR等低延遲要求的場合。
目前,Buffer的狀態,都是通過各個狀態的Buffer的量來表示狀態,對應的關系如下:
Buffer狀態 | 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 |
Buffer的狀態在代碼中用BufferState描述,BufferState的定義如下:
* frameworks/native/libs/gui/include/gui/BufferSlot.h
struct BufferState {
BufferState()
: mDequeueCount(0),
mQueueCount(0),
mAcquireCount(0),
mShared(false) {
}
uint32_t mDequeueCount;
uint32_t mQueueCount;
uint32_t mAcquireCount;
bool mShared;
... ...
};
前面講解dequeueBuffer和queueBuffer流程時,BufferQueue有很多個隊列,我們再來看一下BufferQueue中,幾個隊列間的關系。
BufferQueueCore中的定義如下:
* frameworks/native/libs/gui/include/gui/BufferQueueCore.h
class BufferQueueCore : public virtual RefBase {
... ...
typedef Vector<BufferItem> Fifo;
... ...
// mSlots is an array of buffer slots that must be mirrored on the producer
// side. This allows buffer ownership to be transferred between the producer
// and consumer without sending a GraphicBuffer over Binder. The entire
// array is initialized to NULL at construction time, and buffers are
// allocated for a slot when requestBuffer is called with that slot's index.
BufferQueueDefs::SlotsType mSlots;
// mQueue is a FIFO of queued buffers used in synchronous mode.
Fifo mQueue;
// mFreeSlots contains all of the slots which are FREE and do not currently
// have a buffer attached.
std::set<int> mFreeSlots;
// mFreeBuffers contains all of the slots which are FREE and currently have
// a buffer attached.
std::list<int> mFreeBuffers;
// mUnusedSlots contains all slots that are currently unused. They should be
// free and not have a buffer attached.
std::list<int> mUnusedSlots;
// mActiveBuffers contains all slots which have a non-FREE buffer attached.
std::set<int> mActiveBuffers;
mSlots
mSlots 是Buffer序號的一個數組,Producer端的mSlots也是這個mSlots,Consumer端是mSlots也是里的mSlots的引用。它可實現Buffer在Producer和Consumer之間轉移,而不需要真正的在Binder間去傳輸一個GraphicBuffer。初始狀態時為空,當requestBuffer流程執行時,將去為對應的Buffer序號,分配真正的Buffer。mQueue
mQueue是一個先進先出的Vector,是同步模式下使用。里面就是處于QUEUED狀態的Buffer。mFreeSlots
mFreeSlots包含所有是FREE狀態,且還沒有分配Buffer的,Buffer序號集合。剛開始時,mFreeSlots被初始化為MaxBufferCount個Buffer序號集合,dequeueBuffer的時候,將先從這個集合中獲取。但是消費者消費完成,釋放的Buffer并不返回到這個隊列中,而是返回到mFreeBuffers中。mFreeBuffers
mFreeBuffers包含的是所有FREE狀態,且已經分配Buffer的,Buffer序號的結合。消費者消費完成,釋放的Buffer并不返回到這個隊列中,而是返回到mFreeBuffers中。mUnusedSlots
mUnusedSlots和mFreeSlots有些相似,只是mFreeSlots會被用到,而mUnusedSlots中的Buffer序號不會不用到。也就是,總的Buffer序號NUM_BUFFER_SLOTS中,除去MaxBufferCount個mFreeSlots,剩余的集合。mActiveBuffers
mActiveBuffers包含所有非FREE狀態的Buffer。也就是包含了DEQUEUED,QUEUED,ACQUIRED以及SHARED這幾個狀態的。
我們從數學的角度來看看他們之間的關系:
mSlots的數組大小為NUM_BUFFER_SLOTS,但是其中,真正用起來的也只有MaxBufferCount個,其他的都不會被用到。所以,我們可以這么理解,mSlots是BufferQueue中實際流轉起來的Buffer。
mSlots = mFreeBuffers + mActiveBuffers
對于整體而言:
NUM_BUFFER_SLOTS = mUnusedSlots + mFreeSlots + mFreeBuffers + mActiveBuffers
mSlots是BufferSlot的集合,BufferSlot定義如下:
struct BufferSlot {
BufferSlot()
: mGraphicBuffer(nullptr),
mEglDisplay(EGL_NO_DISPLAY),
mBufferState(),
mRequestBufferCalled(false),
mFrameNumber(0),
mEglFence(EGL_NO_SYNC_KHR),
mFence(Fence::NO_FENCE),
mAcquireCalled(false),
mNeedsReallocation(false) {
}
// Buffer序號對應的Buffer
sp<GraphicBuffer> mGraphicBuffer;
// 創建EGLSyncKHR對象用
EGLDisplay mEglDisplay;
// Buffer序號當前的狀態
BufferState mBufferState;
// mRequestBufferCalled 表示Producer確實已經調用requestBuffer
bool mRequestBufferCalled;
// mFrameNumber 表示該Buffer序號已經被queue的次數. 主要用于dequeueBuffer時,遵從LRU,這很有用,因為buffer 變FREE時,可能release Fence還沒有發信號出來。
uint64_t mFrameNumber;
// 現在已經被mFence替換了,基本不用
EGLSyncKHR mEglFence;
// mFence 是同步的一種方式,上一個owner使用完Buffer后,需要發信號出來,下一個owner才可以使用。
sp<Fence> mFence;
// 表示Buffer已經被Consumer取走
bool mAcquireCalled;
// 表示Buffer需要重新分配,需要設置BUFFER_NEEDS_REALLOCATION 通知Producer,不要用原來的緩存的Buffer
bool mNeedsReallocation;
};
看完Buffer的狀態后,再回頭去看看前面介紹的dequeueBuffer和queueBuffer,是不是就很好理解了。
我們再來看看BufferQueue的工作模式,BufferQueue可以工作在幾個模式:
- 同步模式 Synchronous-like mode
默認情況下,BufferQueue將工作在同步模式下。在該模式下,每個Buffer都從Producer進入,從Consumer退出,沒有Buffer沒有丟棄掉。如果Producer生產的太快,Consumer來不及消費,Producer將阻塞等待FREE的Buffer。前面的分析流程的時候在waitForFreeSlotThenRelock也說到了這點。
這是waitForFreeSlotThenRelock函數中的邏輯:
if (mDequeueTimeout >= 0) {
status_t result = mCore->mDequeueCondition.waitRelative(
mCore->mMutex, mDequeueTimeout);
if (result == TIMED_OUT) {
return result;
}
} else {
mCore->mDequeueCondition.wait(mCore->mMutex);
}
- 非同步模式 Non-blocking mode
和同步模式相反,BufferQueue工作在非阻塞模式下,在這種模式下,如果沒有FREE Buffer,將生成一個錯誤,而不是阻塞等待FREE的Buffer。這種模式,也沒有Buffer不丟棄。這中模式可以避免潛在的死鎖,如果應用不理解Graphics框架中復雜的依賴條件。前面我們的代碼分析中也看到這一點。waitForFreeSlotThenRelock
什么時候不去tryAgain
?
if (tryAgain) {
if ((mCore->mDequeueBufferCannotBlock || mCore->mAsyncMode) &&
(acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
return WOULD_BLOCK;
}
mAsyncMode是通過BufferQueueProducer的setAsyncMode函數設置的,從Producer調用過來,受Producer控制。
mDequeueBufferCannotBlock則是在Producer 連接到BufferQueue時,根據條件判斷的,具體邏輯如下:
status_t BufferQueueProducer::connect(const sp<IProducerListener>& listener,
int api, bool producerControlledByApp, QueueBufferOutput *output) {
... ...
if (mDequeueTimeout < 0) {
mCore->mDequeueBufferCannotBlock =
mCore->mConsumerControlledByApp && producerControlledByApp;
}
mCore->mAllowAllocation = true;
VALIDATE_CONSISTENCY();
return status;
}
舍棄模式 Discard mode
BufferQueue可以配置為丟棄舊Buffer,而不是生成錯誤或進行等待。比如,如果用GL對紋理進行快速的繪制,那么舊的Buffer不要丟棄。共享Buffer模式 shared buffer mode
共享Buffer模式,表示Buffer是Producer和Consumer共享。共享Buffer模式下,一直用的都是同一個Buffer。而Buffer的狀態不能遷移為FREE狀態。代碼中可以留意mCore->mSharedBufferMode
和mCore->mSharedBufferSlot
。這個模式其實也包含在同步模式中,只是比較特殊,單獨說一下。
現在,再回頭去看看前面介紹的dequeueBuffer和queueBuffer,是不是就更好理解了。
acquireBuffer流程
Buffer queue到BufferQueue中后,將通知消費者去消費。消費時,通過acquireBuffer來獲取Buffer,我們且不管acquireBuffer是什么地方調的,我們先來看BufferQueue中acquireBuffer的處理流程。
* frameworks/native/libs/gui/BufferQueueConsumer.cpp
status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
nsecs_t expectedPresent, uint64_t maxFrameNumber) {
ATRACE_CALL();
int numDroppedBuffers = 0;
sp<IProducerListener> listener;
{
Mutex::Autolock lock(mCore->mMutex);
int numAcquiredBuffers = 0;
for (int s : mCore->mActiveBuffers) {
if (mSlots[s].mBufferState.isAcquired()) {
++numAcquiredBuffers;
}
}
if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
BQ_LOGE("acquireBuffer: max acquired buffer count reached: %d (max %d)",
numAcquiredBuffers, mCore->mMaxAcquiredBufferCount);
return INVALID_OPERATION;
}
bool sharedBufferAvailable = mCore->mSharedBufferMode &&
mCore->mAutoRefresh && mCore->mSharedBufferSlot !=
BufferQueueCore::INVALID_BUFFER_SLOT;
// In asynchronous mode the list is guaranteed to be one buffer deep,
// while in synchronous mode we use the oldest buffer.
if (mCore->mQueue.empty() && !sharedBufferAvailable) {
return NO_BUFFER_AVAILABLE;
}
- acquireBuffer時,也是受mCore->mMutex控制的。
- numAcquiredBuffers,已經acquired的Buffer。mMaxAcquiredBufferCount最大可以acquire的Buffer,可以溢出一個,以便Consumer能方便替換舊的Buffer,如果舊的Buffer還沒有釋放時。
- sharedBufferAvailable,共享Buffer模式下使用。在這個模式下,mAutoRefresh表示,Consumer永遠可以acquire到一塊Buffer,即使BufferQueue還沒有處于可以acquire的狀態。
- mQueue,如沒有Buffer被queue過來,mQueue為空,那么Consumer這邊就acquire不到新的Buffer,Consumer這邊已經acquire的會被繼續使用。
如果有Buffer或是共享Buffer模式,繼續~
* frameworks/native/libs/gui/BufferQueueConsumer.cpp
BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
if (expectedPresent != 0 && !mCore->mQueue.empty()) {
const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
while (mCore->mQueue.size() > 1 && !mCore->mQueue[0].mIsAutoTimestamp) {
const BufferItem& bufferItem(mCore->mQueue[1]);
// If dropping entry[0] would leave us with a buffer that the
// consumer is not yet ready for, don't drop it.
if (maxFrameNumber && bufferItem.mFrameNumber > maxFrameNumber) {
break;
}
if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
desiredPresent > expectedPresent) {
// This buffer is set to display in the near future, or
// desiredPresent is garbage. Either way we don't want to drop
// the previous buffer just to get this on the screen sooner.
BQ_LOGV("acquireBuffer: nodrop desire=%" PRId64 " expect=%"
PRId64 " (%" PRId64 ") now=%" PRId64,
desiredPresent, expectedPresent,
desiredPresent - expectedPresent,
systemTime(CLOCK_MONOTONIC));
break;
}
BQ_LOGV("acquireBuffer: drop desire=%" PRId64 " expect=%" PRId64
" size=%zu",
desiredPresent, expectedPresent, mCore->mQueue.size());
if (!front->mIsStale) {
// Front buffer is still in mSlots, so mark the slot as free
mSlots[front->mSlot].mBufferState.freeQueued();
if (!mCore->mSharedBufferMode &&
mSlots[front->mSlot].mBufferState.isFree()) {
mSlots[front->mSlot].mBufferState.mShared = false;
}
// Don't put the shared buffer on the free list
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();
}
bool bufferIsDue = desiredPresent <= expectedPresent ||
desiredPresent > expectedPresent + MAX_REASONABLE_NSEC;
bool consumerIsReady = maxFrameNumber > 0 ?
front->mFrameNumber <= maxFrameNumber : true;
if (!bufferIsDue || !consumerIsReady) {
return PRESENT_LATER;
}
}
這里主要做了一些幾件事:
- expectedPresent 期望被顯示的時間
也就是這個Buffer希望在什么時候被顯示到屏幕上。如果Buffer的DesiredPresent的時間早于這個時間,那么這個Buffer將被準時顯示。或者稍晚才被顯示,如果我們不想顯示直到expectedPresent時間之后,我們返回PRESENT_LATER,不去acquire它。但是如果時間在一秒之內,就不會延遲了,直接acquire回去。 - 檢查是否需要丟棄一些幀
如果是Surface自動生成的時間,就不去檢查是否需要丟棄掉一些幀,這些Surface對顯示時間是沒有嚴格的要求的。如果mQueue中有多個Buffer,我們將丟掉一些queue過來比較早的Buffer。如果最近queue的Buffer,離期望顯示的時間已經沒有一秒了,那之前queue過來的Buffer都將被丟棄掉。這很好理解,你好比你要買一款手機,新款的廣告雖然來了,但是還有一段時間才能上市,你等不了這么就久,就先買就舊款了,總得用手機吧。但是,如果新款不到一秒就上市了,我們就稍微等會兒直接買新款,不買舊款了。
front->mIsStale,表示Buffer已經被釋放了,這是在BufferQueueCore::freeAllBuffersLocked
時置的位。此時,我們需要將Buffer都返回到BufferQueue FREE狀態中。
該丟棄的丟棄了,余下的就可以用來去顯示了。
* frameworks/native/libs/gui/BufferQueueConsumer.cpp
int slot = BufferQueueCore::INVALID_BUFFER_SLOT;
if (sharedBufferAvailable && mCore->mQueue.empty()) {
// make sure the buffer has finished allocating before acquiring it
mCore->waitWhileAllocatingLocked();
slot = mCore->mSharedBufferSlot;
// Recreate the BufferItem for the shared buffer from the data that
// was cached when it was last queued.
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;
}
如果是共享Buffer模式,即使mQueue為空,也會把共享的Buffer返回去。其他情況下就返回,mQueue的第一個Buffer。
* frameworks/native/libs/gui/BufferQueueConsumer.cpp
ATRACE_BUFFER_INDEX(slot);
if (!outBuffer->mIsStale) {
mSlots[slot].mAcquireCalled = true;
if (mCore->mQueue.empty()) {
mSlots[slot].mBufferState.acquireNotInQueue();
} else {
mSlots[slot].mBufferState.acquire();
}
mSlots[slot].mFence = Fence::NO_FENCE;
}
if (outBuffer->mAcquireCalled) {
outBuffer->mGraphicBuffer = NULL;
}
mCore->mQueue.erase(front);
mCore->mDequeueCondition.broadcast();
ATRACE_INT(mCore->mConsumerName.string(),
static_cast<int32_t>(mCore->mQueue.size()));
mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
VALIDATE_CONSISTENCY();
}
if (listener != NULL) {
for (int i = 0; i < numDroppedBuffers; ++i) {
listener->onBufferReleased();
}
}
return NO_ERROR;
}
acquire到Buffer后,修改mSlots中對應Buffer序號的mBufferState狀態。acquire的Buffer,需要從mQueue中 刪掉。留意這里的ATRACE_INT,這個在systrace分析時,非常有用。如果Buffer被丟棄了,可以通過Producer的監聽者,去通知Producer Buffer已經被release掉了。
releaseBuffer流程分析
Consumer具體怎么消費的,我們暫時不管,我們先來看消費完成后,releaseBuffer的流程。
* frameworks/native/libs/gui/BufferQueueConsumer.cpp
status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
EGLSyncKHR eglFence) {
ATRACE_CALL();
ATRACE_BUFFER_INDEX(slot);
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
releaseFence == NULL) {
BQ_LOGE("releaseBuffer: slot %d out of range or fence %p NULL", slot,
releaseFence.get());
return BAD_VALUE;
}
sp<IProducerListener> listener;
{ // Autolock scope
Mutex::Autolock lock(mCore->mMutex);
// FrameNumber已經變,buffer已經被重新分配
if (frameNumber != mSlots[slot].mFrameNumber &&
!mSlots[slot].mBufferState.isShared()) {
return STALE_BUFFER_SLOT;
}
if (!mSlots[slot].mBufferState.isAcquired()) {
BQ_LOGE("releaseBuffer: attempted to release buffer slot %d "
"but its state was %s", slot,
mSlots[slot].mBufferState.string());
return BAD_VALUE;
}
mSlots[slot].mEglDisplay = eglDisplay;
mSlots[slot].mEglFence = eglFence;
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();
if (!mCore->mSharedBufferMode && mSlots[slot].mBufferState.isFree()) {
mSlots[slot].mBufferState.mShared = false;
}
// Don't put the shared buffer on the free list.
if (!mSlots[slot].mBufferState.isShared()) {
mCore->mActiveBuffers.erase(slot);
mCore->mFreeBuffers.push_back(slot);
}
listener = mCore->mConnectedProducerListener;
BQ_LOGV("releaseBuffer: releasing slot %d", slot);
mCore->mDequeueCondition.broadcast();
VALIDATE_CONSISTENCY();
} // Autolock scope
// Call back without lock held
if (listener != NULL) {
listener->onBufferReleased();
}
return NO_ERROR;
}
- release Buffer的流程相對簡單,slot就是需要釋放的Buffer的序號。
- Buffer的FrameNumber變了,可能Buffer已經重新分配,這個是不用管。
- 只能釋放acquire狀態的buffer序號,釋放后是Buffer放會mFreeBuffers中。
- releaseFence,從Consumer那邊傳過來,Producer可以Dequeue mFreeBuffers中的Buffer,但是只有releaseFence發信號出來后,Consumer才真正用完,Producer才可以寫。
- 同樣的,可以通過listener通知Producer。
就這么多~~
小結
本章主要通過測試應用,講解ANativeWindow,Surface間的關系,Surface和Producer,Consumer間的關系;P應用怎么使用BufferQueue。講解了BufferQueue相關的幾個流程,dequeueBuffer,queueBuffer,acquireBuffer,releaseBuffer;以及Buffer的狀態,DEQUEUED,QUEUED,ACQUIRED,FREE遷移。