【译】Why Wayland on Android is a hard problem
這是幾個人問我的問題,而且我已經成為一名專家。?有一些嘗試(moste notablly,一個內置于libhybris?)解決這個問題。?然而,由于Wayland和Android處理EGL和圖形上下文的方式存在差異,因此幾乎不可能真正實現它的正確性。?在我真正解釋之前,我們需要一些背景知識。
Wayland EGL
首先,讓我們從客戶在Wayland合成器上獲取GL上下文時的工作方式開始吧。?從客戶的角度來看,這很簡單。?它使用EGL中的eglGetDisplay()函數,并為EGL提供了將其轉換為NativeDisplayType指針的wl_display指針。?然后,當它想要創建一個窗口時,它調用wl_egl_window_create()和它想要使用的表面以及獲得wl_egl_window指針的大小。?然后wl_egl_window指針強制轉換為NativeWindowType指針并將其傳遞給eglCreateWindowSurface()?。?通過wayland-egl.h的函數來破壞wl_egl_window以及改變它的大小。?除此之外,客戶端可以使用常規EGL函數來處理渲染上下文,就像任何其他EGLSurface并且可以使用Wayland協議調用來管理輸入和任何其他Wayland事物。
服務器對Wayland EGL表面的看法有點復雜,但它仍然不錯。?服務器的EGL表面末端通過EGL_WL_bind_wayland_display擴展來處理。?在啟動時,服務器調用eglBindWaylandDisplayWL()和EGL為其提供wl_display指針。?EGL實現設置和aditional全局或兩個,并在此時添加它需要的任何鉤子。?當服務器從客戶端wl_buffer對象時,它可以使用eglQueryWaylandBufferWL()來獲取緩沖區的寬度,高度和顏色格式。?為了將緩沖區綁定為紋理,它使用eglCreateImageKHR()函數,目標為EGL_WAYLAND_BUFFER_WL并且從客戶端接收的wl_buffer資源被wl_buffer到EGLClientBuffer?。?然后,合成器可以使用glEGLImageTargetTexture2DOES()將EGLImageKHR綁定為紋理。?它看起來有點復雜,但它非常簡單:獲取一個wl_buffer?,創建一個EGLImageKHR?,綁定為紋理。
但是從駕駛員的角度來看,這一切看起來如何呢??這就是它開始變得復雜的地方......
在eglBindWaylandDisplayWL()內部,驅動程序向wl_display添加至少一個全局,它提供了一些創建wl_buffer對象并將其與物理GPU緩沖區相關聯的方法。?在客戶端實現中,它獲取GPU緩沖區,允許客戶端呈現給它們,然后使用wl_surface.commit()將緩沖區交給服務器。eglSwapBuffers()調用的一般過程如下:
EGL實現調用wl_surface.commit()而不是將其留給客戶端的原因是它允許客戶端在簡單的渲染循環中運行而不涉及實際的Wayland調用。?渲染所需的一切都隱藏在eglSwapBuffers()?。
到目前為止,還沒那么糟糕。?事物變得多毛的是同步的。?需要eglSwapBuffers()?(根據規范)執行隱式glFlus()操作,以保證客戶端在調用eglSwapBuffers()之前呈現的所有內容都在屏幕上結束。?從服務器的角度來看,它從客戶端獲取一個wl_buffer?,并假設它可以自由地將其綁定為紋理并立即使用它進行渲染。?Wayland協議沒有說緩沖和渲染同步?。?這聽起來很糟糕,但它在現實中非常有效,因為它允許驅動程序使用它想要的任何同步原語,而不限制它現在必須實現的Wayland特定的同步對象。?為了正確處理同步,EGL實現為Wayland客戶端/服務器提供以下兩個保證:
eglSwapBuffers(或eglSwapBuffersWithDamageEXT)必須在返回之前調用wl_display.attach,wl_display.damage和wl_display.commit。?這樣客戶端就可以將各種表面狀態位與wl_display.commit請求同步。?它是否發生在另一個線程中并不重要,只是它發生在eglSwapBuffers reutnrs時。
只要合成器獲得wl_surface.attach,就可以將該緩沖區資源自由地傳遞到類型為EGL_WAYLAND_BUFFER_WL的eglCreateImageKHR中,將其轉換為紋理,并立即開始繪制。
究竟如何滿足這兩個約束取決于EGL的實現。?在某些特定的圖形堆棧中,?glFinish()在eglSwapBuffers()內部調用,以確保圖形卡在將緩沖區傳遞給合成器之前已完成緩沖區。?但是,這會導致GPU停滯并降低性能。?大多數驅動程序通過使用與每個緩沖區關聯的柵欄來解決此問題。在調用wl_surface.attach/damage/commit之前,等待緩沖區完成而不是eglSwapBuffers()?,它們設置了一個同步圍欄,調用wl_surface.attach/damage/commit,并立即返回。?然后,他們使用圍欄在流的下游某處序列化渲染命令。?這可能意味著eglCreateImageKHR()阻塞等待緩沖區完成,或者您將柵欄進一步向下傳遞并阻止在glBindTexture()或者甚至只是使用它來將命令序列化到GPU。?如何創建,傳遞和等待圍欄是EGL實現的內部細節,并且超出了Wayland核心協議的范圍。
關于wl_surface.frame事件的最后一個注釋。?此事件與緩沖區或GPU上發生的事情無關。?它的存在唯一的目的是告訴客戶端合成器已經開始使用當前連接的緩沖區進行渲染,并且它可以繼續并開始渲染它的下一幀。?在很多情況下,驅動程序并不關心此事件,但它對于實現eglSwapInterval(1)或類似事件非常有用。
Android EGL
好了,既然我們已經給Wayland EGL做了一個簡短的介紹,讓我們來談談Android。?Android通過使用有時稱為meta-EGL的實現其圖形堆棧:包含另一個EGL實現的EGL實現。?這允許Android OS跟蹤一些內容,并在核心驅動程序EGL實現提供的基礎上提供額外的擴展。?然而,我們關心的大部分內容實際上都在驅動程序EGL實現中。
對于客戶端,Android提供了一個名為ANativeWindow的數據結構,由顯示服務器實現。?(對于純android,這是SurfaceFlinger。)顯示服務器使用一些魔法將此結構傳遞給客戶端,客戶端將其傳遞給eglCreateWindowSurface()?。?然后,EGL實現使用ANativeWindow結構內部的函數指針從隊列中獲取緩沖區,渲染它們,然后將它們傳遞回顯示服務器進行顯示。?ANativeWindow結構還包含使用基于文件描述符的柵欄同步渲染的機制。?我們關心的函數指針如下:
-
dequeueBuffer()?:驅動程序使用它從窗口系統的緩沖區隊列中獲取該窗口的緩沖區。ANativeWindow接口的實現者負責通過gralloc分配一些緩沖區,并在驅動程序調用dequeueBuffer()時將它們分發出去。
-
queueBuffer()?:驅動程序調用dequeueBuffer()緩沖區(先前從dequeueBuffer()檢索)傳回窗口系統進行合成。?對此函數唯一真正的要求是緩沖區必須來自此ANativeWindow上的dequeueBuffer()?,并且緩沖區必須按照它們被dequed的相同順序排隊。
-
cancelBuffer()?:驅動程序調用它來告訴它它不打算使用它先前出列的緩沖區。
-
perform()?:此函數用于執行各種操作,例如設置曲面的大小或更改顏色格式。?調用執行來自客戶端(而不是驅動程序)通過一組內聯包裝函數,如native_window_set_buffers_diemnsions()?。
從服務器的角度來看,它必須找到一些方法通過IPC將這些ANativeWindow結構傳遞給客戶端,并在客戶端實現函數指針。?這怎么發生并不重要。?結構中的所有內容都是基本的二進制數據和一些文件描述符。?顯示服務器還負責分配緩沖區并在需要時將它們交給EGL實現。?緩沖區由ANativeBuffer表示,它只是整數和文件描述符的集合。?使用Android的“gralloc”模塊分配緩沖區,這不在本文的討論范圍之內。?顯示服務器是否在服務器進程中分配緩沖區并將它們傳遞給客戶端或在客戶端進程中分配它們并將它們傳遞給服務器無關緊要。?關鍵是EGL實現可以通過ANativeWindow訪問緩沖區隊列,并使用gralloc分配它們。
值得注意的是,Android對如何調用這些函數的限制很少。?已經提到過,緩沖區必須按照它們出列的順序排隊或取消。?但是,Android沒有與輸入或窗口幾何體更改同步的要求。?實際上,在大多數Android應用程序中,每次幾何體因為設備旋轉或應用程序全屏而發生變化時,整個UI都會被破壞并重新創建。?這與Wayland的“每一幀都是完美的”口頭禪是截然不同的。
Wayland + Android
好吧,當我們采用這兩個并嘗試讓Wayland在Android世界中工作時會發生什么。?事實證明,這非常有效。?libhybris項目有一個實現,至少在某些硬件上運行得很好,?Jolla?(以及其他)正在運行在其上運行的設備。?雖然它在某些硬件上運行良好,但在一般情況下還遠非完美。?真的,這不是libhybris開發人員的錯,而是Wayland和Android思維方式的核心沖突。
libhybris的工作方式(以及任何其他尋求做同樣事情的實現)是通過編寫另一個meta-EGL層。meta-EGL提供了一個支持EGL_WL_bind_wayland_display擴展的EGL實現,并使用ANativeWindow下的ANativeWindow實現它。?ANativeWindow結構通常或多或少地實現如下:
-
dequeueBuffer()?:如果已存在足夠的曲面,請從隊列中抓取一個并將其交還給驅動程序。?如果它需要一個新的表面,它通過Wayland協議發送與驅動程序的服務器端的通信,以分配一個新的緩沖區并獲得一個wl_buffer對象。?客戶端是否從gralloc獲取緩沖區并將其交給服務器或服務器分配緩沖區并將其交給客戶端并不重要。?libhybris庫目前是前者,但后者也可以完成,我已經考慮過這樣做了。
-
queueBuffer()?:通常,這會調用attach(使用驅動程序給它的緩沖區),損壞和提交。?這是(sort-of)由驅動程序的SwapBuffers()函數調用的。
-
cancelBuffer()?:是的,這應該是顯而易見的。
-
perform()?:未使用。?這些操作可以通過Wayland協議直接完成,也可以通過作用于wl_egl_window結構的函數完成。
現在滾動回Wayland部分,看看每個支持Wayland的EGL實現應該提供的兩個要求。?第二個是相當容易的,因為Android已經提供了一個fence機制。?事實證明,第一個幾乎不可能實際滿足。
真正的問題來自于Android沒有提供關于驅動程序在eglSwapBuffers中必須做什么的真正保證。?在某些時候,驅動程序會將客戶端呈現的緩沖區放回到使用ANativeWindow.queueBuffer()的隊列中,但它可能會在將來的某個時刻或從另一個線程執行此操作。?更糟糕的是,在某些情況下可能根本不提交緩沖區。?通過將EGL_SWAP_BEHAVIOR屬性設置為EGL_BUFFER_PRESERVED?,或者通過使用EGL_EXT_swap_buffers_with_damage擴展,客戶端根本不進行渲染,然后調用eglSwapBuffers()是完全可以接受的。?在這種情況下,預計相同的圖像將顯示在屏幕上與前一個圖像相同。?但是,Wayland保證在每個eglSwapBuffers()調用上調用wl_surface.attach?,?wl_surface.damage和wl_surface.commit變得非常難以滿足。
我已經在libhybris中做了幾次嘗試來解決這個問題。?一種是使用EGL_KHR_fence_sync擴展來等待驅動程序EGL實現在執行提交之前實際將緩沖區提交給ANativeWindow?。?如果驅動程序EGL實現在同步返回時沒有為我們提供緩沖區,那么無論如何我們都會繼續提交。?不幸的是,這依賴于驅動程序EGL實現以我們期望的方式實現EGL_KHR_fence_sync,即不從eglClientWaitSyncKHR()返回,直到緩沖區實際被推送。?并非所有與Android兼容的驅動程序EGL實現都會這樣做。?Giulio Camuffo試圖使libhybris的實現更加智能,跟蹤驅動程序EGL實現從隊列中提取的緩沖區,并試圖猜測它將在下一個緩沖區中放入哪個緩沖區。?不幸的是,這些解決方案都不適用于所有驅動程序。
這是關于它的長期和短期。?這是一種不幸的情況。?如果您對如何使其變得更好有任何想法,我很樂意聽到它們。?或者甚至更好,只是開始攻擊libhybris,看看你是否可以改進它。?但是,請注意,對一個驅動程序起作用的東西可能完全打破另一個驅動程序。?歡迎來到硬件世界。?我希望你讀得好!
http://www.jlekstrand.net/jason/projects/wayland/wayland-android/
總結
以上是生活随笔為你收集整理的【译】Why Wayland on Android is a hard problem的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Building Android App
- 下一篇: Android 5.0 Usb调试拦截分