Android图形系统之HWComposer

Posted 飞天猫_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android图形系统之HWComposer相关的知识,希望对你有一定的参考价值。

android图形系统之HWComposer


1、HWC

HWChwcomposer)硬件组合抽象层,是Android中进行窗口(Layer)合成和显示的HAL层模块,其实现是特定于设备的,而且通常由显示设备制造商 (OEM)完成,为SurfaceFlinger服务提供硬件支持。

1.1 HWC作用

SurfaceFlinger可以使用OpenGL ES合成Layer,这需要占用并消耗GPU资源。大多数GPU都没有针对图层合成进行优化,因此当SurfaceFlinger通过GPU合成图层时,应用程序无法使用GPU进行自己的渲染。而HWC通过硬件设备进行图层合成,可以减轻GPU的合成压力。

1.2 Overlay

HWComposer机制最开的目的是为了替代Overlay的。Overlay技术:Overlay(覆盖)是一种数字视频的显示技术,它允许数字信号不经过显示芯片处理,而直接通过显存输出到显示器屏幕上。Overlay显示模式最大的用途在于优化视频播放。由于不同的视频有不同基准色调、亮度、对比度和饱和度,对于不同的电脑、不同的视频文件,为了获得最好的显示效果就需要对各种显示属性进行调节,普通显示模式显然无法胜任,所以就用到了Overlay显示模式进行单独调节。Overlay显示模式具有速度快、画质好、占用系统资源少等特点,很适合于视频播放。

1.3HWC实现

显示设备的能力千差万别,很难直接用API表示硬件设备支持合成的Layer数量,Layer是否可以进行旋转和混合模式操作,以及对图层定位和硬件合成的限制等。因此HWC描述上述信息的流程是这样的:

  1. SurfaceFlinger向HWC提供所有Layer的完整列表,让HWC根据其硬件能力,决定如何处理这些Layer。

  2. HWC会为每个Layer标注合成方式,是通过GPU还是通过HWC合成。

  3. SurfaceFlinger负责先把所有注明GPU合成的Layer合成到一个输出Buffer,然后把这个输出Buffer和其他Layer(注明HWC合成的Layer)一起交给HWC,让HWC完成剩余Layer的合成和显示。

1.4 HWC功能

虽然每个显示设备的能力不同,但是官方要求每个HWC硬件模块都应该支持以下能力:

  1. 至少支持4个叠加层:状态栏、系统栏、应用本身和壁纸或者背景。

  2. 叠加层可以大于显示屏,例如:壁纸

  3. 同时支持预乘每像素(per-pixel)Alpha混合和每平面(per-plane)Alpha混合。

  4. 为了支持受保护的内容,必须提供受保护视频播放的硬件路径

  5. RGBA packing order, YUV formats, and tiling, swizzling, and stride properties

Tiling:可以把Image切割成MxN个小块,最后渲染时,再将这些小块拼接起来,就像铺瓷砖一样。
Swizzling:一种拌和技术,表示向量单元可以被任意地重排或重复。

1.5 HWC效率

并非所有情况下HWC都比GPU更高效,例如:当屏幕上没有任何变化时,尤其是叠加层有透明像素并且需要进行图层透明像素混合时。在这种情况下,HWC可以要求部分或者全部叠加层都进行GPU合成,然后HWC持有合成的结果Buffer,如果SurfaceFlinger 要求合成相同的叠加图层列表,HWC可以直接显示之前合成的结果Buffer,这有助于提高待机设备的电池寿命。

1.6 Compose方式

目前SurfaceFlinger中支持两种合成方式,一种是Device合成,一种是Client合成。SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成。

Client合成方式是相对与硬件合成来说的,其合成方式是,将各个Layer的内容用GPU渲染到暂存缓冲区中,最后将暂存缓冲区传送到显示硬件。这个暂存缓冲区,我们称为FBTarget,每个Display设备有各自的FBTarget。Client合成,之前称为GLES合成,我们也可以称之为GPU合成。Client合成,采用RenderEngine进行合成。

