GUI系统之SurfaceFlinger(7)应用程序的典型绘图流程

2024-04-22 17:32

本文主要是介绍GUI系统之SurfaceFlinger(7)应用程序的典型绘图流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章都是通过阅读源码分析出来的,还在不断完善与改进中,其中难免有些地方理解得不对,欢迎大家批评指正。
转载请注明:From LXS. http://blog.csdn.net/uiop78uiop78/

GUI系统之SurfaceFlinger章节目录:
blog.csdn.net/uiop78uiop78/article/details/8954508




1.1.1 应用程序的典型绘图流程

我们知道,BufferQueue有最多达32个BufferSlot,这样设计的目的是什么?一个可能的原因就是提高图形渲染速度。因为假如只有两个buffer,可以想象一下,当应用程序这个生产者的产出效率大于消费者的处理速度时,很快它就会dequeue完所有缓冲区而处于等待状态,从而导致不必要的麻烦。当然,实际上32只是最大的容量,具体值是可以设置的,大家可以结合后面的ProjectButter小节来理解一下。

前面小节我们已经学习了BufferQueue的内部原理,那么应用程序又是如何与之配合的呢?

解决这个疑惑的关键就是了解应用程序是如何执行绘图流程的,这也是本节我们叙述的重点。不过大家应该有个心里准备,应用程序并不会直接使用BufferQueue。和Android系统中很多其它地方一样,“层层包裹”在这里同样是存在的,因而我们要尽量抓住其中的重点,并辅以一定的手段,才能更好更快地从诸多错综复杂的类关系中找出问题的答案。

出于以上原因的考虑,我们选取系统的开机动画这一应用程序,来分析整个图形绘制的流程。值得一提的是,这个开机动画的实现符合前面提到的两个改进的图形系统中的第一个,即应用程序与SurfaceFlinger都是使用OpenGL ES来完成UI显示,不过因为它是一个C++程序,所以不需要上层GLSurfaceView的支持。

当一个Android设备上电后,正常情况下它会先后显示最多4个不一样的开机画面,分别是:

l  boot-loader

这显然是第一个出现的画面。因为boot-loader只是负责系统后续模块的加载与启动,而且要求文件体积很小,所以一般我们只让它显示一张静态的图片

l  kernel

在进入内核后,同样会在物理屏幕上有所显示。和boot-loader一样,默认情况下它也只是一张静态图片

l  android(2个)

Android是系统启动的最后一个阶段,也是最耗时间的一个。它的开机画面既可以是静态文字描述、静态图片,也可以是动态画面。通常第一个是文字或者静态图片(假如指定路径下的图片不存在的话,就显示文字。关于这方面的资料很多,大家可以自行查阅,我们这里不作过多叙述),另外一个则是动画,如下图所示:


图 11‑14 原生态Android系统中的开机动画

 

这个开机动画的实现类是BootAnimation,它的内部就是借助SurfaceFlinger来完成的。另外,因为它并不是传统意义上的Java层应用程序,这使得我们可以抛离很多上层的牵绊,以最直观的方式来审视BufferQueue的使用,是分析本节问题的最佳选择。

BootAnimation是一个C++程序,其工程源码路径是/frameworks/base/cmds/bootanimation。和很多native应用一样,它也是在init脚本中被启动的,大概来看下这一过程。

service bootanim /system/bin/bootanimation

    class main

    user graphics

    group graphics

    disabled

    oneshot

以上内容是从init.rc脚本中摘录出来的,完整地描述了bootanimation这个程序的启动属性。如果大家对其中的内容不清楚的话,可以参见本书的系统启动章节。

当bootanimation被启动后,它首先会进入main函数,即main@Bootanimation_main.cpp,生成一个BootAnimation对象,并开启线程池(因为它需要与SurfaceFlinger等系统服务进行跨进程的通信)。在BootAnimation的构造函数中,同时生成一个SurfaceComposerClient:

BootAnimation::BootAnimation() : Thread(false)

{

    mSession = newSurfaceComposerClient();

}

SurfaceComposerClient是每个UI应用程序与SurfaceFlinger间的独立纽带,后续很多操作都是通过它来完成的。不过这个类只是一个封装,真正起作用的还是其内部的ISurfaceComposerClient。更多的分析我们将放在后续小节中,这里只要先知道它的功能就可以了。值得一提的是,前面小节中我们讲到了ISurfaceTexture,这里又有一个ISurfaceComposerClient,两者有什么区别呢?

简单来说,ISurfaceTexture是应用程序与BufferQueue的传输通道,而ISurfaceComposerClient则是它与SurfaceFlinger间的桥梁。这样子的设计是合理的,体现了模块化的思想——SurfaceFlinger的职责是“Flinger”,即把系统中所有应用程序的最终的“绘图结果”进行“混合”,然后统一显示到物理屏幕上。它不应该,也没有办法分出太多的精力去一一关注各个应用程序的“绘画过程”。这个光荣的任务自然而然地落在了BufferQueue的肩膀上,它是每个应用程序“一对一”的辅导老师,指导着UI程序的“画板申请”、“作画流程”等一系列细节。下面的图描述了这三者的关系:

 


