admin 管理员组文章数量: 1184232
| 方案 | 原理 | 特点 |
| 使用PNG图片(BitmapDrawable) | 解码PNG图片生成Bitmap,传到底层,由GPU渲染 | 图片解码消耗CPU运算资源,Bitmap占用内存大,绘制慢 |
| 使用XML或Java代码实现(ShapeDrawable) | 直接将Shape信息传到底层,由GPU渲染 | 消耗CPU资源少,占用内存小,绘制快 |
页面渲染背景知识
- 页面渲染时,被绘制的元素最终要转换成矩阵像素点(即多维数组形式,类似 中的Bitmap),才能被显示器显示。
- 页面由各种基本元素组成,例如圆形、圆角矩形、线段、文字、矢量图(常用贝塞尔曲线组成)、Bitmap等。
- 元素绘制时尤其是动画绘制过程中,经常涉及插值、缩放、旋转、透明度变化、动画过渡、毛玻璃模糊,甚至包括3D变换、物理运动(例如游戏中常见的抛物线运动)、多媒体文件解码(主要在桌面机中有应用,移动设备一般不用GPU做解码)等运算。
- 绘制过程经常需要进行逻辑较简单、但数据量庞大的浮点运算。
CPU与GPU结构对比
- 黄色的Control为控制器,用于协调控制整个CPU的运行,包括取出指令、控制其他模块的运行等;
- 绿色的ALU(Arithmetic Logic Unit)是算术逻辑单元,用于进行数学、逻辑运算;
- 橙色的Cache和DRAM分别为缓存和RAM,用于存储信息。
-
从结构图可以看出,CPU的控制器较为复杂,而ALU数量较少。因此CPU擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算。
- 以8086为例,一百多条汇编指令大部分都是逻辑指令,数学计算相关的主要是16位加减乘除和移位运算。一次整型和逻辑运算一般需要1~3个机器周期,而浮点运算要转换成整数计算,一次运算可能消耗上百个机器周期。
- 更简单的CPU甚至只有加法指令,减法用补码加法实现,乘法用累加实现,除法用减法循环实现。
- 现代CPU一般都带有硬件浮点运算器(FPU),但主要适用于数据量不大的情况。
- CPU是串行结构。以计算100个数字为例,对于CPU的一个核,每次只能计算两个数的和,结果逐步累加。
- 和CPU不同的是,GPU就是为实现大量数学运算设计的。从结构图中可以看到,GPU的控制器比较简单,但包含了大量ALU。GPU中的ALU使用了并行设计,且具有较多浮点运算单元。
- 硬件加速的主要原理,就是通过底层软件代码,将CPU不擅长的图形计算转换成GPU专用指令,由GPU完成。
并行结构举例:级联加法器
- A、B为输入,C为输出,且A、B、C均为总线,以32位CPU为例,则每根总线实际由32根导线组成,每根导线用不同的电压表示一个二进制的0或1。
- Clock为时钟信号线,每个固定的时钟周期可向其输入一个特定的电压信号,每当一个时钟信号到来时,A和B的和就会输出到C。
GPU并行计算举例
Android中的硬件加速
DisplayList
RenderNode
Android绘制流程(Android 6.0)
-
从
ViewRootImpl.performTraversals
到
PhoneWindow.DecroView.drawChild
是每次遍历View树的固定流程,首先根据标志位判断是否需要重新布局并执行布局;然后进行Canvas的创建等操作开始绘制。
- 如果硬件加速不支持或者被关闭,则使用软件绘制,生成的Canvas即 Canvas.class 的对象;
- 如果支持硬件加速,则生成的是 DisplayListCanvas.class 的对象;
- 两者的 isHardwareAccelerated() 方法返回的值分别为false、true,View根据这个值判断是否使用硬件加速。
- View中的 draw(canvas,parent,drawingTime) - draw(canvas) - onDraw - dispachDraw - drawChild 这条递归路径(下文简称 Draw路径 ),调用了 Canvas.drawXxx() 方法,在软件渲染时用于实际绘制;在硬件加速时,用于构建DisplayList。
-
View中的
updateDisplayListIfDirty
-
dispatchGetDisplayList
-
recreateChildDisplayList
这条递归路径(下文简称
DisplayList路径
),仅在硬件加速时会经过,用于在遍历View树绘制的过程中更新DisplayList属性,并快速跳过不需要重建DisplayList的View。
Android 6.0中,和DisplayList相关的API目前仍被标记为“@hide”不可访问,表示还不成熟,后续版本可能开放。 - 硬件加速情况下,draw流程执行结束后DisplayList构建完成,然后通过 ThreadedRenderer.nSyncAndDrawFrame() 利用GPU绘制DisplayList到屏幕上。
纯软件绘制 VS 硬件加速(Android 6.0)
| 渲染场景 | 纯软件绘制 | 硬件加速 | 加速效果分析 |
| 页面初始化 | 绘制所有View | 创建所有DisplayList | GPU分担了复杂计算任务 |
| 在一个复杂页面调用背景透明TextView的setText(),且调用后其尺寸位置不变 | 重绘脏区所有View | TextView及每一级父View重建DisplayList | 重叠的兄弟节点不需CPU重绘,GPU会自行处理 |
| TextView逐帧播放Alpha / Translation / Scale动画 | 每帧都要重绘脏区所有View | 除第一帧同场景2,之后每帧只更新TextView对应RenderNode的属性 | 刷新一帧性能极大提高,动画流畅度提高 |
| 修改TextView透明度 | 重绘脏区所有View | 直接调用RenderNode.setAlpha()更新 | 加速前需全页面遍历,并重绘很多View;加速后只触发DecorView.updateDisplayListIfDirty,不再往下遍历,CPU执行时间可忽略不计 |
- 场景1中,无论是否加速,遍历View树并都会走Draw路径。硬件加速后Draw路径不做实际绘制工作,只是构建DisplayList,复杂的绘制计算任务被GPU分担,已经有了较大的加速效果。
-
场景2中,TextView设置前后尺寸位置不变,不会触发重新Layout。
- 软件绘制时,TextView所在区域即为脏区。由于TextView有透明区域,遍历View树的过程中,和脏区重叠的多数View都要重绘,包括与之重叠的兄弟节点和他们的父节点(详见后面的介绍),不需要绘制的View在 draw(canvas,parent,drawingTime) 方法中判断直接返回。
- 硬件加速后,也需要遍历View树,但只有TextView及其每一层父节点需要重建DisplayList,走的是Draw路径,其他View直接走了DisplayList路径,剩下的工作都交给GPU处理。页面越复杂,两者性能差距越明显。
- 场景3中,软件绘制每一帧都要做大量绘制工作,很容易导致动画卡顿。硬件加速后,动画过程直接走DisplayList路径更新DisplayList的属性,动画流畅度能得到极大提高。
- 场景4中,两者的性能差距更明显。简单修改透明度,软件绘制仍然要做很多工作;硬件加速后一般直接更新RenderNode的属性,不需要触发invalidate,也不会遍历View树(除了少数View可能要对Alpha做特殊响应并在 onSetAlpha() 返回true,代码如下)。
总结
版权声明:本文标题:如何通过优化硬件加速提高Android设备的性能表现? 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1770576749a3535234.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论