Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU

Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU

Android的每个View都会经过Measure和Layout来确定当前需要绘制的View所在的大小和位置,Android系统中整体的绘制源码是在ViewRootImpl类的performTraversals()方法,通过这个方法Measure和Layout递归获取View的大小和位置,并且以深度作为优先级。显然,层级越深,元素越多,耗时就越长。

对于绘制,Android支持两种绘制方式:

软件绘制(CPU)

硬件绘制(GPU)

硬件加速从Android 3.0开始支持,它在UI显示和绘制效率方面远高于软件绘制。但它的局限如下:

耗电:GPU功耗高于CPU。

兼容性:不兼容某些接口和函数。

内存大:使用OpenGL的接口需要占用内存8MB。

将数据渲染到屏幕上是通过系统进程SurfaceFlinger实现,主要流程:

1、响应客户端事件,创建Layer与客户端的Surface建立连接。

2、接收客户端数据和属性,修改Layer属性,如尺寸、颜色、透明度等。

3、将创建的Layer内容刷新到屏幕上。

4、维持Layer的序列,并对Layer最终输出做出裁剪计算。

其中,SurfaceFlinger系统进程和应用进程使用了匿名共享内存SharedClient,并且,每一个应用和SurfaceFlinger之间都会创建一个SharedClient,并且,每个SharedClient中,最多可以创建31个SharedBufferStack,每一个SharedBufferStack对应一个Surface,即一个window。因此,一个Android应用程序最多可以包含31个窗口。最后,显示的整体流程如下:

应用层绘制到缓冲区

SurfaceFlinger把缓冲区数据渲染到屏幕,其中使用了Android匿名共享内存SharedClient缓存需要显示的数据来达到目的。

绘制的过程首先是CPU准备数据,其中CPU主要负责Measure、Layout、Record、Execute的计算工作,GPU负责Rasterization(栅格化)、渲染。因为图形API不允许CPU直接和GPU通信,所以要通过一个图形驱动的中间层来进行连接。图形驱动里面维护了一个队列,CPU把Display List添加到队列中,GPU从这个队列中取出数据进行绘制,最终才在显示屏上显示出来。

GPU硬件加速渲染中,Android app的UI渲染分两步进行。

第一步是构建Display List,发生在应用程序进程的Main Thread;

第二步是渲染Display List,发生在应用程序进程的Render Thread。

Android应用程序窗口中,每一个使用GPU硬件加速的View都抽象为一个Render Node,而且如果一个View设置有Background,这个Background也被抽象为一个Render Node。这是由于在OpenGLRenderer库中,并没有View的概念,所有的一切可绘制的元素都抽象为一个Render Node。

每一个Render Node都关联有一个Display List Renderer。这又涉及到另外一个概念------Display List。注意,这个Display List不是Open GL里面的Display List,不过它们概念上差不多的。Display List是一个绘制命令缓冲区。也就是说,当View的成员函数onDraw被调用时,调用通过参数传递进来的Canvas的draw函数绘制图形时,实际上只是将对应的绘制命令以及参数保存在一个Display List中,此时并没有真正的去绘制,真正的渲染是调用Display List Replay。

只有GPU硬件加速渲染的View,才会关联Render Node,也才会用到Display List。对于使用了软件方式渲染的View,具体的做法是创建一个新的Canvas,这个Canvas的底层是一个Bitmap,也就是说,绘制都发生在这个Bitmap上。绘制完成之后,这个Bitmap再被记录在其Parent View的Display List中。而当Parent View的Display List的命令被执行时,记录在里面的Bitmap再通过Open GL命令绘制。

另一方面,对于TextureView,它也不是通过Display List来绘制。由于它的底层实现直接就是一个Open GL纹理,因此就可以跳过Display List这一中间层,从而提高效率。这个Open GL纹理的绘制通过一个Layer封装。Layer和Display List Renderer可以看作是同一级别的概念,它们都是通过Open GL命令来绘制UI元素的。只不过前者操作的是Open GL纹理,而后者操作的是Display List。

Android应用程序窗口的View通过树形结构来组织。这些View不管是通过硬件加速渲染还是软件渲染,或者是一个特殊的TextureView,在它们的成员函数onDraw被调用期间,它们都是将自己的UI绘制在Parent View的Display List中。其中,最顶层的Parent View是一个Root View,它关联的Root Node称为Root Render Node。也就是说,最终Root Render Node的Display List将会包含有一个窗口的所有绘制命令。在绘制窗口的下一帧时,Root Render Node的Display List都会通过一个Open GL Renderer真正地通过Open GL命令绘制在一个Graphic Buffer中。最后这个Graphic Buffer被交给SurfaceFlinger进行合成和显示。

引入Display List概念有什么好处呢?

第一个是在下一帧绘制中,如果一个View的内容不需要更新,那么就不用重建它的Display List,也就是不需要调用它的onDraw成员函数。

第二个好处是在下一帧中,如果一个View仅仅是一些简单的属性发生变化,例如位置和Alpha值发生变化,那么也无需要重建它的Display List,只需要在上一次建立的Display List中修改一下对应的属性就可以了,这也意味着不需要调用它的onDraw成员函数。这两个好处在绘制应用程序窗口的一帧时,省去很多应用程序代码的执行,也就是大大地节省了CPU的时间。

不同于任何其他View,TextureView直接使用OpenGL纹理,一般用于游戏或Camera。

TextureView的UI通过一个HardwareLayer来描述,该HardwareLayer可以通过TextureView类的成员函数getHardwareLayer获得,主要目的为创建一个关联的Layer(和SurfaceFlinger的Layer概念不同)以及为其生成一个OpenGL纹理,可见TextureView是通过Open GL纹理来实现的。

