图像渲染过程
在介绍渲染过程之前先介绍一下VSync信号
VSync 信号由硬件时钟生成,每秒钟发出 60 次(这个值取决设备硬件,比如 iPhone 真机上通常是 59.97)。iOS 图形服务接收到 VSync 信号后,会通过 IPC 通知到 App 内。App 的 Runloop 在启动后会注册对应的 CFRunLoopSource 通过 mach_port 接收传过来的时钟信号通知,随后 Source 的回调会驱动整个 App 的动画与显示。

在VSync信号到来之后,系统图形服务会通过CADisplayLink(CADisplayLink 是一个和屏幕刷新率(每秒刷新60次)一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source))去通知App。App主线程开始在CPU中计算显示内容,比如视图创建、布局计算、图片解码、文本绘制等等。随后CPU会将计算好的内容提交到GPU中,由GPU进行变换、合成、渲染。随后GPU会把渲染结果提交到“帧缓冲区”中,等待下一次VSync信号到来时显示到屏幕上。
如果在一个VSync时间内,CPU/GPU没有完成内容提交,那这一帧就会被丢弃。造成掉帧的现象。

Core Animation 在 RunLoop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件。当一个触摸事件到来时,RunLoop 被唤醒,App 中的代码会执行一些操作,比如创建和调整视图层级、设置 UIView 的 frame、修改 CALayer 的透明度、为视图添加一个动画;这些操作最终都会被 CALayer 标记,并通过 CATransaction 提交到一个中间状态去。当上面所有操作结束后,RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知。这时 Core Animation 注册的那个 Observer 就会在回调中,把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,通过 DisplayLink 稳定的刷新机制会不断的唤醒runloop,使得不断的有机会触发observer回调,从而根据时间来不断更新这个动画的属性值并绘制出来。

整个过程一共分为六步

  • Handle Events:这个过程中会先处理点击事件
  • Commit Transaction:app通过CPU处理显示内容的前置计算,比如布局计算、图片编码等任务。然后将图片发送给Render Server
  • Decode:打包好的图层被传输至Render Server之后,首先对图片进行解码,在完成解码之后的下一个Runloop执行Draw Calls
  • Draw Calls:解码完成后,会调用下层渲染框架的方法进行绘制,从而调用到GPU
  • Render:GPU进行渲染
  • Display:在render结束的下一个runloop进行显示
    Commit Transaction包含哪些过程?
  • Layout:构建视图
  • Display:绘制视图
  • Prepare:图片解码&转换
  • Commit:图层打包&发送至Render(GPU)
    Render在GPU渲染包括哪些过程?
  1. GPU收到command Buffer,包含图元信息
  2. Tiler开始工作,先通过顶点着色器对顶点进行处理,更新图元信息
  3. 平铺过程:平铺生成的几何图形,将图元信息转化成像素,写入Parameter Buffer中
  4. 如果Parameter Buffer已满/更新完了全部的图元信息,则会开始下一步
  5. renderer:将像素信息进行处理得到bitmap,之后存入Render Buffer
  6. Render Buffer中存储由渲染好的bitmap,供之后的Display使用。

使用 Instrument 的 OpenGL ES,可以对过程进行监控。OpenGL ES tiler utilization 和 OpenGL ES renderer utilization 可以分别监控 Tiler 和 Renderer 的工作情况
CALayer
在iOS中,所有视图都引申自UIView。
CALayer包含三个部分:
Model Tree :也就是我们通常所说的layer。
Presentation Tree:呈现出来的layer,也就是我们做动画时你看到的那个layer,可以通过layer.presentationLayer获得。
Render Tree :私有,无法访问。主要是对Presentation Tree数据进行渲染,并且不会阻塞线程。

而CALayer在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和UIView最大的不同是CALayer不处理用户的交互。CALayer并不清楚具体的响应链。

为什么要维护UIView和CALayer两个层级?
这样设计的主要原因就是为了职责分离,拆分功能,方便代码的复用。

