admin管理员组

文章数量:1516870

一 概述

在手机客户端尤其是 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;

本文标签: 硬件加速关键点加速