Device合成就是用专门的硬件合成器进行合成HWComposer,所以硬件合成的能力就取决于硬件的实现。其合成方式是将各个Layer的数据全部传给显示硬件,并告知它从不同的缓冲区读取屏幕不同部分的数据。HWComposer是Devicehec的抽象。

2、Fence

Fence是一种同步机制,在Android里主要用于图形系统中GraphicBuffer的同步。

2.1 Fence作用

Fence和已有同步机制相比,它主要被用来处理跨硬件的情况,尤其是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

2.1 Fence实现

Fence,顾名思义就是把先到的拦住,等后来的,两者步调一致了再往前走。抽象地说,Fence包含了同一或不同时间轴上的多个时间点,只有当这些点同时到达时Fence才会被触发。

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,新加了接口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。

2.1 Fence流程

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()作为纹理进行合成。合成完后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) undefined
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触发才能使用。

引用
https://blog.csdn.net/cosmoslhf/article/details/49925317?locationnum=1&fps=1
http://netaz.blogspot.com/2013/10/android-fences-introduction-in-any.html
http://www.khronos.org/registry/vg/extensions/KHR/EGL_KHR_fence_sync.txt
http://snorp.net/2011/12/16/android-direct-texture.html

Android 进阶——图形显示系统之底层图像显示原理小结

文章大纲

引言

在Android中,当我们谈到 布局优化卡顿优化 时,通常都知道 需要减少布局层级、减少主线程耗时操作,这样可以减少丢帧。如果丢帧比较严重,那么界面可能会有明显的卡顿感。我们知道 通常手机刷新是每秒60次,即每隔16.6ms刷新一次。,接下来系列文章将全面介绍图形显示和绘制的相关知识,包括Choreographer机制等等。

一、图形显示系统概述

一个典型的显示系统中一般由CPU、GPU、Display三个部分组成,其中 CPU负责计算帧数据,把计算好的数据交给GPU,GPU会对图形数据进行栅格化处理和渲染,渲染好后放到Frame Buffer(图像缓冲区)里存起来,然后Display(屏幕或显示器)负责把Frame Buffer里的数据呈现到屏幕上。曾经在一篇文章看过一个生动的比喻,如果把应用程序图形渲染过程当作一次绘画过程,那么绘画过程中 Android 的各个图形组件的作用是:

  • 画笔——Skia 或者 OpenGL。我们可以用 Skia 画笔绘制 2D 图形,也可以用 OpenGL 来绘制 2D/3D 图形。正如前面所说,前者使用 CPU 绘制,后者使用 GPU 绘制。
  • 画纸——Surface。所有的元素都在 Surface 这张画纸上进行绘制和渲染。在 Android 中,Window 是 View 的容器,每个窗口都会关联一个 Surface。而 WindowManager 则负责管理这些窗口,并且把它们的数据传递给 SurfaceFlinger。
  • 画板——Graphic Buffer。Graphic Buffer 缓冲用于应用程序图形的绘制,在 Android 4.1 之前使用的是双缓冲机制;在 Android 4.1 之后,使用的是三缓冲机制。
  • 显示——SurfaceFlinger,它将 WindowManager 提供的所有 Surface通过硬件合成器 Hardware Composer 合成并输出到显示屏。

二、图形显示系统基础理论

1、屏幕刷新率

Linux 通常使用Frame Buffer(一块显示驱动程序内部缓冲区内存的映射区域)来缓存显示的数据,一旦用户进程把图像数据复制到Frame Buffer中,显示驱动程序就会一个像素一个像素地扫描整个Frame Buffer,并根据其中的值更新屏幕上的像素点的颜色,显示驱动程序的更新动作是固定且重复的(与硬件相关),整个更新周期就是传说中的刷新率

2、逐行扫描