CALayer是显示的的基础:存储bitmap
CALayer有一个属性contents;
contents提供了layer的内容,是一个指针类型,通常是CGImageRef(在Mac OS中还可以是NSImage类型)
所以实际情况为:CALayer中的contents属性保存了由设备渲染好的位图bitmap,当设备屏幕刷新时,会从CALayer中读取生成好的bitmap(通常也被称为 backing store),从而呈现到屏幕上。
所以,如果我们在代码中对CALayer的contents属性进行了设置,
layer.contents = (__bridge id)image.CGImage
在运行时,操作系统就会调用底层的接口,将image通过cpu + Gpu的渲染得到对应的bitmap,存储于CALayer.contents中,在设备屏幕进行刷新的时候就会读取contents中的内容在屏幕上呈现
也正因为每次要被渲染的内容是被静态的存储起来的,所以每次渲染时,Core Animation会触发调用drawRect:方法,使用存储好的bitmap进行新一轮的展示
离屏渲染
与普通情况下GPU直接将渲染好的内容放入帧缓冲器中不同,需要先额外创建离屏渲染缓冲区将提前渲染好的内容放入其中,等到合适的时机再将离屏渲染缓冲区中的内容进一步叠加、渲染、完成后将结果切换到帧缓冲器中
离屏渲染的效率问题
从上面流程来看,离屏渲染时由于App需要提前对部分内容进行额外的渲染并保存到OffscreenBuffer中,以及需要在必要时刻对OffscreenBuffer和Framebuffer进行内容切换,所以会需要更长的处理时间(事实上这两步关于buffer的切换代价都非常大)
并且OffscreenBuffer本身就需要额外的空间,大量的离屏渲染可能造成内存的过大压力。与此同时,OffscreenBuffer的总大小也不能超过屏幕总像素的2.5倍。
所以在大部分情况下,我们都要去避免这种开销很大、容易掉帧的问题
为什么要使用离屏渲染呢?
既然开销大、容易掉帧,为什么还要使用呢?

  • 一些特殊效果需要使用额外的OffscreenBuffer来保存渲染的中间状态,所以不得不使用离屏渲染
  • 某些情况下,可以将内容提前渲染保存在OffscreenBuffer中,达到复用的目的
    对于第一种情况,也就是不得不使用离屏渲染的情况,一般都是系统自动触发的,比如阴影、圆角。最常见的情形之一就是:使用了Mask蒙版。
    iOS 8开始提供的模糊特效UIBlurEffectView:
    其模糊过程分为多步:
  • 1.先渲染需要模糊的内容本身
  • 2.对内容进行缩放
  • 3.4.分别对上一步内容进行横向和纵向的模糊操作
  • 5.最好用模糊的效果叠加合成,最终实现完整的模糊特效。
    光栅化shouldRasterize
    光栅化概念:将图转化为一个个栅格组成的图像
    光栅化特点:每个元素对应帧缓冲区中的一像素
    光栅化会导致离屏渲染,影响图像性能,那么光栅化是否有助于优化性能,就取决于光栅化创建的位图缓存是否被有效复用,而减少渲染的频度。
    开启光栅化后,就会触发离屏渲染,RenderServer会强制将CALayer的渲染位图结果bitmap保存下来,这样下次再需要渲染时就可以直接复用,从而提高效率。
    而保存的bitmap包含layer的subLayer、圆角、阴影、组透明度group opacity等,所以如果layer的构成包含以上几种元素,结构复杂且需要反复利用,那么就可以考虑打开光栅化。
    不过使用光栅化的时候需要注意:
  1. 如果 layer 不能被复用,则没有必要打开光栅化
  2. 如果 layer 不是静态,需要被频繁修改,比如处于动画之中,那么开启离屏渲染反而影响效率
  3. 离屏渲染缓存内容有时间限制,缓存内容 100ms 内如果没有被使用,那么就会被丢弃,无法进行复用
  4. 离屏渲染缓存空间有限,超过 2.5 倍屏幕像素大小的话也会失效,无法复用