图 11‑15 应用程序、BufferQueue及SurfaceFlinger间的关系

 

所以BootAnimation在其构造函数中就建立了与SurfaceFlinger的联系通道。那么它在什么时候会再去建立与BufferQueue的连接呢?因为BootAnimation继承自RefBase,当main函数中通过sp指针引用它时,会触发如下函数:

void BootAnimation::onFirstRef() {

    status_t err =mSession->linkToComposerDeath(this);//监听死亡事件

    if (err == NO_ERROR) {

        run("BootAnimation", PRIORITY_DISPLAY);//开启线程

    }

}

当一个client与远程server端的建立了binder联系后,它就可以使用这个server的服务了,但前提是服务端正常运行——换句话说,假如出现了server异常的情况,client又如何知道呢?这就是linkToComposerDeath要解决的问题,它的第一个参数指明了接收binder server死亡事件的人,在这个例子中就是BootAnimation自身。这是因为它继承了IBinder::DeathRecipient,并实现了其中的binderDied接口。

如果上一步没有出错的话(err== NO_ERROR),接下来就要启动一个新线程来承载业务了。为什么需要独立创建一个新的线程呢?前面main函数中大家应该发现了BootAnimation启动了binder线程池,可以想象在只有一个线程的情况下,它是不可能既监听binder请求,又去做开机动画的绘制的。所以当一个新的线程被run起来后,又触发了下列函数的调用:

