1. 程式人生 > >Android中的GraphicBuffer同步機制-Fence

Android中的GraphicBuffer同步機制-Fence

mman fill spl 之前 超出 on() passing cli 觸發

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

BufferQueue對App端的接口為IGraphicBufferProducer,實現類為Surface,對SurfaceFlinger端的接口為IGraphicBufferConsumer,實現類為SurfaceFlingerConsumer。

BufferQueue中對每一個GraphiBuffer都有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。



說完Fence的基本作用,再說下它的實現。

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

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

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

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



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

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

我們知道Unix系統一切皆文件,因此,有個這個擴展以後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部分是同步的主要實現,libsync是對driver接口的封裝。Fence是對libsync的進一步的C++封裝。

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

另外Fence的軟件實現位於/drivers/base/sw_sync.c。SyncFeatures用以查詢系統支持的同步機制:/frameworks/native/libs/gui/SyncFeatures.cpp。


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

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

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

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

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

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

整個大致流程如圖:

技術分享

能夠看到,對於非Overlay的層來說GraphicBuffer先後經過兩個生產消費者模型。我們知道GraphicBuffer核心包括的是buffer_handle_t結構,它指向的native_handle_t包括了gralloc中申請出來的圖形緩沖區的文件描寫敘述符和其他基本屬性,這個文件描寫敘述符會被同一時候映射到client和服務端。作為共享內存。

技術分享

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

技術分享

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

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

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

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

技術分享

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

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

該函數會釋放老的GraphicBuffer,釋放前會通過syncForReleaseLocked()函數插入releaseFence,代表假設觸發時該GraphicBuffer消費者已經使用完成。然後調用releaseBufferLocked()還給BufferQueue,當然還帶著這個releaseFence。

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

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

Android中的GraphicBuffer同步機制-Fence