如果在滚动tableView时,每次都执行圆角设置,肯定会阻塞UI,设置这个将会使滑动更加流畅。
当shouldRasterize设成true时,layer被渲染成一个bitmap,并缓存起来,等下次使用时不会再重新去渲染了。实现圆角本身就是在做颜色混合(blending),如果每次页面出来时都blending,消耗太大,这时shouldRasterize = yes,下次就只是简单的从渲染引擎的cache里读取那张bitmap,节约系统资源。
圆角的离屏渲染
什么情况下圆角会触发离屏渲染呢?
如果只是设置了cornerRadius而没有设置masksToBounds,由于不需要叠加剪裁,此时是不会出发离屏渲染的。而当设置了裁剪属性时,由于MasksToBounds会对layer以及所有subLayer的content都进行剪裁,所以不得不触发离屏渲染。
view.layer.masksToBounds = true
离屏渲染的具体逻辑
为什么圆角设置了masksToBounds就会触发离屏渲染呢?
画家算法
画家算法也叫作优先填充,它是三维计算机图形学中处理可见性问题的一种解决方法。当将三维场景投影到二维平面的时候,需要确定哪些多边形是可见的,哪些是不可见的。
用一个图来描述一下:

对于一个正常view的绘制
上层的sublayer会覆盖下层的sublayer,下层sublayer绘制完之后就可以抛弃了,从而节约空间提高效率。所有sublayer依次绘制完成之后,整个绘制过程完成,就可以进行后续的呈现了。

可当我们设置了圆角 + masksToBounds,如前文所示,masksToBounds裁剪属性会应用到所有的sublayer上。也就意味着每次有新视图时,原有视图并不能立即被丢弃,必须在离屏渲染缓冲区中临时保存,以等待下一轮圆角+剪裁。这也就诱发了离屏渲染。

实际上不光只是圆角 + 剪裁,如果设置了透明度 + 组透明度(layer.allowsGroupOpacity + layer.opacity),阴影属性等等都会产生类似效果,作用于layer以及其所有sublayer上,这就必然导致离屏渲染。

但是需要注意 并不是 圆角 + 剪裁 就会产生离屏渲染,如果加上了背景色、边框、其他图像内容,还是会产生离屏渲染,也很好理解,单层内容不需要用到离屏渲染技术。

避免圆角的离屏渲染

  1. 对图片进行剪裁,而不是对button进行剪裁。
  2. 使用贝塞尔曲线画圆角
  3. 第三方库YYImage。其内部使用了UIBezierPath+CoreGraphics生成了新的圆角图片。

动画探索

  1. 隐式动画
    每个UIView都有一个layer属性,它的类型是CALayer,属于QuartzCore框架。CALayer本身并不包含在UIKit中,它不能响应事件。由于CALayer在设计之初就考虑了它的动画操作功能,CALayer很多属性在修改时都能形成动画效果,这种属性称为“隐式动画属性”。 对每个UIView的非root layer对象属性进行修改时,都会形成隐式动画。之所以叫隐式是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去做动画。

CALayer的隐式动画实际上是自动执行了CATransaction动画,执行一次隐式动画大概是0.25秒。

在iOS4中,苹果对UIView添加了一种基于block的动画方法:+animateWithDuration:animations:。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。CATransaction的+begin和+commit方法在+animateWithDuration:animations:内部自动调用,这样block中所有属性的改变都会被事务所包含。

CATransaction事务
事务实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值。
事务是通过CATransaction类来做管理,这个类的设计有些奇怪,不像你从它的命名预期的那样去管理一个简单的事务,而是管理了一叠你不能访问的事务。CATransaction没有属性或者实例方法,并且也不能用+alloc和-init方法创建它。但是可以用+begin和+commit分别来入栈或者出栈。
任何可以做动画的图层属性都会被添加到栈顶的事务,你可以通过+setAnimationDuration:方法设置当前事务的动画时间,或者通过+animationDuration方法来获取值(默认0.25秒)。
Core Animation在每个run loop周期中自动开始一次新的事务(run loop是iOS负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西),即使你不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。

[CATransaction begin];//set the animation duration to 1 second[CATransaction setAnimationDuration:1.0];//add the spin animation on completion[CATransaction setCompletionBlock:^{//rotate the layer 90 degreesCGAffineTransform transform = self.colorLayer.affineTransform;transform = CGAffineTransformRotate(transform, M_PI_2);self.colorLayer.affineTransform = transform;}];//randomize the layer background colorCGFloat red = arc4random() / (CGFloat)INT_MAX;CGFloat green = arc4random() / (CGFloat)INT_MAX;CGFloat blue = arc4random() / (CGFloat)INT_MAX;self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;//commit the transaction[CATransaction commit];

