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中申请出来的图形缓冲区的文件描写叙述符和其他基本属性,这个文件描写叙述符会被同一时候映射到client和服务端。作为共享内存。

因为服务和client进程都能够訪问同一物理内存,因此不加同步的话会引起错误。为了协调client和服务端,在传输GraphicBuffer时。还带有Fence,标志了它是否被上一个使用者使用完毕。Fence按作用大体分两种:acquireFence和releaseFence。前者用于生产者通知消费者生产已完毕,后者用于消费者通知生产者消费已完毕。以下分别看一下这两种Fence的产生和使用过程。首先是acquireFence的使用流程:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamluemh1b2p1bg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

当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的更多相关文章

  1. Android 12(S) 图像显示系统 - GraphicBuffer同步机制 - Fence

    必读: Android 12(S) 图像显示系统 - 开篇 一.前言 前面的文章中讲解Android BufferQueue的机制时,有遇到过Fence,但没有具体讲解.这篇文章,就针对Fence这种 ...

  2. Android中的常见通信机制和Linux中的通信机制

    Handler Handler是Android系统中的一种消息传递机制,起作用是应对多线程场景.将A进程的消息传递给B线程,实现异步消息处理.很多情况是将工作线程中需要更新UI的操作消息传递给UI主线 ...

  3. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  4. 游戏中的网络同步机制——Lockstep(帧同步)

    本文来自: https://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockste ...

  5. Android 中View的绘制机制源代码分析 一

    尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113 差点儿相同半年没有写博客了,一是由于工作比較忙,二是认为没有什么内容值得写, ...

  6. Android 中View的绘制机制源代码分析 二

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要解说 ...

  7. Android中的事件分发机制

    Android中的事件分发机制 作者:丁明祥 邮箱:2780087178@qq.com 这篇文章这周之内尽量写完 参考资料: Android事件分发机制完全解析,带你从源码的角度彻底理解(上) And ...

  8. Android中的内存管理机制以及正确的使用方式

    概述 从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源.现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面来理解操 ...

  9. 分享:Android中利用机器码注册机制防止破解(转)

    转自:http://blog.csdn.net/huzgd/article/details/6684094 最近做一个Android应用时遇到这个问题,客户要求功能必须注册才能使用,而程序本身又不是联 ...

随机推荐

  1. 【Leetcode】474. Ones and Zeroes

    Today, Leet weekly contest was hold on time. However, i was late about 15 minutes for checking out o ...

  2. 【转】linux下passwd命令设置修改用户密码

    1.passwd 简单说明: 我们已经学会如何添加用户了,所以我们还要学习设置或修改用户的密码:passwd命令的用法也很多,我们只选如下的几个参数加以说明:想了解更多,请参考man passwd或p ...

  3. 警告视图及操作表单在xcode7.0中的使用

    警告视图(alert)及操作表单(action sheet)都用于向用户提供反馈.(模态视图) 操作表单:要求用户在两个以上选项之间做出选择.操作表单从屏幕底部出现,显示一系列按钮供用户选择.用户必须 ...

  4. Mac sierra下 wget安装

    本文由@ray 出品,转载请注明出处.  文章链接:http://www.cnblogs.com/wolfray/p/8040699.html 没有Wget的日子是非常难过的,强大的Mac OS 下安 ...

  5. [Windows Server 2012] 手工创建安全网站

    ★ 欢迎来到[护卫神·V课堂],网站地址:http://v.huweishen.com★ 护卫神·V课堂 是护卫神旗下专业提供服务器教学视频的网站,每周更新视频.★ 本节我们将带领大家:手工创建安全站 ...

  6. [Windows Server 2012] 手工破解MySQL密码

    ★ 欢迎来到[护卫神·V课堂],网站地址:http://v.huweishen.com★ 护卫神·V课堂 是护卫神旗下专业提供服务器教学视频的网站,每周更新视频.★ 本节我们将带领大家:破解MySQL ...

  7. css 众妙之门 学习笔记

    伪类: 结构伪类: :empty :only-child :before :after :active :hover :focus :link :visited :first-child :last- ...

  8. nagios插件nagiosql安装配置

    nagios插件nagiosql安装配置 # Nagiosql install [root@Cagios ~]# yum install -y libssh2 libssh-devel [root@C ...

  9. c#中动态创建textbox并且从数据库中获取表中数据添加到textbox中

    private void FormLugOther_Load(object sender, EventArgs e) { foreach (string str in FormLug.FieldLis ...

  10. 谷歌通过ajax获取本地JSON文件,为什么会提示跨域?

    在本地写了一段JSON代码,然后用ajax读取后,在浏览器打开,发现谷歌提示涉及到跨域问题, 但是跨域是由于协议,域名,端口中有一个不同,才会跨域,我在本地访问自己的文件,怎么和跨域扯上关系了?? 下 ...