显示器并不是一次性将画面显示到屏幕上,而是从左到右边,从上到下逐行扫描,顺序显示整屏的一个个像素点,不过这一过程快到人眼无法察觉到变化。以 60 Hz 刷新率的屏幕为例,这一过程即 1000 / 60 ≈ 16ms。

3、帧率 (Frame Rate)

表示 GPU 在一秒内绘制操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟GPU最多绘制 60 帧画面。帧率是动态变化的,例如当画面静止时,GPU 是没有绘制操作的,屏幕刷新的还是buffer中的数据,即GPU最后操作的帧数据。

4、画面闪烁(撕裂)的原因

当屏幕更新到一半的时候用户进程更新了Frame Buffer 中的数据,将导致屏幕上画面的上部分是前一帧的画面,下半部分变成了新的画面,于是就会让用户觉得画面有闪烁感(虽然会在下一次刷新时自动纠正过来)。因为图像绘制和屏幕读取操作使用的是同个Frame Buffer,导致屏幕刷新时可能读取到的是不完整的一帧画面

5、双缓冲和三缓冲Frame Buffer机制

为了处理的画面闪烁感,引入了双缓冲Frame Buffer机制,即驱动程序从自己的内部缓冲区中提供了两块映射到内存的区域,绘制计算(CPU/GPU)操作共享一个Frame Buffer,显示器拥有独立的 Buffer(暂且命名为Display Buffer),GPU 处理完成后将一帧图像数据写入到 Back Buffer,当屏幕正在刷新时,Display Buffer 并不会发生变化,当且仅当Back Buffer准备就绪后,通过ioctl操作进行交换再告诉显示设备切换用于显示的Frame Buffer才会切换。但如果切换的时间点不对,在画面更新到一半时切换,还是不可避免地产生画面闪烁的异常。简而言之,双缓存是指CPU/GPU共享一个Frame Buffer,显示器独享一个Buffer并从里面读取数据显示。但CPU/GPU的处理时间较长超过了16ms的话,双缓冲机制还是会产生较多的Jank,于是三缓冲应运而生,即在双缓冲机制基础上增加了一个 Graphic Buffer 缓冲区,CPU和GPU 各自拥有独立的Buffer,这样可以最大限度的利用空闲时间,带来的坏处是多使用的一个 Graphic Buffer 所占用的内存。当接到VSync脉冲时Display Buffer 可以选择Frame Buffer 和 Graphic Buffer中已经准备好数据的进行交换。

三、VSync (VerticalSynchronization)

1、VSync 概述

虽然前面引入了双缓冲机制,但是依然存在切换时间点不准确导致画面闪烁的现象,虽然可以在底层通过代码逻辑去判断,但是通过ioctl轮询Frame Buffer的状态效率很低,为此采取了底层驱动固定地发送VSync信号给用户进程,用户进程接到VSync信号时就知道该切换了。垂直同步VSync的缩写),它利用VBI时期出现的vertical sync pulse(垂直同步脉冲)来保证双缓冲在最佳时间点才进行交换(是指各自的内存地址,可以认为该操作是瞬间完成)。虽然双缓存会在VSync脉冲时交换,但CPU/GPU绘制时机是随机的。

假如是 Frame buffer准备完成一帧数据以后就进行,那么如果此时屏幕还没有完整显示上一帧内容的话,肯定是会出问题的,看来只能是等到屏幕处理完一帧数据后,才可以执行这一操作了。当扫描完一个屏幕后,设备需要重新回到第一行以进入下一次的循环,此时有一段时间空隙,称为Vertical Blanking Interval(VBI),则这个时间点就是我们进行缓冲区交换的最佳时间。因为此时屏幕没有在刷新,也就避免了交换过程中出现 screen tearing的状况。

2、Android 图形界面刷新机制

Android中为了确定缓冲交换时间引入了VSync 机制,由底层模拟VSync 信号一直固定发出,当用户进程接到时就开始渲染处理,而引入缓冲机制是为了更高效和顺滑,简而言之,简而言之,当VSync信号到来时且缓冲区数据准备完毕后,就会进行缓存交换

