【译】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_bufferEGLClientBuffer 。 然后,合成器可以使用glEGLImageTargetTexture2DOES()EGLImageKHR绑定为纹理。 它看起来有点复杂,但它非常简单:获取一个wl_buffer ,创建一个EGLImageKHR ,绑定为纹理。

但是从驾驶员的角度来看,这一切看起来如何呢? 这就是它开始变得复杂的地方......

eglBindWaylandDisplayWL()内部,驱动程序向wl_display添加至少一个全局,它提供了一些创建wl_buffer对象并将其与物理GPU缓冲区相关联的方法。 在客户端实现中,它获取GPU缓冲区,允许客户端呈现给它们,然后使用wl_surface.commit()将缓冲区交给服务器。eglSwapBuffers()调用的一般过程如下:

  1. 获取与客户端刚刚完成呈现的缓冲区对应的wl_buffer对象。
  2. 使用给定的缓冲区调用wl_surface.attach()。
  3. 调用wl_surface.damage()来破坏整个缓冲区。 (最好使用wl_surface.damage(INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX) 。)
  4. 调用wl_surface.commit()来应用客户端的更改

EGL实现调用wl_surface.commit()而不是将其留给客户端的原因是它允许客户端在简单的渲染循环中运行而不涉及实际的Wayland调用。 渲染所需的一切都隐藏在eglSwapBuffers() 。

到目前为止,还没那么糟糕。 事物变得多毛的是同步的。 需要eglSwapBuffers() (根据规范)执行隐式glFlus()操作,以保证客户端在调用eglSwapBuffers()之前呈现的所有内容都在屏幕上结束。 从服务器的角度来看,它从客户端获取一个wl_buffer ,并假设它可以自由地将其绑定为纹理并立即使用它进行渲染。 Wayland协议没有说缓冲和渲染同步 。 这听起来很糟糕,但它在现实中非常有效,因为它允许驱动程序使用它想要的任何同步原语,而不限制它现在必须实现的Wayland特定的同步对象。 为了正确处理同步,EGL实现为Wayland客户端/服务器提供以下两个保证:

  1. eglSwapBuffers(或eglSwapBuffersWithDamageEXT)必须在返回之前调用wl_display.attach,wl_display.damage和wl_display.commit。 这样客户端就可以将各种表面状态位与wl_display.commit请求同步。 它是否发生在另一个线程中并不重要,只是它发生在eglSwapBuffers reutnrs时。

  2. 只要合成器获得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)或类似事件非常有用。

扫描二维码关注公众号,回复: 3712218 查看本文章

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.damagewl_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/

猜你喜欢

转载自blog.csdn.net/omnispace/article/details/82882477