2.显式动画
iOS显示动画有两类动画方式: UIKit 和 core animation
https://www.jianshu.com/p/043e7ec7f3ef
UIKit
UIKit动画实质上是对CoreAnimation的封装,提供简洁的动画接口。UIView动画可以设置的动画属性有:
a、大小变化(frame)
b、拉伸变化(bounds)
c、中心位置(center)
d、旋转(transform)
e、透明度(alpha)
f、背景颜色(backgroundColor)
g、拉伸内容(contentStretch)
CoreAnimation
在开发过程中,对于动画效果,我们常用的就是UIView动画,简单快捷,一个代码块能实现一个动画。
但是当有一些特殊的需求时,那你难免会有大量的Block嵌套产生,同时如何高效的控制动画效果,比如停止动画,控制动画节奏等会让人无从下手。此时CoreAnimation就派上用场了。
Core Animation(核心动画)是一组强大的动画 API,是直接操作 CALayer 层来产生动画,相比上述的 UIView 动画,可以实现更复杂的动画效果。
为了不阻塞主线程,Core Animation 的核心是 OpenGL ES 的一个抽象物,所以大部分的渲染是直接提交给GPU来处理。 而Core Graphics/Quartz 2D(UIBezierPath是Core Graphics框架关于路径的封装)的大部分绘制操作都是在主线程和CPU上同步完成的。

3.Lottie\webp

  1. Lottie 内部实现基于CoreAnimation,固定60帧,无法修改。而直播场景下,视频帧率通常为15帧,常见动画做到30帧保证流程即可,无需达到60;
  2. 部分Lottie动画存在离屏渲染;
  • 即便有离屏渲染,Lottie对全局影响有多大?
  • [离屏渲染] 数量与渲染区域大小根据结论,在列表场景中使用lottie动画出现离屏渲染时会严重影响GPU性能

WebP,是一种同时提供了有损压缩与无损压缩的图片文件格式,是Google推出的影像技术,它可让网页图档有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快。
WebP调用的本质还是initWithData:scale:这个方法,类似于去读取一个文件
优点:(google到的)

  • WebP 无损压缩的图片可以比同样大小的 PNG 小 26%;
  • WebP 有损压缩的图片可以比同样大小的 JPEG 小 25-34%;
  • WebP 支持无损的透明图层通道,代价只需增加 22% 的字节存储空间;
  • WebP 有损透明图像可以比同样大小的 PNG 图像小3倍。
    缺点:
  • 压缩时间长,大概是png的8倍左右(不过一般都是在服务端压缩,客户端解码,所以服务端可以做个预压缩)
  • 解码时间比png长,大概几十毫秒。WebP是节省了流量(图片小),增加了解码时间,换句话说就是:同样的图片,网络越快(图片更小的WebP就没有明显优势),图片越多(WebP要解码),WebP比png要慢。
  • UIWebView,WKWebView都不支持WebP。(UIWebView可以用NSUrlProtocol来解决,但是WKWebView还没有太完美的办法)
  • 不支持流式解压缩(即图片加载的时候会由模糊慢慢变清晰的过程,WebP貌似不支持这种解压缩方式)

