必讀:
Android 12(S) 影像顯示系統 - 開篇
一、前言
前面的文章中講解Android BufferQueue的機制時,有遇到過Fence,但沒有具體講解。這篇文章,就針對Fence這種同步機制,做一些介紹。
Fence在Android影像顯示系統中用於GraphicBuffer的同步。我們不禁有疑問:那它和其它的同步機制相比有什麼特點呢?
Fence主要被用來處理跨硬體的情況,在我們關注的Android影像顯示系統中即處理CPU,GPU和HWC之間的資料同步。
二、Fence的基本作用
在Android BufferQueue的機制中,GraphicBuffer在生產者--圖形緩衝佇列--消費者之間流轉。每一個GraphicBuffer都有一個對應的BufferState標記其狀態,詳細可以參考:Android 12(S) 影像顯示系統 - BufferQueue的工作流程(八)
本文作者@二的次方 2022-05-20釋出於部落格園
通常這裡的BufferState代表的僅僅是GraphicBuffer的所有權,即此刻它歸誰所擁有;但這塊快取是否可以被所有者使用還需要等待同步訊號到來,即使用權要等到相關的Fence訊號發出才可獲得。
在BufferState的各種狀態的定義的解釋中可以看到:
[/frameworks/native/libs/gui/include/gui/BufferSlot.h]
// DEQUEUED indicates that the buffer has been dequeued by the producer, but
// has not yet been queued or canceled. The producer may modify the
// buffer's contents as soon as the associated release fence is signaled.
// The slot is "owned" by the producer.
// QUEUED indicates that the buffer has been filled by the producer and
// queued for use by the consumer. The buffer contents may continue to be
// modified for a finite time, so the contents must not be accessed until
// the associated fence is signaled. The slot is "owned" by BufferQueue.
// ACQUIRED indicates that the buffer has been acquired by the consumer. As
// with QUEUED, the contents must not be accessed by the consumer until the
// acquire fence is signaled. The slot is "owned" by the consumer.
? DEQUEUED狀態時,雖然GraphicBuffer被生產者擁有,但直到和這塊快取相關的release fence同步訊號發出後,生產者才能修改GraphicBuffer的內容;
? QUEUED狀態時,雖然GraphicBuffer已經入佇列被BufferQueue持有,但直到和這塊快取相關的fence同步訊號發出前,生產者仍可能修改GraphicBuffer的內容,也即fence同步訊號發出後,才能去訪問其內容;
? ACQUIRED狀態時,雖然GraphicBuffer被生產者擁有,但是直到acquire fence同步訊號發出後,消費者才可去消費快取中的資料;
為什麼要把所有權和使用權分開呢?
主要還是CPU、GPU這種跨硬體程式設計同步處理的需要。GPU程式設計和純CPU程式設計有一個很大的不同就是它是非同步的,也就是說當我們呼叫GL command返回時這條命令並不一定真正執行完成了,只是把這個命令放在本地的command buffer裡。具體什麼時候這條GL command被真正執行完畢CPU是不知道的,除非CPU使用glFinish()等待這些命令執行完,另外一種方法就是基於同步物件的Fence機制。
而和GraphicBuffer相對應的BufferState狀態一定程度上說明了該GraphicBuffer的歸屬,但只指示了CPU裡的狀態,而GraphicBuffer的真正使用者一般是GPU。比如當生產者把一個GraphicBuffer放入BufferQueue時,只是在CPU層面完成了歸屬的轉移。但GPU說不定還在用,如果還在用的話消費者是不能拿去合成的。這時候GraphicBuffer和生產消費者的關係就比較曖昧了,消費者對GraphicBuffer具有擁有權,但無使用權,它需要等一個訊號,告訴它GPU用完了,消費者才真正擁有使用權。
一個簡化的模型如下:
GraphicBuffer由一個使用者傳遞到下一個使用者,並通過Fence訊號告知新的使用者什麼時候可以開始使用它。Fence的存在非常單純,從誕生開始就是為了在合適的時間發出一個訊號。
另一個角度來說,為什麼不在生產者把GraphicBuffer交給消費者時就呼叫glFinish()等GPU完成呢?這樣擁有權和使用權就一併傳遞了,無需Fence。就功能上這樣做是可以的,但效能會有影響,因為glFinish()是阻塞的,這時CPU為了等GPU自己也不能工作了。如果用Fence的話就可以等這個GraphicBuffer真正要被消費者用到時再阻塞,而那之前CPU和GPU是可以並行工作的。這樣相當於實現了臨界資源的lazy passing。
本文作者@二的次方 2022-05-20釋出於部落格園
三、簡單介紹Fence的實現
Fence,顧名思義就是把先到的攔住,等後來的,兩者步調一致了再往前走。抽象地說,Fence包含了同一或不同時間軸上的多個時間點,只有當這些點同時到達時Fence才會被觸發。
更詳細的介紹可以參考這篇文章Android Synchronization Fences – An Introduction
Fence可以由硬體實現(Graphic driver),也可以由軟體實現(Android kernel中的sw_sync)。EGL中提供了同步物件的擴充套件KHR_fence_sync。其中提供了eglCreateSyncKHR (),eglDestroySyncKHR()產生和銷燬同步物件。這個同步物件是往GL command佇列中插入的一個特殊操作,當執行到它時,會發出訊號指示佇列前面的命令已全部執行完畢。函式eglClientWaitSyncKHR()可讓呼叫者阻塞等待訊號發生。
在此基礎之上,Android對其進行了擴充套件 --- ANDROID_native_fence_sync ,新加了介面eglDupNativeFenceFDANDROID()。它可以把一個同步物件轉化為一個檔案描述符(反過來,eglCreateSyncKHR()可以把檔案描述符轉成同步物件)。這個擴充套件相當於讓CPU中有了GPU中同步物件的控制程式碼,檔案描述符可以在程式間傳遞(通過Binder等IPC機制),這就為多程式間的同步提供了基礎。我們知道Unix系統一切皆檔案,因此,有個這個擴充套件以後Fence的通用性大大增強了。
Android還進一步豐富了Fence的software stack。主要分佈在三部分:
- C++ Fence類位於/frameworks/native/libs/ui/Fence.cpp;
- C的libsync庫位於/system/core/libsync/sync.c;
- Kernel driver部分
總得來說,kernel driver部分是同步的主要實現,libsync是對driver介面的封裝,Fence是對libsync的進一步的C++封裝。Fence會被作為GraphicBuffer的附屬隨著GraphicBuffer在生產者和消費間傳輸。另外Fence的軟體實現位於/drivers/base/sw_sync.c。SyncFeatures用以查詢系統支援的同步機制:/frameworks/native/libs/gui/SyncFeatures.cpp。
四、Fence在Android影像顯示系統的具體用法
Fence的主要作用就是保證GraphicBuffer在App, GPU和HWC三者間流轉時的資料讀寫同步(不同程式 or 不同硬體間的同步)。
概述下從APP渲染影像寫入GraphicBuffer經SurfaceFlinger處理最終呈現到Display的旅程:
1. GraphicBuffer先由App端作為生產者進行繪製,然後放入到BufferQueue,等待消費者取出作下一步的渲染合成;
2. SurfaceFlinger是Layer的管理者也是GraphicBuffer的消費者,畫面更新時,SurfaceFlinger會收集所有可見的圖層並詢問HWC各個圖層採用的合成方式;
3. 對於採用Device合成方式的圖層,SurfaceFlinger會直接把該圖層對應的GraphicBuffer的buffer handle通過呼叫setLayerBuffer放入HWC的Layer list;
4. 對於採用Client合成方式的圖層,即需要GPU繪製的層(超出HWC處理層數或者有複雜變換的),SurfaceFlinger會將所有待合成的圖層通過renderengine繪製到一塊client target buffer中,然後通過呼叫setClientTarget傳遞給HWC,此時SF對於APP來說是消費者,它消費APP生成的圖層資料,對於HWC來說SF是生產者,它生產target buffer給HWC消費;
參考:Android 12(S) 影像顯示系統 - SurfaceFlinger GPU合成/CLIENT合成方式 - 隨筆1
5. HWC最後疊加所有圖層再往Display呈現出來,這時HWC是消費者。整個大致流程如圖:
[/hardware/interfaces/graphics/composer/2.1/IComposerClient.hal]
/** Possible composition types for a given layer. */
enum Composition : int32_t {
INVALID = 0,
CLIENT = 1,
DEVICE = 2,
SOLID_COLOR = 3,
CURSOR = 4,
SIDEBAND = 5,
};
可以看到,對於合成方式是CLIENT的圖層來說,其GraphicBuffer先後經過兩個生產消費者模型:
⚽ 應用渲染時 == 生產者/消費者模型
⚽ SurfaceFlinger的GPU合成時 == 生產者/消費者模型
我們知道GraphicBuffer核心包含的是buffer_handle_t結構,它指向的native_handle_t包含了Gralloc中申請出來的圖形緩衝區的檔案描述符和其它基本屬性,這個檔案描述符會被同時對映到客戶端和服務端,作為共享記憶體(跨程式的共享)。
由於服務端程式和客戶端程式都可以訪問同一實體記憶體,因此不加同步的話會引起錯誤(讀寫衝突/混亂)。為了協調客戶端和服務端不同程式下對同一共享記憶體的訪問,在傳輸GraphicBuffer時,還帶有Fence,標誌了它是否被上一個使用者使用完畢。
Fence按作用大體分兩種:acquireFence和releaseFence。前者用於生產者通知消費者生產已完成,後者用於消費者通知生產者消費已完成。下面分別看一下這兩種Fence的產生和使用過程。首先是acquireFence的使用流程:
當App端通過queueBuffer()向BufferQueue插入GraphicBuffer時,會順帶一個Fence,這個Fence指示這個GraphicBuffer是否已被生產者用好。之後該GraphicBuffer被消費者通過acquireBuffer()拿走,同時也會取出這個acquireFence。之後消費者(也就是SurfaceFlinger)要把它拿來渲染時,需要等待Fence被觸發,之後才可以處理這塊快取中的資料。
我們看一下相關流程中的關鍵程式碼片段:
生產端在queueBuffer時,傳遞進來Fence
[ /frameworks/native/libs/gui/Surface.cpp ]
int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
... // fenceFd即為生產者傳遞進來的,進一步封裝為Fence Object,連同GraphicBuffer一塊入佇列
getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
...
}
消費端在acquireBuffer時,同時取得acquireFence,傳遞給最終的消費者SurfaceFlinger
[ /frameworks/native/libs/gui/BLASTBufferQueue.cpp ]
void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
...
BufferItem bufferItem;
status_t status =
mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false); //請求可用的buffer
...
auto buffer = bufferItem.mGraphicBuffer;
...
t->setBuffer(mSurfaceControl, buffer, releaseCallbackId, releaseBufferCallback); // 傳遞Buffer給SurfaceFlinger
t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
t->setAcquireFence(mSurfaceControl, // 傳遞fence給SurfaceFlinger
bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE);
...
}
SurfaceFlinger接受buffer與fence並設定到對應的Layer
[/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp]
uint32_t SurfaceFlinger::setClientStateLocked() {
...
if (what & layer_state_t::eAcquireFenceChanged) {
if (layer->setAcquireFence(s.acquireFence)) flags |= eTraversalNeeded; // 傳遞給Layer
}
...
if (layer->setBuffer(buffer, s.acquireFence, postTime, desiredPresentTime, isAutoTimestamp, // 傳遞給Layer
s.cachedBuffer, frameNumber, dequeueBufferTimestamp, frameTimelineInfo,
s.releaseBufferListener)) {
flags |= eTraversalNeeded;
}
...
}
Layer把資訊 Buffer & acquireFence儲存到mDrawingState
mDrawingState.buffer = buffer;
mDrawingState.acquireFence = fence;
合成階段等待acquireFence觸發後再去處理資料
SurfaceFlinger在對Layer進行重新繪製時,BufferLayer::latchBuffer
中會先去判斷mDrawingState記錄的最新設定下來的GraphicBuffer的acquire fence是否已經signaled,沒有的話會再繼續SurfaceFlinger::signalLayerUpdate ==> invalidate
嘗試,直到acquire fence被觸發,再去繼續執行updateTexImage==>updateActiveBuffer
等合成前的準備工作。
// invalidate
SurfaceFlinger::handleMessageInvalidate()
// pageflip
SurfaceFlinger::handlePageFlip()
// 然後呼叫到latchBuffer
BufferLayer::latchBuffer() {
...
// If the head buffer's acquire fence hasn't signaled yet, return and
// try again later
if (!fenceHasSignaled()) { // 等待acquire fence的邏輯
ATRACE_NAME("!fenceHasSignaled()");
mFlinger->signalLayerUpdate(); // 會再次觸發invalidate
return false;
}
...
}
注:上面的理解,我也只是根據程式碼來判斷的,沒有實際驗證過,理解也許有錯,僅供參考!
本文作者@二的次方 2022-05-20釋出於部落格園
如果圖層是DEVICE合成方式渲染,那麼不需要經過GPU,那就需要把這些層對應的acquireFence傳到HWC中。這樣,HWC在合成前就能確認這個buffer是否已被生產者使用完,因此一個正常點的HWC需要等這些個acquireFence全被觸發才能去繪製。所以在向HWC設定setLayerBuffer時,也會把傳遞acquireFence
[/frameworks/native/services/surfaceflinger/DisplayHardware/ComposerHal.cpp]
Error Composer::setLayerBuffer(Display display, Layer layer,
uint32_t slot, const sp<GraphicBuffer>& buffer, int acquireFence)
{
mWriter.selectDisplay(display);
mWriter.selectLayer(layer);
const native_handle_t* handle = nullptr;
if (buffer.get()) {
handle = buffer->getNativeBuffer()->handle;
}
mWriter.setLayerBuffer(slot, handle, acquireFence);
return Error::NONE;
}
我這裡有一個疑問:如果上面BufferLayer::latchBuffer
中分析的等待acquireFence的邏輯是正確的,那這個等待的邏輯看起來是直接傳遞給HWC合成的buffer也會經過這裡,那是不是setLayerBuffer再傳遞acquireFence給HWC,其實HWC就無需再判斷acquireFence是否被觸發了呢?因為latchBuffer中的邏輯保證了傳遞給HWC的buffer肯定是可以直接操作的了
對於CLIENT合成方式的圖層,因為HWC不需要直接這些層同步,它只要和這些CLIENT圖層合成的結果FramebufferTarget同步就可以了。
GPU程式合成CLIENT圖層前,會去RenderSurface::dequeueBuffer獲取一塊GraphicBuffer用來儲存合成的結果,然後使用SkiaGLRenderEngine::drawLayers去把所有CLIENT圖層畫到這塊圖形快取中。
[/frameworks/native/libs/renderengine/skia/SkiaGLRenderEngine.cpp]
status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
const std::vector<const LayerSettings*>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
const bool /*useFramebufferCache*/,
base::unique_fd&& bufferFence, base::unique_fd* drawFence) {
...
// wait on the buffer to be ready to use prior to using it
waitFence(bufferFence); // 等待buffer可用,這塊buffer就是通過RenderSurface::dequeueBuffer獲取的
...
if (drawFence != nullptr) {
*drawFence = flush(); // 這個drawFence用作同步HWC,告訴HWC: 我GPU畫完了,你HWC消費吧
}
}
GPU合成完CLIENT圖層後,通過RenderSurface::queueBuffer()將GraphicBuffer放入對應的BufferQueue,包括對應的Fence == SkiaGLRenderEngine::drawLayers時產生的;然後消費端FramebufferSurface通過advanceFrame() --> nextBuffer() -> acquireBufferLocked()從BufferQueue中拿一個GraphicBuffer,附帶拿到它的acquireFence。接著呼叫
[/frameworks/native/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp]
status_t FramebufferSurface::nextBuffer(uint32_t& outSlot,
sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence,
Dataspace& outDataspace) {
...
status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace);
...
}
其中會把剛才acquire的GraphicBuffer連帶acquireFence通過呼叫setClientTarget設到HWC的ClientTarget Layer。
綜上,HWC進行最後合成處理的前提是CLIENT圖層層的acquireFence及FramebufferTarget的acquireFence都被觸發。
看完acquireFence,再看看releaseFence的使用流程:
合成過程中,先是GPU工作,在Output::composeSurfaces()
函式中對CLIENT合成方式的圖層進行合成,結果放在framebuffer中(即RenderSurface::dequeueBuffer獲得的GraphicBuffer)。然後SurfaceFlinger會呼叫Output::postFramebuffer()
讓HWC開始工作。postFramebuffer()
中最主要是呼叫Display::presentAndGetFrameFences()
最終會呼叫到HWC的方法present
和getReleaseFences
通知HWC合成顯示並返回得到releaseFence。
HWC中產生的releaseFence主要是:同步DEVICE合成方式的Layer的GraphicBuffer的釋放;
[/frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp]
void Output::postFramebuffer() {
...
auto& outputState = editState();
outputState.dirtyRegion.clear();
mRenderSurface->flip();
auto frame = presentAndGetFrameFences(); // 通知HWC合成顯示並獲取到releaseFence
mRenderSurface->onPresentDisplayCompleted(); // 通知FramebufferSurface進行release buffer ==>走到FramebufferSurface::onFrameCommitted()
for (auto* layer : getOutputLayersOrderedByZ()) {
// The layer buffer from the previous frame (if any) is released
// by HWC only when the release fence from this frame (if any) is
// signaled. Always get the release fence from HWC first.
sp<Fence> releaseFence = Fence::NO_FENCE;
if (auto hwcLayer = layer->getHwcLayer()) { // 獲取Layer對應的releaseFence
if (auto f = frame.layerFences.find(hwcLayer); f != frame.layerFences.end()) {
releaseFence = f->second;
}
}
// If the layer was client composited in the previous frame, we
// need to merge with the previous client target acquire fence.
// Since we do not track that, always merge with the current
// client target acquire fence when it is available, even though
// this is suboptimal.
// TODO(b/121291683): Track previous frame client target acquire fence.
if (outputState.usesClientComposition) {
// 如果有GPU合成Layer(Client合成方式),做Fence::merge
// releaseFence -- HWC生成的
// clientTargetAcquireFence -- 本質是 RenderSurface::queueBuffer傳入的 acquire fence,是在SkiaGLRenderEngine::drawLayers中生成的
releaseFence =
Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
}
layer->getLayerFE().onLayerDisplayed(releaseFence); // 把releaseFence同步到各個Layer
}
...
}
對於DEVICE合成的Layer,因為是HWC消費這些圖層的GraphicBuffer資料。所以HWC產生的release fence來同步這些圖形快取的釋放;
對於CLIENT合成的Layer,因為是GPU消費這些圖層的GraphicBuffer資料併合成到client target中。所以當GPU完成合成時就意味著這些圖層的快取資料就不再被使用了,因此client target acquire fence作為release fence來同步這些圖形快取的釋放;
client target acquire fence即表示GPU合成完了,HWC可以消費了,也意味著相關Layer的影像快取可以釋放了。
release fence需要同步到Layer。實現位於Layer的onLayerDisplayed()函式中:
[/frameworks/native/services/surfaceflinger/BufferStateLayer.cpp]
void BufferStateLayer::onLayerDisplayed(const sp<Fence>& releaseFence) {
...
sp<CallbackHandle> ch;
for (auto& handle : mDrawingState.callbackHandles) {
if (handle->releasePreviousBuffer) {
ch = handle;
break;
}
}
auto status = addReleaseFence(ch, releaseFence);
if (status != OK) {
ALOGE("Failed to add release fence for layer %s", getName().c_str());
}
mPreviousReleaseFence = releaseFence;
}
中間還會有一些過程,然後經過Binder 回撥,會通知到BLASTBufferQueue,我們在setBuffer時,有設定一個releaseBufferCallback
,這個回撥中會收到release fence,最終會調到 releaseBuffer 連同fence一塊歸還到快取佇列中,之後生產者就可以dequeueBuffer時取到這塊快取了
[/frameworks/native/libs/gui/BLASTBufferQueue.cpp]
void BLASTBufferQueue::processNextBufferLocked(bool useNextTransaction) {
auto releaseBufferCallback =
std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4);
t->setBuffer(mSurfaceControl, buffer, releaseCallbackId, releaseBufferCallback); // 設定了releaseBufferCallback
}
那對於儲存GPU繪製結果的那一層呢?
Output::postFramebuffer() ==> RenderSurface::onPresentDisplayCompleted() ==>FramebufferSurface::onFrameCommitted()
[/frameworks/native/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp]
void FramebufferSurface::onFrameCommitted() {
if (mHasPendingRelease) {
sp<Fence> fence = mHwc.getPresentFence(mDisplayId); // 獲取release fence
if (fence->isValid()) {
status_t result = addReleaseFence(mPreviousBufferSlot,
mPreviousBuffer, fence);
ALOGE_IF(result != NO_ERROR, "onFrameCommitted: failed to add the"
" fence: %s (%d)", strerror(-result), result);
}
status_t result = releaseBufferLocked(mPreviousBufferSlot, mPreviousBuffer); // release buffer
ALOGE_IF(result != NO_ERROR, "onFrameCommitted: error releasing buffer:"
" %s (%d)", strerror(-result), result);
mPreviousBuffer.clear();
mHasPendingRelease = false;
}
}
此處拿到HWC生成的FramebufferTarget的releaseFence,設到FramebufferSurface中相應的GraphicBuffer Slot中。這樣FramebufferSurface對應的GraphicBuffer也可以被釋放回BufferQueue了。當將來EGL從中拿到這個buffer時,照例也要先等待這個releaseFence觸發才能使用。
講到這裡,fence的內容就差不多了。不過好多細節的東西都沒分析,也許還有理解錯誤。
本文作者@二的次方 2022-05-20釋出於部落格園
Fence在圖形顯示系統中,我覺得簡單點講,應該就概括為2個作用:
1. 生產者填充資料到GraphicBuffer,並附帶產生一個acquire fence,這塊buffer連同fence一塊返回到BufferQueue,acquireBuffer後被消費者取走 --> 此時acquire fence的作用
// 消費者焦急的等待....
生產者:Hi, 消費者,我把資料都寫到buffer了,你準備盡情消費吧!
消費者:收到,那我開始消費了哦!
2. 消費者使用完GraphicBuffer中的資料,並附帶產生一個release fence,這塊buffer連同fence一塊返回到BufferQueue,dequeueBuffer後被生產者取走 --> 此時release fence的作用
// 生產者焦急的等待....
消費者:Hi,生產者,這塊buffer中的資料我消費完了,不再使用了哦!
生產者:收到,那我就向這塊buffer中填充新的資料了
也許理解不正確,謹慎閱讀,就先這樣吧?
----
本文參考了很多有價值的文章,並據此修改來理解Android 12 中的邏輯,站在巨人的肩膀上可以看的更遠。本篇僅供學習參考,理解上難免有錯誤!
https://blog.csdn.net/jinzhuojun/article/details/39698317
https://zhuanlan.zhihu.com/p/68782630
https://www.jianshu.com/p/3c61375cc15b