上一篇文章介绍了整个Surface机制(我是这么称呼的,主要是Surface的建立,Surface的显示存储的管理),同时我们也介绍过了整个显示系统,那么这篇文章就介绍一下SurfaceFlinger 这个核心服务层的机制。
从代码中我们可以看出SurfaceFlinger 是一个thread,运行在system_server进程中,并且其threadLoop()方法的返回值为true,因此它是一个循环的loop。这样保证了SurfaceFlinger业务的循环周期性。
1. SurfaceFlinger的同步
SurfaceFlinger 并不是时时刻刻都在执行业务中,当WMS请求SurfaceFlinger创建Surface,或者WMS对Surface进行属性设置时,我们希望此时的SurfaceFlinger并不进行显示操作,以保证对Surface的线程保护,因此SurfaceFlinger
的loop中实现了同步机制。
主要的同步情况有如下几种,当然也有其他一些要求SurfaceFlinger同步的情况,不够对于研究SurfaceFlinger就不太重要了
1. 创建Surface同步
假如当前只有一个Client,比如WMS请求SufaceFlinger创建一个Surface,那么此时应该保持SurfaceFlinger loop处在block状态,因为这个过程涉及到对一些成员变量的处理,为了保证同步而需要hold住整个loop。
2. 设置Surface属性或SurfaceFlinger属性同步
创建完Surface之后,WMS会请求SurfaceFlinger对其Layer进行属性设置或者对SurfaceFlinger的属性进行设置,如下面概括图中SurfaceComposerClient中的函数接口。
3. Surface绘制同步
当ViewRoot对Surface进行绘制时,同样需要将SurfaceFlinger hold住,当整个窗口绘制完成之后,再向SurfaceFlinger发送signal信号。如下面时序图所示。

4. freeze/unfreeze同步
当每个Activity启动的时候,AMS都会请求WMS freeze整个屏幕,当Activity启动之后,再unfreeze整个屏幕,我猜测这么做的目的是为了保证在Activity以及Activity的窗口在创建过程中,对Activity窗口的Surface进行的线程保护,以免出现屏幕的闪烁等用户体验较差的现象。
2. Layer存储
在SurfaceFlinger中,Layer是怎么样存储的呢?所有的Layer,不论是那个Client创建的Layer,均保存在一个名为layersSortedByZ的变量中,也就是说WMS请求创建的Surface的Layer和其他Client请求创建的Layer都保存在layersSortedByZ中,但是layersSortedByZ保存过程中则遵守一定的规则。下面代码中的do_compare揭示了这个规则。
@SurfaceFlinger.h
每次向
layersSortedByZ中添加新的Layer,都会做一次排序,按照规则将其放在合适的位置。
1. 首先,按照Layer的Z-order值来排序,Z-order值小的,放在layersSortedByZ低索引值位置;
2. 其次,如果两个LayerZ-order值相同,sequence值小的,放在layersSortedByZ低索引值位置;
Z-order值如何确定?
WMS根据不同的Window Type来确定Z-order值,Z-order
= LAYER*TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET。
根据下面代码中的不同的Window Type的LAYER值,可以确定Z-order值,例如TYPE_APPLICATION窗口,其
Z-order = 2*10000+1000 = 21000。
@PhoneWindowManager.java
sequence值如何确定?
sequence值是根据Layer的创建的顺序来维护这个序列值,下面代码中的LayerBase的构造函数中的sequence值,每创建一个Layer,sSequence加一赋值给sequence。
@LayerBase.cpp
3.属性更新
这一节的所描述的实现都在函数handleTransactionLocked()中。
从下面概括图中可以看出,WMS可以对SurfaceFlinger进行属性设置,也可以对当前的Surface对应的Layer进行属性设置,因此handleTransactionLocked()函数就是对SurfaceFlinger属性和设置了新属性的Layer的属性更新。
SurfaceFlinger根据这个枚举值来确定handleTransactionLocked()需要更新SurfaceFlinger属性还是layer属性。
如果SurfaceFlinger属性被设置了新内容,则SurfaceFlinger会记录标志eTransactionNeeded;如果layer属性被设置了新内容,那么
SurfaceFlinger会记录标志eTraversalNeeded。handleTransactionLocked()通过记录的标志来执行各自的属性得更新。‘
这里提到的属性的更新,主要是看SurfaceFlinger或者laye新设置的属性与旧的属性相比,哪些属性做了修改,然后
记录下来,在接下来的SurfaceFlinger
loop中使用新的属性来显示图形。
类SurfaceFlinger 和Layer中各自定义了两个属性的变量,其中mCurrentState为新设置属性,mDrawingState为显示图形时用到的属性,一般为旧属性。不过类SurfaceFlinger 和Layer分别定义了不同的State类。
4. 图形缓存
这一部分的的实现在函数handlePageFlip()中。
有这么一种可能,当前显示到显示设备上的layer不止一个,而且layer是按照Z-Order的顺序来叠加到OpenGL的surface上的,那么这就需要layer的Z-Order值和坐标来确定每个layer能够被显示的区域。
4.1 page flip
前面一篇文章中介绍过,每个surface均有2个buffer供使用,一个作为FronteBuffer供SurfaceFlinger去显示,另外一个作为BackBuffer供ViewRoot去绘制窗口。因此在显示各个layer之前,我们需要做一个page flip过程,将当前的已经绘制了应用窗口的BackBuffer选择为FrontBuffer,用于显示;将之前的已经显示完成的FrontBuffer在重置为BackBuffer供ViewRoot去绘制。
而实现这个page flip的过程很简单
lockPageFlip()@Layer.cpp
SharedBufferServer::RetireUpdate::operator()@SharedBufferStack.cpp

