一.图像显示原理
关于CPU和GPU都是通过总线连接起来的
在CPU当中往往是一个位图,再经由总线在合适的时机传输给GPU, 然后GPU会把位图进行图层的渲染,包括文理的合成; 之后把结果放到Frame Buffer,由视频控制器根据Vsync信号,在制定的时间之前去提取FrameBuffer中对应屏幕显示内容,最终显示到手机屏幕上
CPU全部工作
- Layout(布局):UI布局, 文字size计算等
- Display(绘制):如drawRect
- Prepare(准备)如图片编解码
Commint(提交):提交位图
- 首先,我们创建一个UIView空间,显示部分交由CALayer负责. CALayer中有一个contents属性, 就是我们最终绘制到屏幕上的一个位图, 比如helloworld这个Label, contents里面是一个helloworld文字位图
- 然后系统会在合适的时机,回调drawrect方法, 我们在此之上绘制自定义的内容, 绘制好的内容交由CoreAnimation这个框架提交OpenGL渲染管线, 进行最终的绘图渲染和文理合成, 然后显示到屏幕上
GPU渲染管线
- 顶点着色
- 图元装配
- 光栅化
- 片段着色
- 片段处理
二. UI卡顿和掉帧
知识背景
- 页面滑动的流畅性是60fps, 即1s有60帧的画面更新才可以让眼睛感觉画面流畅
- 在规定的事件内, 即16.67ms(按照一分钟60帧计算), 由CPU+GPU协同产生一帧的数据
- CPU在做 UI布局, 文本计算, 绘制, 图片编解码等工作占用时间过长, 导致留给GPU的时间非常少, GPU想要把图层合成和文理渲染准备完毕, 所需要的总时间可能超过16.67ms,
这样在下一帧VSync信号到来之前, 我们没有准备好下一帧画面, 就产生了掉帧现象, 我们肉眼看到的效果就是卡顿现象.
三 . 滑动方案优化
- 方案一
CPU把下面操作放到子线程:
- 对象的创建, 调整, 销毁
- 预排班(布局计算, 文本计算)
- 预渲染(文本等异步绘制, 图片编解码等)
- 方案二
GPU优化
- 文理渲染
- 1.1 避免离屏渲染
- 1.2 依托于CPU异步绘制机制减轻GPU压力
- 视图合成
- 2.1 如果多个View层层叠加, GPU就要做每个视图的合成, 合成每一个像素点的像素值, 需要大量的计算
- 2.2如果减轻视图的复杂性, 就可以减轻GPU的压力, 也可以采用CPU异步绘制, 使得提交的位图本身就是一个层级很少的视图, 也可以减轻GPU的压力
四. UI绘制原理&异步绘制
- UIView调用setNeedsDislay(实际上是这个View的layer调用setNeedsDislay方法, 之后在layer上打上一个脏标记), 然后并没有立即发生当前视图的绘制工作, 而是在当前runloop快要结束的时候调用CALayer的display方法, 进入到当前视图的真正绘制工作中.
- 原因要减少绘制次数, 提升性能, 所以要在当前runloop快要结束时调用CALayer的display方法
1. 系统绘制流程
- 在CALayer内部会创建一个backing Store (我们可以理解为是CGContextRef)
- 然后Layer会判断是否有代理
- 没有代理的话会调用CALayer的drawInContext:方法
有代理的话会调用代理的drawInContext方法, 然后做当前视图的绘制工作(发生在系统当中的). 然后在一个合适的时机回调一个系统的方法[UIView drawRect], drawRect的实现默认是什么都不做的, 而给我们开这个口子就是允许我们在系统绘制的基础之上绘制一些其他的工作- 最终无论上面哪一个分支, 都是由CALayer上传位图(backing store)给GPU
2. 异步绘制
问: 如何实现异步绘制?
答: 只要layer.delegate实现了displayLayer:方法, 我么就可以进行异步绘制
- 代理负责生成对应的bitmap(位图)
- 设置该bitmap(位图)作为layer.contents属性的值
异步绘制原理
通过子线程切换, 借助Global queue, 在子线程进行位图的绘制, 此时主线程可以做其他工作; 等子线程绘制完毕, 再回到主线程提交位图, 设置CAlayer的contents属性, 完成UI异步绘制过程.
五 . 离屏渲染
- 在屏渲染
- 指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行.
- 离屏渲染
指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作.
也就是当我们设置某些UI视图层属性, 如果指令为:在未被合成前不能直接显示的时候, 典型的比如设置:
- 圆角属性&&maskTobounds = YES(仅有一条是不会触发的),
- 图层蒙版,
- 阴影,
- 光栅化,
就会触发离屏渲染
- 为什么要避免离屏渲染
- 离屏渲染是发生在GPU层面, 使得GPU触发了OpenGL的多通道渲染管线, 产生了额外的开销, 增加了GPU工作量, 可能使得CPU+GPU的工作时间超出了16.7ms的总耗时, 可能会导致UI卡顿和掉帧
总结
- 系统的UI传递机制是怎样的?
- 使UITableView滚动流畅的方案和思路?
子线程加载数据,主线程刷新,cell复用,cell视图层级简化,尽量少触发离屛渲染;
- 方案一
CPU把下面操作放到子线程:
- 对象的创建, 调整, 销毁
- 预排班(布局计算, 文本计算)
- 预渲染(文本等异步绘制, 图片编解码等)
- 方案二
GPU优化
- 文理渲染
- 1.1 避免离屏渲染
- 1.2 依托于CPU异步绘制机制减轻GPU压力
- 视图合成
- 2.1 如果多个View层层叠加, GPU就要做每个视图的合成, 合成每一个像素点的像素值, 需要大量的计算
- 2.2如果减轻视图的复杂性, 就可以减轻GPU的压力, 也可以采用CPU异步绘制, 使得提交的位图本身就是一个层级很少的视图, 也可以减轻GPU的压力
什么是离屏渲染?
指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作UIView和CALayer之间的关系是怎样的?
- UIView是专门负责事件传递和视图响应的, CALayer是专一负责视图显示的;单一职责设计原则(六大设计原则之一)