iOS 的绘制和渲染

初步学习阶段,水太深,后续会继续学习,参考不同的资料完善修正

第二版

一 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)是可以被编程的,


image.png

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。 并用更高效的实现手段来替换.

参考来源:https://www.jianshu.com/p/6426c5761561

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容