status_t BootAnimation::readyToRun() {…  

   /*第一部分,向server端获得buffer空间,从而得到EGL需要的本地窗口*/

    sp<SurfaceControl>control = session()->createSurface(0, dinfo.w,dinfo.h, PIXEL_FORMAT_RGB_565);

   SurfaceComposerClient::openGlobalTransaction();

    control->setLayer(0x40000000);

   SurfaceComposerClient::closeGlobalTransaction();

sp<Surface> s = control->getSurface();

 

    /*以下为第二部分,即EGL的使用流程*/

    const EGLint attribs[] = {…//属性值较多,节约篇幅,我们省略具体内容};

    EGLint w, h, dummy;

    EGLint numConfigs;//总共有多少个config

    EGLConfig config;

    EGLSurface surface;

    EGLContext context;

    EGLDisplay display =eglGetDisplay(EGL_DEFAULT_DISPLAY);//第一步,得到默认的物理屏幕

    eglInitialize(display, 0,0);//第二步,初始化

    eglChooseConfig(display,attribs, &config, 1, &numConfigs);//第三步,选取最佳的config

    surface =eglCreateWindowSurface(display, config, s.get(),NULL);//第四步,通过本地窗口创建Surface

    context =eglCreateContext(display, config, NULL, NULL);//第五步,创建context环境

    …

    if(eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)//第六步,设置当前环境

        return NO_INIT;

    …

    return NO_ERROR;

}

从这个函数中不但可以看出应用程序是如何使用BufferQueue的,而且还有另外一个重要的学习点,即Opengl ES与EGL的使用流程。在本书应用篇章中我们已经给出了EGL的使用实例,这里则可以做为第二个例子。

函数首先通过session()->createSurface()来获取一个SurfaceControl。其中session()得到的是mSession变量,也就是前面构造函数中生成的SurfaceComposerClient对象,所以createSurface()最终就是由SurfaceFlinger来实现的。只不过SurfaceFlinger中返回的其实是一个ISurface对象,本地端的SurfaceComposerClient又包装了一层,变成了SurfaceControl,言下之意就是对ISurface进行管理。大家肯定会有疑惑,ISurface从哪冒出来的,做什么的?要回答这点不难,只要看下SurfaceFlinger中最终是传了个什么对象过来就行:

sp<ISurface> SurfaceFlinger::createSurface(…)

{

                sp<LayerBaseClient>layer;

                sp<ISurface>surfaceHandle;

    //中间省略layer的生成过程

                surfaceHandle =layer->getSurface();

                returnsurfaceHandle;…

我们省略了中间一大段过程,只保留与问题相关的部分,更详细的分析可以参见后面的SurfaceFlinger小节。从中可以清楚看到,ISurface是通过layer->getSurface()得到的。Layer类在SurfaceFlinger中表示“层”的概念,而ISurface则是客户端与这个“层”进行沟通的通道。通俗地讲“层”就代表了一个“画面”,最终的显示结果就是通过对系统中同时存在的所有“画面”进行处理得到的。打个比方来说,就好像是一排人各举着一张绘画作品,那么观察者从最前面往后看时,他首先可以看到的就是第一张画。而假如第一张画恰好比第二张小,又或者第一张是透明/半透明的(这并非不可能,比如作者是在玻璃上创作的),那么他才能看到第二张画,以此类推。。。

这个比喻告诉我们,layer是有层级的,越靠近用户的那个“层”就越有优势。

明白了这个道理,函数接下来调用setLayer就不难理解了。不过参数中传入了一个数值0x40000000,这又是什么意思?其实这个值就是layer的层级,数字越大就越靠近用户,在显示系统中我们通常称为z-order。以后在Window Manager Service章节的分析中还会看到对setLayer的调用,因为此时系统中还只有开机画面一个应用程序,所以我们还不需要担心z-order的问题。

设置完层级后,我们接着control->getSurface()来得到一个Surface对象。相关的类越来越多了,而且由于命名上的不恰当,进一步加剧了大家理解上的困难。其实可以来猜测下这个类是做什么的?根据如下:

l  第二部分中的eglCreateWindowSurface使用了Surface,证明它必然是一个本地窗口

l  前几个小节我们介绍的两种本地窗口,一个是FramebufferNativeWindow,另一个是SurfaceTextureClient,那么看来Surface必然要与其中一个有关联。很显然的,运行于应用程序端的本地窗口必然是SurfaceTextureClient,我们可以从Surface的继承关系中得到验证:

class Surface : public SurfaceTextureClient

的确是太乱了,我们有必要先来整理下目前已经出现的容易混淆的相关类的关系。

ISurfaceComposerClient: 应用程序与SurfaceFlinger间的通道,在应用进程中则被封装在SurfaceComposerClient这个类中。这是一个匿名binder server,由应用程序(具体位置在SurfaceComposerClient::onFirstRef中)调用SurfaceFlinger这个实名binder的createConnection方法来获取到,服务端的实现是SurfaceFlinger::Client。

ISurface:由应用程序调用ISurfaceComposerClient::createSurface()得到,同时在SurfaceFlinger这一进程中将会有一个Layer被创建,代表了一个“画面”。ISurface就是控制这一画面的handle。

Surface:从逻辑关系上看,它是上述ISurface的使用者。从继承关系上看,它是一个SurfaceTextureClient,也就是本地窗口。SurfaceTextureClient内部持有ISurfaceTexture,即BufferQueue的实现接口。换个角度来思考,当EGL想通过Surface这个native window完成某些功能时,后者实际上又利用ISurface和ISurfaceTexture来取得远程服务端的对应服务,以完成EGL的请求。

回到BootAnimation::readyToRun()中来。因为本地窗口Surface已经成功创建,接下来就该EGL上场了,具体流程我们在代码中都加了注释,这里就不赘述了。

当EGL准备好环境后,意味着程序可以正常使用opengl ES提供的各种API函数进行绘图了。这部分实现就集中在随后的threadLoop()以及android()/movie()中。因为不属于本小节的讨论范围,有兴趣的读者可以自行参阅学习。

最后来做下小结,一个典型的应用程序使用SurfaceFlinger进行绘图的流程如下图所示:


图 11‑16 应用程序通过SurfaceFlinger进行绘图的典型流程

 

上图是从时序纵向角度总结出来的流程,我们再从横向的角度来看下,应该就更清楚了。


图 11‑17 横向角度考查Surface相关类的关系

 

可以看到,涉及到的类还是比较多的,而且多数都涉及到跨进程通信。希望大家能熟悉这两张图,在后几个小节的学习中,如果觉得混乱的时候也可以回头来看下,加深印象。


这篇关于GUI系统之SurfaceFlinger(7)应用程序的典型绘图流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/926416

相关文章

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Linux系统之dns域名解析全过程

《Linux系统之dns域名解析全过程》:本文主要介绍Linux系统之dns域名解析全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、dns域名解析介绍1、DNS核心概念1.1 区域 zone1.2 记录 record二、DNS服务的配置1、正向解析的配置

Linux系统中配置静态IP地址的详细步骤

《Linux系统中配置静态IP地址的详细步骤》本文详细介绍了在Linux系统中配置静态IP地址的五个步骤,包括打开终端、编辑网络配置文件、配置IP地址、保存并重启网络服务,这对于系统管理员和新手都极具... 目录步骤一:打开终端步骤二:编辑网络配置文件步骤三:配置静态IP地址步骤四:保存并关闭文件步骤五:重

Spring AI ectorStore的使用流程

《SpringAIectorStore的使用流程》SpringAI中的VectorStore是一种用于存储和检索高维向量数据的数据库或存储解决方案,它在AI应用中发挥着至关重要的作用,本文给大家介... 目录一、VectorStore的基本概念二、VectorStore的核心接口三、VectorStore的

python之流程控制语句match-case详解

《python之流程控制语句match-case详解》:本文主要介绍python之流程控制语句match-case使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录match-case 语法详解与实战一、基础值匹配(类似 switch-case)二、数据结构解构匹

Windows系统下如何查找JDK的安装路径

《Windows系统下如何查找JDK的安装路径》:本文主要介绍Windows系统下如何查找JDK的安装路径,文中介绍了三种方法,分别是通过命令行检查、使用verbose选项查找jre目录、以及查看... 目录一、确认是否安装了JDK二、查找路径三、另外一种方式如果很久之前安装了JDK,或者在别人的电脑上,想