2.1、没有VSync 的绘制过程

如图所示在第一个时间周期(即两个VSync之间的间隔)Display显示第0帧数据,因为此时CPU已经准备好数据,GPU正好在CPU准备好后开始处理且在第一个周期内完成,Display正常渲染第0帧画面,在第二个周期的时候由于某种原因第二祯数据CPU处理比较晚,GPU在CPU处理完成后再去处理,导致GPU处理完成时已经超过了第二个时间周期,但是Display 刷新率是固定的于是只能显示第0帧,就产生了所谓的Jank 丢帧现象。

"丢"帧(掉帧)表示这一帧延迟显示,另外

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sPGmaUJc-1649942747293)(图像显示.assets/image-20220309152811009.png)]

Display 表示显示设备,上面的数字表示图像祯序号,GPU方块表示GPU正在准备数据,CPU方块表示CPU正在准备数据。

2.2、结合单缓冲机制+VSync 的绘制过程

系统在收到VSync pulse后,将马上开始下一帧的渲染,即一旦收到VSync信号(16ms触发一次),CPU和GPU 才立刻开始计算然后把数据写入Back Buffer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DnuTiMz2-1649942747294)(图像显示.assets/image-20220309154526576.png)]

可以让CPU/GPU有完整的16ms时间来处理数据,减少了jank。当VSync到来时进行双缓存的交换,交换后屏幕会取Display Buffer内的新数据,而实际此时的Back buffer 就可以供GPU准备下一帧数据了。

2.3、结合双缓冲机制+VSync 的绘制过程

如果 VSync到来时 CPU/GPU就开始操作的话,一个完整时间周期是16ms,这样应该会基本避免Jank的出现了,但CPU/GPU计算超过了16ms,如下图所示双缓冲机制的时候

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xvwJJQ5F-1649942747294)(图像显示.assets/image-20220309155740990.png)]

2.4、结合三缓冲机制+VSync 的绘制过程

由于CPU/GPU处理耗时过长(超过了一个周期),当第一个VSync信号到来时,缓冲区B中的数据还未准备完毕,Display未能完成交换只能继续显示缓冲区A中的内容,此时缓冲区A、B分别被Display 和GPU占用了,CPU在第二个VSync时无法开始准备下一祯数据而只能空闲运行,当下一个VSync信号来临时,Display 与B完成缓冲区交换,CPU才能继续处理下一祯数据,导致的结果就是相当于把屏幕的刷新率降低了。因为在第一个周期时原本应该显示第二祯的又多显示了第一帧。究其原因就是因为当两个Buffer都被占用,CPU 则无法准备下一帧的数据。那么如果再提供一个Buffer,有三个Buffer可供CPU、GPU 和Display Buffer使用,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eEvjxsk6-1649942747296)(图像显示.assets/image-20220309162408515.png)]

  • 在第一个VSync时,虽然GPU还占用B缓冲区的数据还在处理,也Display只能显示A缓冲区的数据(但是C缓冲区还空闲)CPU 占用C缓冲区处理数据;
  • 当第二个VSync时,B缓冲区数据处理完毕,Display 切换(与GPU缓冲区交换后,相当于**Display 占用B,GPU占用A,CPU占用C**)到B缓冲区并显示,与此同时CPU处理C的数据完成后与GPU交换,GPU处理C缓冲区,CPU继续使用A缓冲区处理数据;
  • 当第三个VSync到来时,Display切换到C 缓冲区(因为在第二个周期内GPU处理好了C缓冲),Display 切换到C ,A 缓冲被GPU占用,CPU使用B缓冲,如此循环,除了第一帧可能不可避免地产生“Jank“后续的帧显示效果都比较理想。

四、Handler异步消息与同步屏障机制

