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