Android中的GraphicBuffer同步機制-Fence

原文鏈接:https://blog.csdn.net/jinzhuojun/article/details/39698317

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中申請出來的圖形緩衝區的文件描述符和其它基本屬性,這個文件描述符會被同時映射到客戶端和服務端,作爲共享內存。


由於服務和客戶端進程都可以訪問同一物理內存,因此不加同步的話會引起錯誤。爲了協調客戶端和服務端,在傳輸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觸發才能使用
---------------------  
作者:ariesjzj  
來源:CSDN  
原文:https://blog.csdn.net/jinzhuojun/article/details/39698317  
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章