iOS图像渲染 + 动画探索相关推荐

  1. IOS之未解问题--关于IOS图像渲染CPU和GPU

    前言:先上一个图.关于UIKit底层渲染机制,这个有待以后花大把时间收集资料和学习,然后汇总,将整篇"未解问题"去除. 反思:半个月前阿里电话面试,问道一个问题图像渲染什么时候回用 ...

  2. iOS 视图,动画渲染机制探究

    腾讯Bugly特约作者:陈向文 终端的开发,首当其冲的就是视图.动画的渲染,切换等等.用户使用 App 时最直接的体验就是这个界面好不好看,动画炫不炫,滑动流不流畅.UI就是 App 的门面,它的体验 ...

  3. 关东升的《iOS实战:图形图像、动画和多媒体卷(Swift版)》上市了

    关东升的<iOS实战:图形图像.动画和多媒体卷(Swift版)>上市了 承蒙广大读者的厚爱我的<iOS实战:图形图像.动画和多媒体卷(Swift版)>京东上市了,欢迎广大读者提 ...

  4. 英伟达的 VR 探索之路:图像渲染

    [转] https://www.leiphone.com/news/201712/lmC4klSfIiYBu9yX.html https://www.leiphone.com/news/201712/ ...

  5. 上帝视角任意切换:三维重建和图像渲染是怎么结合的?

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 导读 本文介绍了3D图像网站Photo Synth及其前身Photo Tourism,并讲解了它们的两 ...

  6. iOS 图片渲染及优化

    下图是一张超美的太空图,如果我的项目中要使用这么一张图片,我该怎么显示和优化了(如何显示和优化大图显示). 图片资源原始网站:在这里 假如这张图大小为:52kb 图片显示流程 1.加载 (数据缓冲区) ...

  7. ios 图像翻转_在iOS 14中使用计算机视觉的图像差异

    ios 图像翻转 Human eyes are very receptive to visual representations. Similarly, computer vision enables ...

  8. 《Adobe After Effects CS5经典教程》——1.5 对合成图像作动画处理

    本节书摘来自异步社区<Adobe After Effects CS5经典教程>一书中的第1章,第1.5节,作者 [美]Adobe公司 ,译者 许伟民,袁鹏飞,更多章节内容可以访问云栖社区& ...

  9. blender用视频做背景渲染动画节点设置

    首先要明确区分摄像机背景和最终渲染合成输出背景是两个不同的概念. 在摄像机设置背景时候,虽然可以在摄像机视图内看到背景影像,但是最终输出却不会显示这个背景,应为摄像机背景是作为一个参考,辅助我们观察的 ...

最新文章

  1. 书籍:Python机器学习蓝图第2版 Python Machine Learning Blueprints 2nd - 2019.pdf
  2. 中职计算机专业论文,中职教学中计算机专业论文
  3. python在线工具-在线 Python运行工具
  4. [Python unittest] 3-Organizing test code
  5. TCP与UDP网络编程总结(一)
  6. 前端神器avalonJS入门(二)
  7. Hibernate学习之hibernate.cfg.xml
  8. linux容器安卓下载,Docker 1.7.0 发布下载,Linux 容器引擎
  9. [C# 基础知识系列]专题十四:深入理解Lambda表达式
  10. 5008.vs2015创建c++动态库
  11. 塑料壳上下扣合的卡扣设计_一种塑料件卡扣结构制造技术
  12. PAT甲级1115 DFS和BST
  13. 电脑桌面跳出框计算机内存不足,电脑提示虚拟内存不足怎么办 电脑提示虚拟内存不足的原因分析和解决方法...
  14. java 数组优化_Java数组的jit优化问题?
  15. 【Clover】服务器环境中通过Clover boot引导黑群晖DSM(Linux)+Win系统的解决方案与常见bug排查
  16. 阿里云服务器与本地不能复制粘贴
  17. 径向基函数和粗糙集在进化多目标优化中的应用
  18. HDU 3687 National Day Parade(暴力)
  19. 计算机网络实验二 路由器的配置和静态路由
  20. [Python]线程实例化;互斥锁;线程间通信

热门文章

  1. win11管理我的账户提示“无法使用个人帐户在此登录,请改用工作或学校帐户”
  2. 所有用户账户被禁用该怎么办?
  3. 解决error ‘XXX‘ is not defined no-undef且项目没有eslintrc.js文件问题
  4. 如何给PDF文件添加书签及子书签
  5. mysql 数据类型 查询_MySQL数据类型
  6. 细说pc端微信扫码登录
  7. 如何保持长时间高效学习
  8. vlan 虚拟局域⽹
  9. magic3是鸿蒙系统吗,如果荣耀Magic3搭载屏下镜头和鸿蒙系统,你会做第一批吗?...
  10. 怎么把电脑上的python软件卸载干净_如何将电脑上的各种软件彻底卸载干净呢?...