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

相关推荐
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android