我们在屏幕上绘制图像需要的原始数据叫做位图。位图(Bitmap)是一种数据结构。一个位图是由n*m个像素组成,每个像素的颜色信息由RGB组合或者灰度值表示。根据位深度,可将位图分为1、4、8、16、24及32位图像等。每个像素使用的信息位数越多,可用的颜色就越多,颜色表现就越逼真,越丰富,相应的数据量越大。
位图一般存储的是物理像素,而应用层一般用的是逻辑像素,物理像素和逻辑像素之间会存在一定的对应关系。例如,iOS中物理像素和逻辑像素的对应关系如下:
iOS1倍屏1pt对应1个物理像素
iOS2倍屏1pt对应2个物理像素
iOS3倍屏1pt对应3个物理像素
CPU和GPU的区别
讲到CPU、GPU、显示器的协同工作流程,就不得不提一下CPU和GPU的区别。
CPU是中央处理器,适合单一复杂逻辑,而GPU是图形处理器,适合高并发简单逻辑。
GPU有特别多的的计算单元和超长的流水线,但是控制逻辑非常简单,并且还省去了缓存,适合对低延迟要求不高的运算。而CPU不仅被Cache占据了大量空间,而且还有特别复杂的控制逻辑,相比之下计算能力只是CPU很小的一部分。图形渲染涉及到的矩阵运算比较多,矩阵相关的运算可以被拆分成并行的简单的运算,所以渲染处理这件事特别适合GPU去做。
总结来说:GPU的工作计算量大,但技术含量不高,需要简单重复很多次。就好比有个工作需要算成百上千次一百以内加减乘除一样。而CPU就像老教授,积分微分都会算,适合处理单一复杂逻辑运算。
我们通常将图像绘制的完整流程称为渲染流水线,这个过程是CPU和GPU协作完成的。一般一个渲染流程可以分成4个概念阶段,分别是:应用阶段(ApplicationStage),几何阶段(GeometryStage),光栅化阶段(RasterizerStage),像素处理阶段(PixelProcessing)。在《Real–TimeRering4th》中非常透彻的讲解了实时渲染的各种知识点,对渲染原理感兴趣的可以看看这本书,这本书堪称“实时渲染圣经”。下边会简单介绍一下这几个过程。
简而言之,就是图像在应用中的处理阶段。说白了就是一段运行在CPU上的程序,这时还没有GPU什么事。这一阶段主要是CPU负责处理用户的交互和操作,然后做一些应用层布局相关的处理,最后输出图元(点、线和三角形)信息给到下一阶段。
大家可能会疑惑,图元只有简单的点、线、三角形,能表示丰富的立体图形么,下边这张立体感很强的海豚就能给出肯定的答案了,简单的三角形再加上不同的着色,就能呈现出立体图形。
1.顶点着色器(VertexShader)
顶点着色器可以对顶点的属性进行一些基本的处理。将顶点信息进行视角转换、添加光照信息、增加纹理等操作。CPU丢给GPU的信息,就好像是站在上帝视角把这个视角看到的所有信息都给到GPU。而GPU则是站在人类的角度,将人类可以观察到的画面,输出在显示器上。所以这里是以人的视角为中心,进行坐标转换。
2.形状装配(ShapeAssembly)这个阶段是将顶点着色器输出的所有顶点作为输入,并将所有的点装配成指定图元的形状。图元(Primitive)如:点、线、三角形。这个阶段也叫图元装配。
3.几何着色器(GeometryShader)在图元外添加额外的顶点,将原始图元转换成新图元,来构建更加复杂的模型。
光栅化阶段会把前三个几何阶段处理后得到的图元(primitives)转换成一系列的像素。
如上图所示,我们可以看到,每个像素的中心有一个点,光栅化便是用这个中心点来进行划分的,如果中心点在图元内部,那么这个中心点所对应的像素就属于该图元。简而言之,这一阶段就是将连续的几何图形转化为了离散化的像素点。
1.片段着色器(FragmentShader)
通过上述的光栅化阶段之后,我们就拿到了各个图元对应的像素,最后这个阶段要做的事情就是给每个Pixel填充上正确的颜色,然后通过一系列处理计算,得到相应的图像信息,最终输出到显示器上。这里会做内插,就像补间动画一样。比如想要把一系列散点连成平滑曲线,相邻的已知点之间可能会缺少很多点,这时候就需要通过内插填补缺少的数据,最终平滑曲线上除已知点之外的所有点都是插值得到的。同样的,三角形的三个角色值给定后,其它的片段则根据插值计算出来,也就呈现来渐变的效果。
2.测试与混合(TestsandBling)
这个阶段会检测对应的深度值(z坐标),来判断这个像素位于其它图层像素的前面还是后面,决定是否应该丢弃。此外,该阶段还会检查alpha值(alpha值定义了一个像素的透明度),从而对图层进行混合。(一句话简单说,就是检查图层深度和透明度,并进行图层混合。)
UIKit
UIKit是iOS开发者最常用的框架,可以通过设置UIKit组件的布局以及相关属性来绘制界面。但是UIKit并不具备在屏幕成像的能力,这个框架主要负责对用户操作事件的响应(UIView继承自UIResponder),事件经过响应链传递。
CoreAnimation
CoreAnimation主要负责组合屏幕上不同的可视内容,这些可视内容可被分解成独立的图层也就是我们日常开发过程中常接触的CALayer,这些图层被存储在图层树中。CALayer主要负责页面渲染,它是用户能在屏幕上看见的一切的基础。
CoreGraphics
CoreGraphics主要用于运行时绘制图像。开发者可以使用此框架来处理基于路径的绘图,转换,颜色管理,离屏渲染,图案,渐变和阴影等等。
CoreImage
CoreImage与CoreGraphics正好相反,CoreGraphics是在运行时创建图像,而CoreImage则是在运行前创建图像。
OpenGLES和Metal
OpenGLES和Metal都是第三方标准,基于这些标准具体的内部实现是由对应的GPU厂商开发的。Metal是苹果的一套第三方标准,由苹果实现。很多开发者都没有直接使用过Metal,但却通过CoreAnimation、CoreImage这些核心的系统框架在间接的使用metal。
上边渲染框架中提到的CoreAnimation是iOS和OSX上图形渲染和动画的基础框架,主要用来给视图和应用程序的其他可视元素设置动画。CoreAnimation的实现逻辑是将大部分实际绘图的工作交给GPU加速渲染,这样不会给CPU带来负担,还能实现流畅的动画。CoreAnimation的核心类是CALayer,UIKit框架的核心类是UIView,下边详细介绍一下这两个类的关系。
如上图所示,UIView和CALayer是一一对应的关系,每一个UIView都有一个CALayer与之对应,一个负责布局、交互响应,一个负责页面渲染。
他们的两个核心关系如下:
CALayer是UIView的属性之一,负责渲染和动画,提供可视内容的呈现。
UIView提供了对CALayer功能的封装,负责了交互事件的处理。
举一个形象一点的例子,UIView是画板,CALayer就是画布,当你创建一个画板的时候,会自动绑定一个画布,画板会响应你的操作,比如你可以移动画板,画布则负责呈现具体的图形,二者职责分明。一个负责交互,一个负责渲染绘制。
CALayer中的contents属性保存了由设备渲染流水线渲染好的位图bitmap(通常被称为backingstore),也就是我们最开始说的屏幕绘制需要的最原始的数据源。而当设备屏幕进行刷新时,会从CALayer中读取生成好的bitmap,进而呈现到屏幕上。
@interfaceCALayer:NSObjectNSSecureCoding,CAMediaTiming/**Layercontentpropertiesandmethods.**//*Anobjectprovidingthecontentsofthelayer,typicallyaCGImageRef,*butmaybesomethingelse.(Forexample,NSImageobjectsare*)Defaultvalueisnil.*Animatable.*/@property(nullable,strong)idcontents;@CoreAnimation流水线
其实早在WWDC的AdvancedGraphicsandAnimationsforiOSApps(WWDC14419,关于UIKit和CoreAnimation基础的session)中苹果就给出了CoreAnimation框架的渲染流水线,具体流程如下图所示:
整个流水线中app本身并不负责渲染,渲染则是由一个独立的进程负责,即RerServer进程。下边会详细介绍一下整个pipeline的流程。
应用阶段
视图的创建
布局计算
对图层进行打包,在下一次RunLoop时将其发送至RerServer
其次,app通过CPU完成对显示内容的计算
RerServerGPU
这一阶段主要执行metal、CoreGraphics等相关程序,并调用GPU在物理层上完成对图像的渲染
GPU将渲染后的位图数据存储到FrameBuffer
Display
如果把把上边的步骤串在一起,会发现它们执行消耗的时间超过了16.67ms,因此为了满足对屏幕的60FPS刷新率的支持,需要通过流水线的方式将这些步骤并行执行,如下图所示。每一个阶段都在源源不断的给下一个阶段输送产物。这时候就可以满足16.67毫秒产生一帧数据的要求了。
安卓中Activity的一个重要的职责就是对界面生命周期的管理,这也就伴随了对视图窗口的管理。这中间就涉及了两个Android中两个主要的服务,AMS(ActivityManagerService)和WMS(WindowManagerService)。
在Android中,一个view会有对应的canvas。视图树对应一个canvas树,Surfaceflinger控制多个canvas的合成。最终渲染完成输出位图数据,显示到手机屏幕。
View和ViewGroup
View是Android中所有控件的基类,View类有一个很重要的子类:ViewGroup,ViewGroup作为其他view的容器使用。Android的所有UI组件都是建立在View、ViewGroup基础之上的,整体采用“组合”的思想来设计View和ViewGroup:ViewGroup是View的子类,所以ViewGroup也可以被当做View使用。一个Androidapp的图形用户界面会对应一个视图树,而视图树则对应一个canvas树。这个有点儿类似于iOS中的UIView和CALayer的概念,一个负责应用层布局,一个负责底层渲染。
应用层的view对应到canvas,canvas到系统进程就成了layer。SurfaceFlinger主要提供layer的渲染合成服务。SurfaceFlinger是一个常驻的binder服务,会随着init进程的启动而启动。下面这张图就详细的介绍了上层view到底层layer的转化,以及SurfaceFlinger对多个layer的渲染合成。
而离屏渲染的流程是这样的:
与普通情况下GPU直接将渲染好的内容放入Framebuffer中不同,离屏渲染需要先额外创建离屏渲染缓冲区,将提前渲染好的内容放入其中,等到合适的时机再将OffscreenBuffer中的内容进一步叠加、渲染,完成后将结果再写入Framebuffer中。
为什么先要将数据存放在离屏渲染缓冲区呢?有两个原因,一个是被动的,一个是主动的。
一些特殊效果需要使用额外的OffscreenBuffer来保存渲染的中间状态(被动)
处于效率目的,可以将内容提前渲染保存在OffscreenBuffer中,达到复用的目的。(主动)
透明、阴影加圆角通常被称为UI三大宝,但这些效果在iOS的日常开发过程中却往往会导致被动的离屏渲染,下边是几个常见的会触发被动离屏渲染的场景。
讲离屏渲染的原因不得不提画家算法,画家算法的整体思想是按层绘制,首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。这里的层在iOS的渲染技术栈中就可以被对应到layer。
通常对于每一层layer,RerServer会遵循“画家算法”,按次序输出到framebuffer,后一层覆盖前一层,就能得到最终的显示结果,对于这个layer树则是以深度优先的算法将layer输出到framebuffer。
作为“画家”的GPU虽然可以一层一层往画布上进行输出,但是却没有办法在某一层渲染完成之后,再回过头来改变其中的某个部分。因为在这一层之前的若干层layer像素数据,已经在渲染中被合成在一起了。其实这里和photoshop中的图层合并非常像,一旦多个图层被合并在一起,就无法再单独对某一个图层进行修改。所以需要在离屏缓冲区中把子layer依次画好,然后把四个角裁剪好之后再和之前的图层进行混合。
一提离屏渲染,我们直观上的感觉是会对性能有影响。因为为了满足60fps的刷新频率,GPU的操作都是高度流水线化的。本来所有的计算工作都在有条不紊地正在向framebuffer输出,这时候突然又有一些特殊的效果触发了离屏渲染,需要切换上下文,把数据输出到另一块内存,这时候流水线中很多中间产物只能被丢弃,这种频繁的上下文切换对GPU的渲染性能有非常大的影响。
对于一些圆角可以创建四个背景颜色弧形的layer盖住四个角,从视觉上制造圆角的效果
对于view的圆形边框,如果没有backgroundColor,可以放心使用cornerRadius来做
对于所有的阴影,使用shadowPath来规避离屏渲染
对于特殊形状的view,使用layermask并打开shouldRasterize来对渲染结果进行缓存
使用CALayer的cornerRadius并设置cliptobounds以后会触发离屏渲染(offscreenrering)。滚动时每秒需要在60帧上执行裁剪操作,即使内容没有发生任何变化。GPU也必须在每帧之间切换上下文,合成整个帧和裁剪。这些对性能的消耗直接影响到RerServer这个独立渲染进程,造成掉帧。为了优化渲染性能,我们可以选择一些其他的实现圆角的方案。下边是圆角的具体实现需要考虑的条件。
圆角的具体实现需要考虑的条件
圆角下(movementunderneaththecorner)是否有滑动。
是否有穿过圆角滑动(movementthroughthecorner)。
四个圆角是否处于同一个layer上,有没有与其他子layer相交。
圆角的具体实现方案
如何根据对应的条件选取圆角的实现方案
上边提到了圆角的优化要考虑的条件以及不同的圆角实现方案,下边这个流程图就是把条件和方案对应起来,给出了圆角的最佳实现方案。
本文主要介绍了移动端渲染原理的相关内容。文章开始介绍了一下渲染相关的基础知识,讲了渲染所需要的原始数据源-位图以及CPU和GPU如何协同工作得到位图数据的。后面又结合iOS和安卓的技术框架介绍了移动端渲染的相关原理。最后深入分析了iOS中的离屏渲染,讲解了现有的圆角优化的一些方案。
1.iOS图像渲染原理
2.iOSRering渲染全解析
3.iOS渲染流程
4.从AutoLayout的布局算法谈性能
5.AutoLayout是怎么进行自动布局的,性能如何?
6.iOS界面渲染流程分析
7.iOS浅谈GPU及“App渲染流程”
8.CPU和GPU的区别是什么?
9.IOS进阶-图层与渲染
10.一篇文章搞懂到底什么是渲染流水线
11.GPUReringPipeline——GPU渲染流水线简介
12.关于iOS离屏渲染的深入研究
13.texture
14.Android的各个渲染框架和Android图层渲染原理
版权声明:本站所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流,不声明或保证其内容的正确性,如发现本站有涉嫌抄袭侵权/违法违规的内容。请举报,一经查实,本站将立刻删除。