Android 圖形顯示系統(tǒng)(十三)Fence同步機制

Fence是一種同步機制,在Android里主要用于圖形系統(tǒng)中GraphicBuffer的同步。那它和已有同步機制相比有什么特點呢?它主要被用來處理跨硬件的情況。尤其是CPU。GPU和HWC之間的同步,另外它還能夠用于多個時間點之間的同步。GPU編程和純CPU編程一個非常大的不同是它是異步的。也就是說當我們調(diào)用GL command返回時這條命令并不一定完畢了。僅僅是把這個命令放在本地的command buffer里。詳細什么時候這條GL command被真正運行完畢CPU是不知道的,除非CPU使用glFinish()等待這些命令運行完,第二種方法就是基于同步對象的Fence機制。以下舉個生產(chǎn)者把GraphicBuffer交給消費者的樣例。如生產(chǎn)者是App中的renderer。消費者是SurfaceFlinger。GraphicBuffer的隊列放在緩沖隊列BufferQueue中。

BufferQueue對App端的接口為IGraphicBufferProducer,實現(xiàn)類為Surface,對SurfaceFlinger端的接口為IGraphicBufferConsumer,實現(xiàn)類為SurfaceFlingerConsumer。

BufferQueue中對每一個GraphiBuffer都有BufferState標記著它的狀態(tài):

Buffer狀態(tài)

這個狀態(tài)一定程度上說明了該GraphicBuffer的歸屬,但僅僅指示了CPU里的狀態(tài),而GraphicBuffer的真正使用者是GPU。也就是說,當生產(chǎn)者把一個GraphicBuffer放入BufferQueue時,僅僅是在CPU層面完畢了歸屬的轉(zhuǎn)移。

但GPU說不定還在用,假設(shè)還在用的話消費者是不能拿去合成的。這時候GraphicBuffer和生產(chǎn)消費者的關(guān)系就比較曖昧了。消費者對GraphicBuffer具有擁有權(quán)。但無使用權(quán),它須要等一個信號,告訴它GPU用完了,消費者才真正擁有使用權(quán)。一個簡化的模型例如以下:

Fence的簡化模型

這個通知GraphicBuffer被上一個使用者用完的信號就是由Fence完畢的。Fence的存在很單純,從誕生開始就是為了在合適的時間發(fā)出一個信號。

還有一個角度來說,為什么不在生產(chǎn)者把GraphicBuffer交給消費者時就調(diào)用glFinish()等GPU完畢呢?這樣擁有權(quán)和使用權(quán)就一并傳遞了。無需Fence。就功能上這樣做是能夠的,但性能會有影響,由于glFinish()是堵塞的。這時CPU為了等GPU自己也不能工作了。假設(shè)用Fence的話就能夠等這個GraphicBuffer真正要被消費者用到時再堵塞,而那之前CPU和GPU是能夠并行工作的。這樣相當于實現(xiàn)了臨界資源的lazy passing。

說完Fence的基本作用,再說下它的實現(xiàn)。