4.2 纹理初始化
为每个Buffer的纹理进行初始化,为当前的纹理创建一个EGLImageKHR,将当前的Buffer最为该EGLImageKHR的源。这样OpenGL就可以进行纹理映射。
lockPageFlip()@Layer.cpp
4.3 计算显示区域
通过layer的叠加,我们可以计算出总的显示区域以及每个layer需要显示的区域,它的实现在computeVisibleRegions()函数中。这个函数主要计算了layer叠加后的总的显示区域,以及每个layer需要显示的区域。整个的计算过程比较简单,只是需要注意不透明区域的处理,computeVisibleRegions()需要计算出一个不透明区域,通过这个不透明区域验证WMS提供给layer的区域是否正确。即下面代码中的mWormholeRegion计算,mWormholeRegion为屏幕区域减去不透明区域,正常情况mWormholeRegion应该为空,即不透明区域范围应该为屏幕区域,如果不透明区域小雨屏幕区域,那么说明当前的应用程序出现了设置的错误。今天有个网友就出现了这个问题。
handlePageFlip()
在computeVisibleRegions()叠加计算总的显示范围,layer的计算顺序从上到下的过程计算的,也就是先计算Z-Order值较大的,显示在最上层的layer开始往下计算。这么做的好处就是能够很好的计算出不透明区域的范围。
在SurfaceFlinger的区域相互之间的操作处理如下:



4.4 图形缓存
前面选择了FrontBuffer、初始化了纹理、计算了layer的显示区域,那么下一步就该将Buffer内容进行图形处理并保存到OpenGL缓存中。
调用每个layer的draw函数来进行这个操作。如下面代码所示。具体的图形处理过程很复杂,完全交给OpenGL去处理,这里我们就不去关心了。我们只需要知道最终经过图形处理的内容会被缓存到OpenGL的缓存区中。
从前面的显示系统中,介绍过,Surface的缓存Buffer就是FramebufferNativeWindow中定义的2个Buffer,如果/dev/fb0读取设备信息,如果设备支持page flip,那么Surface的缓存Buffer即从/dev/fb0设备中申请;如果不支持,我们则需要从/dev/pmem中申请,同时/dev/fb0还会提供一个Buffer以便图形最终的显示。
/dev/fb0不支持page flip模式

/dev/fb0支持page flip模式