在Handler中Message按照属性不同可以分为**:异步消息同步消息同步屏障消息**,本质上都是Message,只不过机制有所差别。


    /**
     * Use the @link Looper for the current thread with the specified callback interface
     * and set whether the handler should be asynchronous.
     *
     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by @link MessageQueue#enqueueSyncBarrier(long).
     *
     * @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls @link Message#setAsynchronous(boolean) for
     * each @link Message that is sent to it or @link Runnable that is posted to it.
     *
     * @hide
     */
    public Handler(Callback callback, boolean async) 
        if (FIND_POTENTIAL_LEAKS) 
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) 
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            
        

        mLooper = Looper.myLooper();
        if (mLooper == null) 
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    

Handler的构造参数时有两个成员:消息处理回调和否是同步的消息的标识,标识为false就是同步,true就是异步。可以看到标识最终保存到成员变量mAsynchronous中,很明显其他成员方法也会使用,enqueueMessage时会给Message的flag 赋值。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 
        //将Handler赋值给Message的target变量
    	  msg.target = this;
    		//mAsynchronous为false,为同步消息
        if (mAsynchronous) 
            msg.setAsynchronous(true);
        
        return queue.enqueueMessage(msg, uptimeMillis);
    

1、创建异步消息的Handler

false就表示 非异步,即使用的是同步消息,mAsynchronous使用是在enqueueMessage()

   public Handler() 
        this(null, false);
    

2、MessgeQueue#postSyncBarrier向消息队列插入同步屏障消息

同步消息的Handler肯定就是传入的async的值为true,既然消息处理都大同小异肯定需要插入一个机制来实现屏障的功能


    /**
     * Posts a synchronization barrier to the Looper's message queue.
     * @hide
     */
    public int postSyncBarrier() 
        return postSyncBarrier(SystemClock.uptimeMillis());
    
  private int postSyncBarrier(long when) 
        synchronized (this) 
            final int token = mNextBarrierToken++;
            //重点是这里没有tartget赋值
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) 
                while (p != null && p.when <= when) 
                    prev = p;
                    p = p.next;
                
            
            if (prev != null)  // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
             else 
                msg.next = p;
                mMessages = msg;
            
            return token;
        
    

postSyncBarrier()返回一个int类型的数值,通过这个数值可以撤销屏障即removeSyncBarrier(),postSyncBarrier()是私有的,如果我们想调用它就得使用反射。

3、同步屏障的基本原理

同步屏障消息是主要就是挡住普通消息来保证异步消息优先处理的,我们都知道MessageQueue的next()方法是读取消息后才能被处理的,遍历消息队列时,发现了同步屏障消息,那么就只取异步消息了

    //MessageQueue.java
    Message next() 
        ...
        for (;;) 
            ...
            synchronized (this) 
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) 
                    // msg.target == null 就是同步屏障消息,那么只取异步消息
                    do 
                        prevMsg = msg;
                        msg = msg.next;
                     while (msg != null && !msg.isAsynchronous());
                
                if (msg != null) 
                    if (now < msg.when) 
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                     else 
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) 
                            prevMsg.next = msg.next;
                         else 
                            mMessages = msg.next;
                        
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    
                 else 
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                
                ...
        
    

4、屏障消息和普通消息

  • 屏障消息和普通消息的区别在于屏障没有tartget,而普通消息有target。(因为它需要将消息分发给对应的target)
  • 屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的
  • 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发
  • 插入普通消息会唤醒消息队列,但是插入屏障不会。

以上是关于Android图形系统之HWComposer的主要内容,如果未能解决你的问题,请参考以下文章

Android 进阶——图形显示系统之底层图像显示原理小结

Android 进阶——图形显示系统之底层图像显示原理小结

Android图形系统之SurfaceSurfaceViewSurfaceHolder及SurfaceHolder.Callback之间的联系

Android图形系统之SurfaceSurfaceViewSurfaceHolder及SurfaceHolder.Callback之间的联系

Android图形显示之硬件抽象层Gralloc

Android官方开发文档Training系列课程中文版:OpenGL绘图之图形绘制