Fence。顧名思義就是把先到的攔住,等后來的。兩者步調(diào)一致了再往前走。抽象地說。Fence包括了同一或不同一時候間軸上的多個時間點。僅僅有當這些點同一時候到達時Fence才會被觸發(fā)。更具體的介紹能夠參考這篇文章(http://netaz.blogspot.com/2013/10/android-fences-introduction-in-any.html)。

Fence能夠由硬件實現(xiàn)(Graphic driver),也能夠由軟件實現(xiàn)(Android kernel中的sw_sync)。

EGL中提供了同步對象的擴展KHR_fence_sync(http://www.khronos.org/registry/vg/extensions/KHR/EGL_KHR_fence_sync.txt)。

當中提供了eglCreateSyncKHR ()。eglDestroySyncKHR()產(chǎn)生和銷毀同步對象。這個同步對象是往GL command隊列中插入的一個特殊操作,當運行到它時,會發(fā)出信號指示隊列前面的命令已所有運行完成。函數(shù)eglClientWaitSyncKHR()可讓調(diào)用者堵塞等待信號發(fā)生。

在此基礎(chǔ)之上。Android對其進行了擴展-ANDROID_native_fence_sync (http://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_native_fence_sync.txt)。新加了接口eglDupNativeFenceFDANDROID()。

它能夠把一個同步對象轉(zhuǎn)化為一個文件描寫敘述符(反過來,eglCreateSyncKHR()能夠把文件描寫敘述符轉(zhuǎn)成同步對象)。這個擴展相當于讓CPU中有了GPU中同步對象的句柄,文件描寫敘述符能夠在進程間傳遞(通過binder或domain socket等IPC機制),這就為多進程間的同步提供了基礎(chǔ)。

我們知道Unix系統(tǒng)一切皆文件,因此,有個這個擴展以后Fence的通用性大大增強了。

Android還進一步豐富了Fence的software stack。主要分布在三部分:C++ Fence類位于/frameworks/native/libs/ui/Fence.cpp; C的libsync庫位于/system/core/libsync/sync.c; Kernel driver部分位于/drivers/base/sync.c。

總得來說。kernel driver部分是同步的主要實現(xiàn),libsync是對driver接口的封裝。Fence是對libsync的進一步的C++封裝。

Fence會被作為GraphicBuffer的附屬隨著GraphicBuffer在生產(chǎn)者和消費間傳輸。

另外Fence的軟件實現(xiàn)位于/drivers/base/sw_sync.c。SyncFeatures用以查詢系統(tǒng)支持的同步機制:/frameworks/native/libs/gui/SyncFeatures.cpp。

以下分析下Fence在Android中的詳細使用方法。

它基本的作用是GraphicBuffer在App, GPU和HWC三者間傳遞時作同步。

首先溫故一下GraphicBuffer從App到Display的旅程。GraphicBuffer先由App端作為生產(chǎn)者進行繪制。然后放入到BufferQueue。等待消費者取出作下一步的渲染合成。SurfaceFlinger作為消費者。會把每一個層相應(yīng)的GraphicBuffer取來生成EGLImageKHR對象。合成時對于GraphicBuffer的處理分兩種情況。對于Overlay的層。SurfaceFlinger會直接將其buffer handle放入HWC的Layer list。

對于須要GPU繪制的層(超出HWC處理層數(shù)或者有復(fù)雜變換的)。SurfaceFlinger會將前面生成的EGLImageKHR通過glEGLImageTargetTexture2DOES()作為紋理進行合成(http://snorp.net/2011/12/16/android-direct-texture.html)。

合成完后SurfaceFlinger又作為生產(chǎn)者。把GPU合成好的framebuffer的handle置到HWC中的FramebufferTarget中(HWC中hwc_display_contents_1_t中的hwc_layer_1_t列表最后一個slot用于放GPU的渲染結(jié)果所在buffer)。

HWC最后疊加Overlay層再往Display上扔,這時HWC是消費者。

整個大致流程如圖:

Fence在顯示系統(tǒng)中的流程

能夠看到,對于非Overlay的層來說GraphicBuffer先后經(jīng)過兩個生產(chǎn)消費者模型。我們知道GraphicBuffer核心包括的是buffer_handle_t結(jié)構(gòu),它指向的native_handle_t包括了gralloc中申請出來的圖形緩沖區(qū)的文件描寫敘述符和其他基本屬性,這個文件描寫敘述符會被同一時候映射到client和服務(wù)端。作為共享內(nèi)存。

GraphicBuffer模型

因為服務(wù)和client進程都能夠訪問同一物理內(nèi)存,因此不加同步的話會引起錯誤。為了協(xié)調(diào)client和服務(wù)端,在傳輸GraphicBuffer時。還帶有Fence,標志了它是否被上一個使用者使用完畢。Fence按作用大體分兩種:acquireFence和releaseFence。前者用于生產(chǎn)者通知消費者生產(chǎn)已完畢,后者用于消費者通知生產(chǎn)者消費已完畢。以下分別看一下這兩種Fence的產(chǎn)生和使用過程。首先是acquireFence的使用流程:


acquireFence的使用流程

當App端通過queueBuffer()向BufferQueue插入GraphicBuffer時,會順帶一個Fence,這個Fence指示這個GraphicBuffer是否已被生產(chǎn)者用好。之后該GraphicBuffer被消費者通過acquireBuffer()拿走,同一時候也會取出這個acquireFence。之后消費者(也就是SurfaceFlinger)要把它拿來渲染時,須要等待Fence被觸發(fā)。假設(shè)該層是通過GPU渲染的,那么使用它的地方是Layer::onDraw()。當中會通過bindTextureImage()綁定紋理:
486 status_t err = mSurfaceFlingerConsumer->bindTextureImage();
該函數(shù)最后會調(diào)用doGLFenceWaitLocked()等待acquireFence觸發(fā)。由于再接下來就是要拿來畫了。假設(shè)這兒不等待直接往下走,那渲染出來的就是錯誤的內(nèi)容。

假設(shè)該層是HWC渲染的Overlay層,那么不須要經(jīng)過GPU,那就須要把這些層相應(yīng)的acquireFence傳到HWC中。這樣。HWC在合成前就能確認這個buffer是否已被生產(chǎn)者使用完,因此一個正常點的HWC須要等這些個acquireFence全被觸發(fā)才干去繪制。這個設(shè)置的工作是在SurfaceFlinger::doComposeSurfaces()中完畢的。該函數(shù)會調(diào)用每一個層的layer::setAcquireFence()函數(shù):
428 if (layer.getCompositionType() == HWC_OVERLAY) {
429 sp<Fence> fence = mSurfaceFlingerConsumer->getCurrentFence();
...
431 fenceFd = fence->dup();
...
437 layer.setAcquireFenceFd(fenceFd);
能夠看到當中忽略了非Overlay的層,由于HWC不須要直接和非Overlay層同步,它僅僅要和這些非Overlay層合成的結(jié)果FramebufferTarget同步就能夠了。GPU渲染完非Overlay的層后,通過queueBuffer()將GraphicBuffer放入FramebufferSurface相應(yīng)的BufferQueue。然后FramebufferSurface::onFrameAvailable()被調(diào)用。它先會通過nextBuffer()->acquireBufferLocked()從BufferQueue中拿一個GraphicBuffer,附帶拿到它的acquireFence。

接著調(diào)用HWComposer::fbPost()->setFramebufferTarget(),當中會把剛才acquire的GraphicBuffer連帶acquireFence設(shè)到HWC的Layer list中的FramebufferTarget slot中:
580 acquireFenceFd = acquireFence->dup();
...
586 disp.framebufferTarget->acquireFenceFd = acquireFenceFd;
綜上,HWC進行最后處理的前提是Overlay層的acquireFence及FramebufferTarget的acquireFence都被觸發(fā)。

看完acquireFence。再看看releaseFence的使用流程:

releaseFence的使用流程

前面提到合成的過程先是GPU工作,在doComposition()函數(shù)中合成非Overlay的層,結(jié)果放在framebuffer中。然后SurfaceFlinger會調(diào)用postFramebuffer()讓HWC開始工作。

postFramebuffer()中最主要是調(diào)用HWC的set()接口通知HWC進行合成顯示,然后會將HWC中產(chǎn)生的releaseFence(如有)同步到SurfaceFlingerConsumer中。實現(xiàn)位于Layer的onLayerDisplayed()函數(shù)中:
151 mSurfaceFlingerConsumer->setReleaseFence(layer->getAndResetReleaseFence());
上面主要是針對Overlay的層,那對于GPU繪制的層呢?在收到INVALIDATE消息時,SurfaceFlinger會依次調(diào)用handleMessageInvalidate()->handlePageFlip()->Layer::latchBuffer()->SurfaceFlingerConsumer::updateTexImage() ,當中會調(diào)用該層相應(yīng)Consumer的GLConsumer::updateAndReleaseLocked() 函數(shù)。

該函數(shù)會釋放老的GraphicBuffer,釋放前會通過syncForReleaseLocked()函數(shù)插入releaseFence,代表假設(shè)觸發(fā)時該GraphicBuffer消費者已經(jīng)使用完成。然后調(diào)用releaseBufferLocked()還給BufferQueue,當然還帶著這個releaseFence。

這樣。當這個GraphicBuffer被生產(chǎn)者再次通過dequeueBuffer()拿出時。就能夠通過這個releaseFence來推斷消費者是否仍然在使用。

還有一方面,HWC合成完成后,SurfaceFlinger會依次調(diào)用DisplayDevice::onSwapBuffersCompleted() -> FramebufferSurface::onFrameCommitted()。onFrameCommitted()核心代碼例如以下:
148 sp<Fence> fence = mHwc.getAndResetReleaseFence(mDisplayType);
...
151 status_t err = addReleaseFence(mCurrentBufferSlot,
152 mCurrentBuffer, fence);
此處拿到HWC生成的FramebufferTarget的releaseFence,設(shè)到FramebufferSurface中相應(yīng)的GraphicBuffer Slot中。這樣FramebufferSurface相應(yīng)的GraphicBuffer也能夠被釋放回BufferQueue了。當將來EGL從中拿到這個buffer時,照例也要先等待這個releaseFence觸發(fā)才干使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容