在开始了解卡慢、掉帧及绘制原理前,首先让我们先理解下图像的显示原理
关于CPU和GPU都是通过总线连接起来的,在CPU当中输出的往往是一个位图,再经由总线在合适的时机传递个GPU
GPU拿到这个位图之后,会对这个位图的图层进行渲染,包括纹理的合成等
之后会把这个结果放到帧缓冲区中,而后视频控制器会按照VSync信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器,达到最终的显示效果
首先当我们创立一个UIView控件的时候,其中负责显示的CALayer
CALayer中有一个contents属性,就是我们最终要绘制到屏幕上的一个位图,比方说我们创立了一个UILabel,那么在contents里面就放了一个关于Hello world的文字位图
而后系统会在一个合适的时机回调给我们一个drawRect:的方法,这个方法中我们可以去绘制少量自己设置的内容
绘制好了之后,最终会由Core Animation这个框架提交给GPU部分的OpenGL渲染管线,进行最终的位图的渲染,包括纹理合成等,而后显示在屏幕上
那么CPU和GPU具体做了哪些工作承担呢
具体分为四个阶段
Layout:这里主要涉及到少量UI布局,文本计算等,例如一个label的size
Display:绘制阶段,例如drawRect方法就在这一步骤中
Prepare:图片的编解码等操作在此步骤中
Commit:提交位图
顶点着色
图元装配
光栅化
片段着色
片段解决
在显示器中是固定的频率,比方iOS中是每秒60帧(60FPS),即每帧16.7ms
从上图中可以看出,每两个VSync信号之间有时间间隔(16.7ms),在这个时间内,CPU主线程计算布局,解码图片,创立视图,绘制文本,计算完成后将内容交给GPU,GPU变换,合成,渲染(详细可学习 OpenGL相关课程),放入帧缓冲区
如果16.7ms内,CPU和GPU没有来得及生产出一帧缓冲,那么这一帧会被丢弃,显示器就会保持不变,继续显示上一帧内容,这就将导致导致画面卡慢
所以无论CPU,GPU,哪个消耗时间过长,都会导致在16.7ms内无法生成一帧缓存
CPU
CPU在准备下一帧的所做的工作非常多导致耗时,基于减轻CPU工作时长和压力来达到一个优化效果
1、部分对象的创立、调整和销毁可以放到子线程去做
2、预排版( 布局计算、文本计算),这些计算也可以放到子线程去做,这样主线程也可以有更多的时间去响应客户的交互
3、预渲染(文本等异步绘制、图片编解码等)
GPU
1、纹理渲染:如果说我们触发了离屏渲染,例如我们设置圆角时对maskToBounds的设置,包括少量阴影、蒙层等都会触发GPU层面的离屏渲染,对于这种情况下,GPU对于纹理渲染的工作量就会非常的大,我们可以基于此对GPU进行优化,就是尽量减少离屏渲染,我们也可以通过CPU的异步绘制来减轻GPU的压力
2、视图混合: 比方说我们视图层级比较复杂,视图之间层层叠加,那么GPU就要做每一个视图的合成,合成每一个像素点的像素值,假如我们可以减少视图的层级,也是可以减轻GPU的压力,我们也可以通过CPU的异步绘制机制来达到一个提交的位图本身就是一个层级比较少的位图
当我们调用[UIView setNeedsDisplay]这个方法时,其实并没有立即进行绘制工作,系统会立刻调用CALayer的同名方法,并且会在当前layer上打上一个标记,而后会在当前runloop将要结束的时候调用[CALayer display]这个方法,而后进入我们视图的真正绘制过程
而在[CALayer display]这个方法的内部实现中会判断这个layer的delegate能否响应displayLayer:这个方法,假如不响应这个方法,就会进入到系统绘制流程中;假如响应这个方法,那么就会为我们提供异步绘制的入口
在CALayer内部会先创立backing store,我可以了解为CGContext,我们一般在drawRect:方法中通过上下文堆栈当中取出栈顶的context,也就是上下文
而后这个layer会判断能否有代理商,假如没有代理商,那么就会调用[CALayer drawInCotext:];假如有代理商,会调用代理商的drawLayer:inContext:方法,而后做当前视图的绘制工作(这一步是发生在系统内部的),而后在一个合适的时机给与我们这个十分熟习的[UIView drawRect:]方法的回调,[UIView drawRect:]这个方法默认是什么都不做,,系统给我们开这个口子是为了让我们可以再做少量其余的绘制工作
而后无论是哪个分支,最终都会由CALayer上传对应的backing store(可以了解为位图)给GPU,而后就结束了系统默认的绘制流程
实际上我们就需要借用系统给开的这个口子,即[layer.delegate displayLayer:]
在这个异步绘制过程中就需要代理商负责生成对应的bitmap(位图)
同时设置bitmap作为layer.contents属性的值
国际惯例,流程图走一波(原谅我画图能力实在有限TT)
如果说我们在某一个时机调用了[view setNeedsDisplay]这个方法,系统会在当前runloop将要结束的时候调用[CALyer display]方法,而后假如我们这个layer的代理商实现了[view displayLayer]这个方法
而后会通过子线程的切换,我们在子线程中去做一个位图的绘制,主线程可以去做少量其余的操作
在子线程中第一步先通过CGBitmapContextCreate()方法来创立一个位图的上下文,而后我们通过CoreGraphic API可以做当前UI控件的少量绘制工作,最后我们再通过CGBitmapContextCreateImage()这个函数来根据当前所绘制的上下文来生成一张CGImage图片
最后回到主线程来提交这个位图,设置layer的contents属性,这样就完成了一个UI控件的异步绘制过程
离屏渲染指的是GPU在当前屏幕缓冲区以外开拓了一个缓冲区进行渲染操作
当前屏幕渲染不需要额外创立新的缓存,也不需要开启新的上下文,相对于离屏渲染性能更好。但是受当前屏幕渲染的局限因素限制(只有自身上下文、屏幕缓存有限等),当前屏幕渲染有些情况下的渲染处理不了的,就使用到离屏渲染
离屏渲染对性能的的代价是很高的,主要表现在:
创立了新的缓冲区
上下文的频繁切换
导致产生离屏渲染的起因:
shouldRasterize(光栅化)
masks(遮罩)
shadows(阴影)
edge antialiasing(抗锯齿)
group opacity(不透明)
复杂形状设置圆角等
渐变