DisplayList渲染发生在应用程序进程的Render Thread中,DisplayList渲染函数最终调用syncAndDrawFrame。函数drawFrame最主要的操作就是调用另外一个成员函数postAndWait往Render Thread的Queue抛一个消息,并且进入睡眠状态,等待Render Thread在合适的时候唤醒。

任务可分为2个:

  1. 将Main Thread维护的Display List同步到Render Thread维护的Display List去,这个同步过程由Render Thread执行,但是Main Thread会被阻塞住。如果能够完全地将Main Thread维护的Display List同步到Render Thread维护的Display List去,那么Main Thread就会被唤醒,此后Main Thread和Render Thread就互不干扰,各自操作各自内部维护的Display List这意味着Render Thread在渲染应用程序UI当前帧的Display List同时,Main Thread可以去准备应用程序UI下一帧的Display List,这样就使得应用程序窗口的UI更流畅。否则的话,Main Thread就会继续阻塞,直到Render Thread完成应用程序窗口当前帧的渲染为止;同步过程主要由syncFrameState函数实现

2.对RootRenderNode的Display List进行渲染,就可以得到整个Android应用程序窗口的UI。主要由context->draw()完成,前提是当前帧能够进行绘制,什么时候当前帧不能够进行绘制呢?应用程序进程绘制好一个窗口之后,得到的图形缓冲区要交给Surface Flinger进行合成,最后才能显示在屏幕上。Surface Flinger为每一个窗口都维护了一个图形缓冲区队列。当这个队列等待合成的图形缓冲区的个数大于等于2时,就表明Surface Flinger太忙了。因此这时候就最好不再向它提交图形缓冲区,这就意味着应用程序窗口的当前帧不能绘制了,也就是丢帧,这个判断机制也在syncFrameState函数。

Display List引用的Bitmap的同步方式与Display List和Render Property的同步方式有所不同。在同步Bitmap的时候,Bitmap将作为一个Open GL纹理上传到GPU去被Render Thread使用,因为Render Thread通过已上传到GPU的Open GL纹理来使用这些Bitmap。

当这个TreeInfo对象的成员变量prepareTextures的值等于true时,表示应用程序窗口的Display List引用到的Bitmap均已作为Open GL纹理上传到了GPU。这意味着应用程序窗口的Display List引用到的Bitmap已全部同步完成。在这种情况下,Render Thread在渲染下一帧之前,就可以唤醒Main Thread。另一方面,如果上述TreeInfo对象的成员变量prepareTextures的值等于false,就意味着应用程序窗口的Display List引用到的某些Bitmap不能成功地作为Open GL纹理上传到GPU,这时候Render Thread在渲染下一帧之后,才可以唤醒Main Thread,防止这些未能作为Open GL纹理上传到GPU的Bitmap一边被Render Thread渲染,一边又被Main Thread修改。

Display List引用的Bitmap保存在它的成员变量ownedBitmapResources和bitmapResources的两个Vector中。

ownedBitmapResources:该列表中Bitmap的底层储存是由应用程序提供和管理的。这意味着很难维护该底层储存在Main Thread和Render Thread的一致性,这时候就需要将参数info指向的一个TreeInfo对象的成员变量prepareTextures的值设置为false,这样canUnblockUiThread就为false。

bitmapResources:该列表中Bitmap的底层储存不是由应用程序提供和管理的,因此就能够保证它不会被随意修改而又不通知Render Thread进行同步。对于这些Bitmap,就可以将它们作为Open GL纹理上传到GPU去。并不是所有的这些Bitmap都是能够作为Open GL纹理上传到GPU去的,有两个原因:

一、Bitmap太大,超出预先设定的最大Open GL纹理的大小。这种情况通过调用TextureCache类的成员函数canMakeTextureFromBitmap进行判断。

二、已经作为Open GL纹理上传到GPU的Bitmap太多,超出预先设定的最多可以上传到GPU的大小。

一旦某个Bitmap不能作为Open GL纹理上传到GPU去,那么也是需要完全同步Main Thread和Render Thread渲染应用程序窗口的一帧的。这时候也需要将参数info指向的一个TreeInfo对象的成员变量prepareTextures的值设置为false,这样canUnblockUiThread也为false。

Android应用程序窗口UI的视图是树形结构。渲染时候,先绘制父视图的UI,再绘制子视图的UI。可以把这种绘制模式看作是分层的,即先绘制背后的层,再绘制前面的层,这是通过defer函数实现的。

RecordingCanvas: 之前Java层的DisplayListCanvas对应native层的DisplayListCanvas。引入RecordingCanvas后,其在native层的对应物就变成了RecordingCanvas。和DisplayListCanvas类似,画在RecordingCanvas上的内容都会被记录在RenderNode的DisplayList中。

BakedOpRenderer: 顾名思义,就是用于绘制batch/merge好的操作。用于替代之前的OpenGLRenderer。它是真正用GL绘制到on-screen surface上的。

相关:

https://zhangphil.blog.csdn.net/article/details/149907463

https://zhangphil.blog.csdn.net/article/details/153739120

相关推荐
火柴就是我2 小时前
从头写一个自己的app
android·前端·flutter
lichong9513 小时前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端
用户69371750013843 小时前
14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)
android·后端·kotlin
火柴就是我3 小时前
NekoBoxForAndroid 编译libcore.aar
android
Kaede64 小时前
MySQL中如何使用命令行修改root密码
android·mysql·adb
明君879976 小时前
Flutter 图纸标注功能的实现:踩坑与架构设计
android·ios
成都大菠萝6 小时前
Android Auto开发(3)-Audio Integration
android
成都大菠萝6 小时前
Android Auto开发(5)-Audio Integration
android
泡沫·6 小时前
7.LAMPLNMP 最佳实践
android