admin 管理员组文章数量: 1184232
一 概述
在手机客户端尤其是 Android 应用开发过程中,我们经常会接触到“硬件加速”这个概念。由于操作系统对底层软硬件封装非常完善,上层软件开发者往往对硬件加速的底层原理了解很少,也不清楚了解底层原理的意义,因此常会有一些误解,如硬件加速是不是通过特殊算法实现页面渲染加速,或是通过硬件提高 CPU/GPU 运算速率实现渲染加速。
硬件加速,直观上说就是依赖 GPU 实现图形绘制加速,软硬件加速的区别主要是图形的绘制究竟是 GPU 来处理还是 CPU,如果是 GPU,就认为是硬件加速绘制,反之,则为软件绘制。在 Android 中也是如此,不过相对于普通的软件绘制,硬件加速还做了其他方面优化,不仅仅限定在绘制方面,绘制之前,在如何构建绘制区域上,硬件加速也做出了很大优化,因此硬件加速特性可以从下面两部分来分析:
- 前期策略:如何构建需要绘制的区域
- 后期绘制:单独渲染线程,依赖 GPU 进行绘制
无论是软件绘制还是硬件加速,绘制内存的分配都是类似的,都是需要请求 SurfaceFlinger 服务分配一块内存,只不过硬件加速有可能从 FrameBuffer 硬件缓冲区直接分配内存(SurfaceFlinger 一直这么干的),两者的绘制都是在 APP 端,绘制完成之后同样需要通知 SurfaceFlinger 进行合成,在这个流程上没有任何区别, 真正的区别在于在 APP 端如何完成 UI 数据绘制 ,本文就直观的了解下两者的区别,会涉及部分源码,但不求甚解。
1.1 了解硬件加速对App开发的意义
对于 App 开发者,简单了解硬件加速原理及上层 API 实现,开发时就可以充分利用硬件加速提高页面的性能。以 Android 举例,实现一个圆角矩形按钮通常有两种方案:使用 PNG 图片;使用代码(XML/Java)实现。简单对比两种方案如下。
| 方案 | 原理 | 特点 |
|---|---|---|
| 使用PNG图片(BitmapDrawable) | 解码PNG图片生成Bitmap,传到底层,由GPU渲染 | 图片解码消耗CPU运算资源,Bitmap占用内存大,绘制慢 |
| 使用XML或Java代码实现(ShapeDrawable) | 直接将Shape信息传到底层,由GPU渲染 | 消耗CPU资源少,占用内存小,绘制快 |
1.2 页面渲染背景知识
- 页面渲染时,被绘制的元素最终要转换成矩阵像素点(即多维数组形式,类似安卓中的 Bitmap),才能被显示器显示
- 页面由各种基本元素组成,例如圆形、圆角矩形、线段、文字、矢量图(常用贝塞尔曲线组成)、Bitmap 等
- 元素绘制时尤其是动画绘制过程中,经常涉及插值、缩放、旋转、透明度变化、动画过渡、毛玻璃模糊,甚至包括 3D 变换、物理运动(例如游戏中常见的抛物线运动)、多媒体文件解码(主要在桌面机中有应用,移动设备一般不用 GPU 做解码)等运算
- 绘制过程经常需要进行逻辑较简单、但数据量庞大的浮点运算。
二 CPU与GPU结构对比
CPU(Central Processing Unit,中央处理器)是计算机设备的核心器件,用于执行程序代码,软件开发者对此都很熟悉;GPU(Graphics Processing Unit,图形处理器)主要用于处理图形运算,通常所说“显卡”的核心部件就是 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 完成。
扩展:很多计算机中的 GPU 有自己独立的显存;没有独立显存则使用共享内存的形式,从内存中划分一块区域作为显存。显存可以保存 GPU 指令等信息。
2.1 并行结构举例:级联加法器
为了方便理解,这里先从底层电路结构的角度举一个例子。如下图为一个加法器,对应实际的数字电路结构。
A、B 为输入,C 为输出,且 A、B、C 均为总线,以 32 位 CPU 为例,则每根总线实际由 32 根导线组成,每根导线用不同的电压表示一个二进制的 0 或 1。
Clock 为时钟信号线,每个固定的时钟周期可向其输入一个特定的电压信号,每当一个时钟信号到来时,A 和 B 的和就会输出到 C。
现在我们要计算 8 个整数的和。
对于 CPU 这种串行结构,代码编写很简单,用 for 循环把所有数字逐个相加即可。串行结构只有一个加法器,需要 7 次求和运算;每次计算完部分和,还要将其再转移到加法器的输入端,做下一次计算。整个过程至少要消耗十几个机器周期。
而对于并行结构,一种常见的设计是级联加法器,如下图,其中所有的 clock 连在一起。当需要相加的 8 个数据在输入端 A1~B4 准备好后,经过三个时钟周期,求和操作就完成了。如果数据量更大、级联的层级更大,则并行结构的优势更明显。
由于电路的限制,不容易通过提高时钟频率、减小时钟周期的方式提高运算速度。并行结构通过增加电路规模、并行处理,来实现更快的运算。但并行结构不容易实现复杂逻辑,因为同时考虑多个支路的输出结果,并协调同步处理的过程很复杂(有点像多线程编程)。
2.2 GPU并行计算举例
假设我们有如下图像处理任务,给每个像素值加 1。GPU 并行计算的方式简单粗暴,在资源允许的情况下,可以为每个像素开一个 GPU 线程,由其进行加 1 操作。数学运算量越大,这种并行方式性能优势越明显。
三 软硬件加速的分歧点
大概从 Android 4.+ 开始,默认情况下都是支持和开启了硬件加速的,也存在手机支持硬件加速,但是部分 API 不支持硬件加速的情况,如果使用了这些 API,就需要主动关闭硬件加速,或者在 View 层,或者在 Activity 层关闭,比如 Canvas 的 clipPath 等。但是,View 的绘制是软件实现的还是硬件加速实现的,一般在开发的时候并不可见,那么图形绘制的时候,软硬件的分歧点究竟在哪呢?举个例子,有个 View 需要重绘,一般会调用 View 的 invalidate,触发重绘,跟着这条线走,去查一下分歧点。
从上面的调用流程可以看出,视图重绘最后会进入
ViewRootImpl 的 draw,软硬件加速的分歧点就在这个函数里面
,我们来看代码:
ViewRootImpl.java
privatevoiddraw(boolean fullRedrawNeeded){
......if(!dirty.isEmpty()|| mIsAnimating || accessibilityFocusDirty){
<!--关键点1 是否开启硬件加速-->if(mAttachInfo.mThreadedRenderer != null &&
mAttachInfo.mThreadedRenderer.isEnabled()){
......
dirty.setEmpty();<!--关键点2 硬件加速绘制-->
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo,this);}else{
...<!--关键点3 软件绘制-->if(!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)){
returnfalse;}......关键点1是启用硬件加速的条件,必须支持硬件并且开启了硬件加速才可以,满足这两个条件后,就调用 ThreadedRenderer.draw,否则 drawSoftware(软件绘制)。简答看一下这个条件,默认情况下,该条件是成立的,因为 4.+ 之后的手机一般都支持硬件加速,而且在 ViewRootImpl 通过 setView 添加窗口的时候,会调用 enableHardwareAcceleration 开启硬件加速,我们来看代码:
ViewRootImpl.java
publicvoidsetView(View view, WindowManager.LayoutParams attrs, View panelParentView){
......if(view instanceofRootViewSurfaceTaker){
mSurfaceHolderCallback =((RootViewSurfaceTaker)view).willYouTakeTheSurface();if(mSurfaceHolderCallback != null){
mSurfaceHolder =newTakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
mSurfaceHolder.addCallback(mSurfaceHolderCallback);}}......if(mSurfaceHolder == null){
// While this is supposed to enable only, it can effectively disable// the acceleration too.//关键点: 开启硬件加速enableHardwareAcceleration(attrs);finalboolean useMTRenderer = MT_RENDERER_AVAILABLE
&& mAttachInfo.mThreadedRenderer != null;if(mUseMTRenderer != useMTRenderer){
// Shouldn't be resizing, as it's done only in window setup,// but end just in case.endDragResizing();
mUseMTRenderer = useMTRenderer;版权声明:本文标题:_rendernode背后的故事:揭秘Android中实现高效视觉效果的关键 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1771858489a3549354.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论