初步学习阶段,水太深,后续会继续学习,参考不同的资料完善修正
第二版
一 CPU做了什么
CPU具有核少,逻辑处理单元多的特点,可以处理精细复杂的运算,控件的坐标计算、文本的大小、布局、合并、图片例如png、jpg格式的图片解码为bitmap格式,生产绘制的指令和数据
1 对象的创建、维护、销毁
例如:UIView、CALayer的使用:UIView是在CALayer的集成上封装的,添加了事件的传递和响应,已经Core Animation 底层接口的高级封装
2图层树的维护和解析,生成绘制指令和数据
CALayer是一个树形结构,Core Animation 有图层树、呈现树、渲染书,Core Animation 会对这三种树进行进行维护和解析,每一帧打包都会包含所有的动画属性和图层,通过IPC(内部处理通信)发送给渲染服务,渲染服务进而发送到GPU
3计算视图的布局、文本的布局和绘制
GPU是不能直接对text文本做处理的,而且GPU的计算能力不足以支撑文本的布局,在CPU上先计算文本的大小、顶点坐标,布局,合成,并转化为texture,发送给GPU进行渲染操作
CPU 对UIView、CALayer 的frame进行坐标计算、布局,最终都会转化为GPU中的顶点坐标,如果是用autoLayout,会有更多的计算公式
4 Core Graphics
调用UIView 中的drawRect 方法和CALayerDelegate中的drawLayer:inContext方法会在CPU中开辟一块等大小的bitmap画布,绘制结束后通过IPC将bitmap发送到渲染服务中,上传到GPU中转化为texture。在CPU中开辟内存和多次上传bitmap合成texture 很耗时,所以少用drawRect方法
5图片的格式转换和解码
除了特定的压缩格式图片,大多的图片格式是不能再GPU中转化为texture的,例如 iOS常用的png和jpg格式
使用imageName方法加载图片需要先将png或者jpg格式图片转化为bitmap,上传到GPU转化为texture,这些操作都是在渲染之前完成的,如果是静态 图片还好,大量的图片:tableview上的图片 就会影响性能,SDWebImage中对图片做了这方面的处理
- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
if (![[self class] shouldDecodeImage:image]) {
return image;
}
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
@autoreleasepool{
CGImageRef imageRef = image.CGImage;
// device color space
CGColorSpaceRef colorspaceRef = SDCGColorSpaceGetDeviceRGB();
BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
// iOS display alpha info (BRGA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
kBitsPerComponent,
0,
colorspaceRef,
bitmapInfo);
if (context == NULL) {
return image;
}
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [[UIImage alloc] initWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation];
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
二GPU做了什么
GPU具有多核、逻辑处理单元少的特点,可以并行处理大量却不复杂的运算,渲染就是这类操作。现在流行的大数据和AI 训练就是要进行大量的简单算法,神经网络,苹果提供的卷积神经网络的API接口
1渲染:渲染需要大量的顶点矩阵计算、颜色插值这是都是简单的加减乘除,不牵涉到其他视图的依赖,可以在GPU中进行
2计算大量简单数据:大数据处理和人工智能,训练AI特别是神经网络这块需要大量的计算训练,苹果也给出了卷积神经网络API
三:OpenGL和Metal
OpenGL是跨平台的2D,3D渲染接口,大多游戏平台都支持OpenGL,在移动端OpenGL被称为OpenGLES,在iOS和Android平台开发了OpenGLES2和OpenGLES3版本,苹果在iOS8提出来Metal,
不管是OpenGL还是Metal,渲染流程都是相似的,在一个管线上进行渲染操作
1 渲染管线
渲染管线就像是一条生产流水线样式 下图所示,顶点着色器(Vertex Processor)和片段着色器(Fragment Processor)是可以被编程的,
1顶点数据经过Vertex Processor 处理做顶点交换
2图元装配,形成点、线、三角形图元
3光栅化像素点
4图元经过Fragment Processor 着色
5 图层混合
6传到帧缓存中
2 顶点着色器
二
第一版
一个画面 显示在 屏幕上的流程
1 布局:为视图/图层准备层级关系,以及设置图层属性(位置,背景色,边框等等)调用layoutSubViews或者 addSubview方法
2 显示:(display)图层的寄宿图片被绘制的阶段,绘制用到:drawRect:和drawLayer:inContext:方法
3 准备提交:Image decoding,Image conversion(如果图片类型不是GPU所造成的,需要对图片进行转换,iOS 对png格式图片做了优化处理)
4 提交:Core Animation 打包所有的图层和动画,然后通过IPC(进程内通信)发送到渲染服务(render server,一个单独管理动画和图层组合的一个系统进程)。这个步骤是递归提交子树的layers,所以如果layer tree如果比较复杂,会消耗很大,对性能造成影响
5 对所有图层属性计算中间值,设置OpenGL几何形状来执行渲染
6 在屏幕上渲染可见的三角形
前五个步骤都是在CPU中执行的,最后一个阶段在GPU执行,6个阶段只有布局和显示是可以被我们控制的,Core Animation框架处理剩下的事情
影响CPU使用效率的操作
1布局计算:如果视图层级过于复杂,当视图呈现或者修改的时候计算图层会消耗一部分时间(UITableview的动态计算cell高度)
2解压图片:图片绘制到屏幕上之前必须把它扩展成完整的未解压的尺寸
3图片转换:图片的颜色格式不是32bit,那么CPU会先进行颜色格式转换,然后CPU才会进行渲染最好直接提供32bit颜色格式的图片,或者在非主线程中进行格式转换,可以通过Core Animation Instruments的Color Copied Images现象进行颜色格式检测
4绘制
使用CALayer进行绘制: 实现了UIVIew的-drawRect:或者CALayerDelegate的-drawLayer:inContext方法,为了支持对图层内容的任意绘制,Core Animation必须创建一个图层宽图层高4字节大小的寄宿图,宽高的单位均为像素。
CALayer的contents属性就对应于寄宿图,寄宿图是通过backing store 来保存的,如果没有实现-drawRect:方法,CALayer的contents为空(通过po CALayer,只有实现了drawRect方法CALayer的contents 才会有内容)
优化方案:使用CATileLayer进行绘制,
在绘制的view中写如下代码
-(id)initWithFrame:(CGRect)frame
{
if(self= [superinitWithFrame:frame]) {
[(CATiledLayer *)self.layer setTileSize:CGSizeMake(100 * self.contentScaleFactor, 100*self.contentScaleFactor)];
}
return self;
}
+(Class)layerClass
{
return [CATiledLayer class];
}
使用CAShapeLayer进行绘制:
1)渲染快速:CAShapeLayer使用了硬件加速,比Core Graphics快
2)高效使用内存:CASHapeLayer不需要想CALayer一样创建一个寄宿图
3)不会被图层边界剪裁掉
4)不会出现像素化
像素对齐:像素不对齐的解决方案
将layer对象的宽高设置成整数,由于会根据layer的bounds来创建位图图片,Core Animation最终会将layer的宽高转换成整数。
Core Animation Instrument 中的Color Misaligned Images选项会做出一些标记。
洋红色:UIView的frame像素不对齐,
黄色:UIImageView的图片像素大小与其frame.size不对齐,图片发生了缩放
iPhone X适配遇到的像素对齐问题
如果是使用CATileLayer进行绘制,如果是水平方向等分的方式进行绘制
影响CPU使用效率的操作
图层混合 -- blending
在iOS的图形处理中,blending主要指的是混合像素颜色的计算。最直观的例子就是,我们把两个图层叠加在一起,如果第一个图层的透明的,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending。
会导致blending的原因:
layer(UIView)的Alpha < 1
UIImgaeView的image含有Alpha channel(即使UIImageView的alpha是1,但只要image含透明通道,则仍会导致Blending)
为什么Blending会导致性能的损失?
原因是很直观的,如果一个图层是不透明的,则系统直接显示该图层的颜色即可。而如果图层是透明的,则会引入更多的计算,因为需要把下面的图层也包括进来,进行混合后颜色的计算。
在了解完Blending之后,我们就知道为什么很多优化准则都需要我们尽量使用不透明图层了。接下来就是在开发中留意和进行优化了。
离屏渲染
GPU的屏幕渲染会有两种:
1)当前屏幕渲染:GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行
2)离屏渲染:GPU在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作
离屏渲染的代价:
1)创建新的缓冲区
2)上下文切换:离屏渲染的过程中会发生上下文:从当前屏幕切换到离屏,等到离屏渲染结束后,将离屏缓冲区中的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
为什么会离屏渲染
一般情况下OpenGL会将提交到渲染服务(Render Server)的动画直接渲染,但是对于一些复杂的图像动画不能直接进行叠加渲染显示,而是需要根据Command Buffer 分通道
离屏渲染触发方式
设置了一下属性都会触发离屏渲染
shouldRasterize(光栅化)
masks(遮罩)
shadows(阴影)
edge antialiasing(抗锯齿)
group opacity(不透明)
需要注意的是,如果shouldRasterize被设置为YES,在触发离屏渲染的同时会将光栅化后的内容缓存起来,如果对应的layer及其sublayer没有变化,在下一帧的时候是可以复用的,这很大程度提高了渲染性能,对于经常变动的内容,这个时候不要开启,否则会造成性能的浪费。例如我们日程经常打交道的TableViewCell,因为TableViewCell的重绘是很频繁的(因为Cell的复用),如果Cell的内容不断变化,则Cell需要不断重绘,如果此时设置了cell.layer可光栅化。则会造成大量的offscreen渲染,降低图形性能。当然,合理利用的话,是能够得到不少性能的提高的,因为使用shouldRasterize后layer会缓存为Bitmap位图,对一些添加了shawdow等效果的耗费资源较多的静态内容进行缓存,能够得到性能的提升。
不要过度使用,系统限制了缓存的大小为2.5X Screen Size.
如果过度使用,超出缓存之后,同样会造成大量的offscreen渲染。
被光栅化的图片如果超过100ms没有被使用,则会被移除
因此我们应该只对连续不断使用的图片进行缓存。对于不常使用的图片缓存是没有意义,且耗费资源的。
这里提到的offscreen rendering主要讲的是通过GPU执行的offscreen,事实上还有的offscreen rendering是通过CPU来执行的(例如使用Core Graphics, drawRect)。其它类似cornerRadios, masks, shadows等触发的offscreen是基于GPU的。
会造成 offscreen rendering的原因有:
Any layer with a mask (layer.mask)
Any layer with layer.masksToBounds being true
Any layer with layer.allowsGroupOpacity set to YES and layer.opacity is less than 1.0
Any layer with a drop shadow (layer.shadow*).
Any layer with layer.shouldRasterize being true
Any layer with layer.cornerRadius, layer.edgeAntialiasingMask, layer.allowsEdgeAntialiasing
因此,对于一些需要优化图像性能的场景,我们可以检查我们是否触发了offscreen rendering。 并用更高效的实现手段来替换.