Android中的GraphicBuffer同步機制-Fence

來源:互聯網
上載者:User

標籤:

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觸發才能使用。

Android中的GraphicBuffer同步機制-Fence

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.