5. 图形显示
当图形内容被缓存到frameBuffer中后,最后的一步就是图形显示。代码中很明确就是SurfaceFlinger loop中的postFramebuffer()函数了。
这个函数最终回调到OpenGL的eglSwapBuffers()函数,这个函数主要有2个步骤(由于硬件加速代码不可见,我们仍然以软件加速为例)
1. 显示当前缓存buffer中内容;
首先,将原来的屏幕上的内容与最新需要显示的内容进行区域相减,将原来的内容copy到当前的缓存buffer中;
EGLBoolean egl_window_surface_v2_t::swapBuffers()@frameworks\base\opengl\libagl\egl.cpp
其次,如果当前的缓存buffer是申请自/dev/fb0,那么直接去显示这个缓存区中内容;如果
缓存buffer是申请自/dev/pmem,那么需要将缓存buffer中内容拷贝到/dev/fb0 buffer中去,其结构如上一节所示。
2. 对2个缓存buffer进行page flip(swap)操作。
通过queueBuffer()操作将将当前Buffer交还给FramebufferNativeWindow,同时调用fb_post进行图形显示。然后通过dequeueBuffer()操作获得另外一个FramebufferNativeWindow的缓存Buffer,实现page
flip(swap)操作。

至此,整个的SurfaceFlinger的机制就分析完了,下图是我总结的一个SurfaceFlinger结构的概括图:

分享到:
相关推荐
python学习资源
jfinal-undertow 用于开发、部署由 jfinal 开发的 web 项目
基于Andorid的音乐播放器项目设计(国外开源)实现源码,主要针对计算机相关专业的正在做毕设的学生和需要项目实战练习的学习者,也可作为课程设计、期末大作业。
python学习资源
python学习资源
python学习一些项目和资源
【毕业设计】java-springboot+vue家具销售平台实现源码(完整前后端+mysql+说明文档+LunW).zip
HTML+CSS+JavaScarip开发的前端网页源代码
python学习资源
【毕业设计】java-springboot-vue健身房信息管理系统源码(完整前后端+mysql+说明文档+LunW).zip
成绩管理系统C/Go。大学生期末小作业,指针实现,C语言版本(ANSI C)和Go语言版本
1_基于大数据的智能菜品个性化推荐与点餐系统的设计与实现.docx
【毕业设计】java-springboot-vue交流互动平台实现源码(完整前后端+mysql+说明文档+LunW).zip
内容概要:本文主要探讨了在高并发情况下如何设计并优化火车票秒杀系统,确保系统的高性能与稳定性。通过对比分析三种库存管理模式(下单减库存、支付减库存、预扣库存),强调了预扣库存结合本地缓存及远程Redis统一库存的优势,同时介绍了如何利用Nginx的加权轮询策略、MQ消息队列异步处理等方式降低系统压力,保障交易完整性和数据一致性,防止超卖现象。 适用人群:具有一定互联网应用开发经验的研发人员和技术管理人员。 使用场景及目标:适用于电商、票务等行业需要处理大量瞬时并发请求的业务场景。其目标在于通过合理的架构规划,实现在高峰期保持平台的稳定运行,保证用户体验的同时最大化销售额。 其他说明:文中提及的技术细节如Epoll I/O多路复用模型以及分布式系统中的容错措施等内容,对于深入理解大规模并发系统的构建有着重要指导意义。
基于 OpenCV 和 PyTorch 的深度车牌识别
【毕业设计-java】springboot-vue教学资料管理系统实现源码(完整前后端+mysql+说明文档+LunW).zip
此数据集包含有关出租车行程的详细信息,包括乘客人数、行程距离、付款类型、车费金额和行程时长。它可用于各种数据分析和机器学习应用程序,例如票价预测和乘车模式分析。
把代码放到Word中,通过开发工具——Visual Basic——插入模块,粘贴在里在,把在硅基流动中申请的API放到VBA代码中。在Word中,选择一个问题,运行这个DeepSeekV3的宏就可以实现在线问答
【毕业设计】java-springboot+vue机动车号牌管理系统实现源码(完整前后端+mysql+说明文档+LunW).zip
【毕业设计】java-springboot-vue交通管理在线服务系统的开发源码(完整前后端+mysql+说明文档+LunW).zip