但愿经过这篇文章从头至尾梳理一下 iOS 中涉及到渲染原理相关的内容,会先从计算机渲染原理讲起,慢慢说道 iOS 的渲染原理和框架,最后再深刻探讨一下离屏渲染。html
但愿能对你们有点帮助~ios
文章Github地址git
对于现代计算机系统,简单来讲能够大概视做三层架构:硬件、操做系统与进程。对于移动端来讲,进程就是 app,而 CPU 与 GPU 是硬件层面的重要组成部分。CPU 与 GPU 提供了计算能力,经过操做系统被 app 调用。github
CPU 和 GPU 其设计目标就是不一样的,它们分别针对了两种不一样的应用场景。CPU 是运算核心与控制核心,须要有很强的运算通用性,兼容各类数据类型,同时也须要能处理大量不一样的跳转、中断等指令,所以 CPU 的内部结构更为复杂。而 GPU 则面对的是类型统1、更加单纯的运算,也不须要处理复杂的指令,但也肩负着更大的运算任务。web
所以,CPU 与 GPU 的架构也不一样。由于 CPU 面临的状况更加复杂,所以从上图中也能够看出,CPU 拥有更多的缓存空间 Cache 以及复杂的控制单元,计算能力并非 CPU 的主要诉求。CPU 是设计目标是低时延,更多的高速缓存也意味着能够更快地访问数据;同时复杂的控制单元也能更快速地处理逻辑分支,更适合串行计算。面试
而 GPU 拥有更多的计算单元 Arithmetic Logic Unit,具备更强的计算能力,同时也具备更多的控制单元。GPU 基于大吞吐量而设计,每一部分缓存都链接着一个流处理器(stream processor),更加适合大规模的并行计算。objective-c
图像渲染流程粗粒度地大概分为下面这些步骤:算法
上述图像渲染流水线中,除了第一部分 Application 阶段,后续主要都由 GPU 负责,为了方便后文讲解,先将 GPU 的渲染流程图展现出来:编程
上图就是一个三角形被渲染的过程当中,GPU 所负责的渲染流水线。能够看到简单的三角形绘制就须要大量的计算,若是再有更多更复杂的顶点、颜色、纹理信息(包括 3D 纹理),那么计算量是不可思议的。这也是为何 GPU 更适合于渲染流程。缓存
接下来,具体讲解渲染流水线中各个部分的具体任务:
Application 应用处理阶段:获得图元
这个阶段具体指的就是图像在应用中被处理的阶段,此时还处于 CPU 负责的时期。在这个阶段应用可能会对图像进行一系列的操做或者改变,最终将新的图像信息传给下一阶段。这部分信息被叫作图元(primitives),一般是三角形、线段、顶点等。
Geometry 几何处理阶段:处理图元
进入这个阶段以后,以及以后的阶段,就都主要由 GPU 负责了。此时 GPU 能够拿到上一个阶段传递下来的图元信息,GPU 会对这部分图元进行处理,以后输出新的图元。这一系列阶段包括:
Rasterization 光栅化阶段:图元转换为像素
光栅化的主要目的是将几何渲染以后的图元信息,转换为一系列的像素,以便后续显示在屏幕上。这个阶段中会根据图元信息,计算出每一个图元所覆盖的像素信息等,从而将像素划分红不一样的部分。
一种简单的划分就是根据中心点,若是像素的中心点在图元内部,那么这个像素就属于这个图元。如上图所示,深蓝色的线就是图元信息所构建出的三角形;而经过是否覆盖中心点,能够遍历出全部属于该图元的全部像素,即浅蓝色部分。
Pixel 像素处理阶段:处理像素,获得位图
通过上述光栅化阶段,咱们获得了图元所对应的像素,此时,咱们须要给这些像素填充颜色和效果。因此最后这个阶段就是给像素填充正确的内容,最终显示在屏幕上。这些通过处理、蕴含大量信息的像素点集合,被称做位图(bitmap)。也就是说,Pixel 阶段最终输出的结果就是位图,过程具体包含:
这些点能够进行不一样的排列和染色以构成图样。当放大位图时,能够看见赖以构成整个图像的无数单个方块。只要有足够多的不一样色彩的像素,就能够制做出色彩丰富的图象,逼真地表现天然界的景象。缩放和旋转容易失真,同时文件容量较大。
在图像渲染流程结束以后,接下来就须要将获得的像素信息显示在物理屏幕上了。GPU 最后一步渲染结束以后像素信息,被存在帧缓冲器(Framebuffer)中,以后视频控制器(Video Controller)会读取帧缓冲器中的信息,通过数模转换传递给显示器(Monitor),进行显示。完整的流程以下图所示:
通过 GPU 处理以后的像素集合,也就是位图,会被帧缓冲器缓存起来,供以后的显示使用。显示器的电子束会从屏幕的左上角开始逐行扫描,屏幕上的每一个点的图像信息都从帧缓冲器中的位图进行读取,在屏幕上对应地显示。扫描的流程以下图所示:
电子束扫描的过程当中,屏幕就能呈现出对应的结果,每次整个屏幕被扫描完一次后,就至关于呈现了一帧完整的图像。屏幕不断地刷新,不停呈现新的帧,就能呈现出连续的影像。而这个屏幕刷新的频率,就是帧率(Frame per Second,FPS)。因为人眼的视觉暂留效应,当屏幕刷新频率足够高时(FPS 一般是 50 到 60 左右),就能让画面看起来是连续而流畅的。对于 iOS 而言,app 应该尽可能保证 60 FPS 才是最好的体验。
在这种单一缓存的模式下,最理想的状况就是一个流畅的流水线:每次电子束从头开始新的一帧的扫描时,CPU+GPU 对于该帧的渲染流程已经结束,渲染好的位图已经放入帧缓冲器中。但这种完美的状况是很是脆弱的,很容易产生屏幕撕裂:
CPU+GPU 的渲染流程是一个很是耗时的过程。若是在电子束开始扫描新的一帧时,位图尚未渲染好,而是在扫描到屏幕中间时才渲染完成,被放入帧缓冲器中 ---- 那么已扫描的部分就是上一帧的画面,而未扫描的部分则会显示新的一帧图像,这就形成屏幕撕裂。
解决屏幕撕裂、提升显示效率的一个策略就是使用垂直同步信号 Vsync 与双缓冲机制 Double Buffering。根据苹果的官方文档描述,iOS 设备会始终使用 Vsync + Double Buffering 的策略。
垂直同步信号(vertical synchronisation,Vsync)至关于给帧缓冲器加锁:当电子束完成一帧的扫描,将要从头开始扫描时,就会发出一个垂直同步信号。只有当视频控制器接收到 Vsync 以后,才会将帧缓冲器中的位图更新为下一帧,这样就能保证每次显示的都是同一帧的画面,于是避免了屏幕撕裂。
可是这种状况下,视频控制器在接受到 Vsync 以后,就要将下一帧的位图传入,这意味着整个 CPU+GPU 的渲染流程都要在一瞬间完成,这是明显不现实的。因此双缓冲机制会增长一个新的备用缓冲器(back buffer)。渲染结果会预先保存在 back buffer 中,在接收到 Vsync 信号的时候,视频控制器会将 back buffer 中的内容置换到 frame buffer 中,此时就能保证置换操做几乎在一瞬间完成(其实是交换了内存地址)。
启用 Vsync 信号以及双缓冲机制以后,可以解决屏幕撕裂的问题,可是会引入新的问题:掉帧。若是在接收到 Vsync 之时 CPU 和 GPU 尚未渲染好新的位图,视频控制器就不会去替换 frame buffer 中的位图。这时屏幕就会从新扫描呈现出上一帧如出一辙的画面。至关于两个周期显示了一样的画面,这就是所谓掉帧的状况。
如图所示,A、B 表明两个帧缓冲器,当 B 没有渲染完毕时就接收到了 Vsync 信号,因此屏幕只能再显示相同帧 A,这就发生了第一次的掉帧。
事实上上述策略还有优化空间。咱们注意到在发生掉帧的时候,CPU 和 GPU 有一段时间处于闲置状态:当 A 的内容正在被扫描显示在屏幕上,而 B 的内容已经被渲染好,此时 CPU 和 GPU 就处于闲置状态。那么若是咱们增长一个帧缓冲器,就能够利用这段时间进行下一步的渲染,并将渲染结果暂存于新增的帧缓冲器中。
如图所示,因为增长了新的帧缓冲器,能够必定程度上地利用掉帧的空档期,合理利用 CPU 和 GPU 性能,从而减小掉帧的次数。
手机使用卡顿的直接缘由,就是掉帧。前文也说过,屏幕刷新频率必需要足够高才能流畅。对于 iPhone 手机来讲,屏幕最大的刷新频率是 60 FPS,通常只要保证 50 FPS 就已是较好的体验了。可是若是掉帧过多,致使刷新频率太低,就会形成不流畅的使用体验。
这样看来,能够大概总结一下
iOS 的渲染框架依然符合渲染流水线的基本架构,具体的技术栈如上图所示。在硬件基础之上,iOS 中有 Core Graphics、Core Animation、Core Image、OpenGL 等多种软件框架来绘制内容,在 CPU 与 GPU 之间进行了更高层地封装。
GPU Driver:上述软件框架相互之间也有着依赖关系,不过全部框架最终都会经过 OpenGL 链接到 GPU Driver,GPU Driver 是直接和 GPU 交流的代码块,直接与 GPU 链接。
OpenGL:是一个提供了 2D 和 3D 图形渲染的 API,它能和 GPU 密切的配合,最高效地利用 GPU 的能力,实现硬件加速渲染。OpenGL的高效实现(利用了图形加速硬件)通常由显示设备厂商提供,并且很是依赖于该厂商提供的硬件。OpenGL 之上扩展出不少东西,如 Core Graphics 等最终都依赖于 OpenGL,有些状况下为了更高的效率,好比游戏程序,甚至会直接调用 OpenGL 的接口。
Core Graphics:Core Graphics 是一个强大的二维图像绘制引擎,是 iOS 的核心图形库,经常使用的好比 CGRect 就定义在这个框架下。
Core Animation:在 iOS 上,几乎全部的东西都是经过 Core Animation 绘制出来,它的自由度更高,使用范围也更广。
Core Image:Core Image 是一个高性能的图像处理分析的框架,它拥有一系列现成的图像滤镜,能对已存在的图像进行高效的处理。
Metal:Metal 相似于 OpenGL ES,也是一套第三方标准,具体实现由苹果实现。Core Animation、Core Image、SceneKit、SpriteKit 等等渲染框架都是构建于 Metal 之上的。
Render, compose, and animate visual elements. ---- Apple
Core Animation,它本质上能够理解为一个复合引擎,主要职责包含:渲染、构建和实现动画。
一般咱们会使用 Core Animation 来高效、方便地实现动画,可是实际上它的前身叫作 Layer Kit,关于动画实现只是它功能中的一部分。对于 iOS app,不管是否直接使用了 Core Animation,它都在底层深度参与了 app 的构建。而对于 OS X app,也能够经过使用 Core Animation 方便地实现部分功能。
Core Animation 是 AppKit 和 UIKit 完美的底层支持,同时也被整合进入 Cocoa 和 Cocoa Touch 的工做流之中,它是 app 界面渲染和构建的最基础架构。 Core Animation 的职责就是尽量快地组合屏幕上不一样的可视内容,这个内容是被分解成独立的 layer(iOS 中具体而言就是 CALayer),而且被存储为树状层级结构。这个树也造成了 UIKit 以及在 iOS 应用程序当中你所能在屏幕上看见的一切的基础。
简单来讲就是用户能看到的屏幕上的内容都由 CALayer 进行管理。那么 CALayer 到底是如何进行管理的呢?另外在 iOS 开发过程当中,最大量使用的视图控件其实是 UIView 而不是 CALayer,那么他们二者的关系到底如何呢?
简单理解,CALayer 就是屏幕显示的基础。那 CALayer 是如何完成的呢?让咱们来从源码向下探索一下,在 CALayer.h 中,CALayer 有这样一个属性 contents:
/** Layer content properties and methods. **/
/* An object providing the contents of the layer, typically a CGImageRef,
* but may be something else. (For example, NSImage objects are
* supported on Mac OS X 10.6 and later.) Default value is nil.
* Animatable. */
@property(nullable, strong) id contents;
复制代码
An object providing the contents of the layer, typically a CGImageRef.
contents 提供了 layer 的内容,是一个指针类型,在 iOS 中的类型就是 CGImageRef(在 OS X 中还能够是 NSImage)。而咱们进一步查到,Apple 对 CGImageRef 的定义是:
A bitmap image or image mask.
看到 bitmap,这下咱们就能够和以前讲的的渲染流水线联系起来了:实际上,CALayer 中的 contents 属性保存了由设备渲染流水线渲染好的位图 bitmap(一般也被称为 backing store),而当设备屏幕进行刷新时,会从 CALayer 中读取生成好的 bitmap,进而呈现到屏幕上。
因此,若是咱们在代码中对 CALayer 的 contents 属性进行了设置,好比这样:
// 注意 CGImage 和 CGImageRef 的关系:
// typedef struct CGImage CGImageRef;
layer.contents = (__bridge id)image.CGImage;
复制代码
那么在运行时,操做系统会调用底层的接口,将 image 经过 CPU+GPU 的渲染流水线渲染获得对应的 bitmap,存储于 CALayer.contents 中,在设备屏幕进行刷新的时候就会读取 bitmap 在屏幕上呈现。
也正由于每次要被渲染的内容是被静态的存储起来的,因此每次渲染时,Core Animation 会触发调用 drawRect:
方法,使用存储好的 bitmap 进行新一轮的展现。
UIView 做为最经常使用的视图控件,和 CALayer 也有着千丝万缕的联系,那么二者之间究竟是个什么关系,他们有什么差别?
固然,二者有不少显性的区别,好比是否可以响应点击事件。但为了从根本上完全搞懂这些问题,咱们必需要先搞清楚二者的职责。
Views are the fundamental building blocks of your app's user interface, and the
UIView
class defines the behaviors that are common to all views. A view object renders content within its bounds rectangle and handles any interactions with that content.
根据 Apple 的官方文档,UIView 是 app 中的基本组成结构,定义了一些统一的规范。它会负责内容的渲染以及,处理交互事件。具体而言,它负责的事情能够归为下面三类
Layers are often used to provide the backing store for views but can also be used without a view to display content. A layer’s main job is to manage the visual content that you provide...
If the layer object was created by a view, the view typically assigns itself as the layer’s delegate automatically, and you should not change that relationship.
而从 CALayer 的官方文档中咱们能够看出,CALayer 的主要职责是管理内部的可视内容,这也和咱们前文所讲的内容吻合。当咱们建立一个 UIView 的时候,UIView 会自动建立一个 CALayer,为自身提供存储 bitmap 的地方(也就是前文说的 backing store),并将自身固定设置为 CALayer 的代理。
从这儿咱们大概总结出下面两个核心关系:
有了这两个最关键的根本关系,那么下面这些常常出如今面试答案里的显性的异同就很好解释了。举几个例子:
相同的层级结构:咱们对 UIView 的层级结构很是熟悉,因为每一个 UIView 都对应 CALayer 负责页面的绘制,因此 CALayer 也具备相应的层级结构。
部分效果的设置:由于 UIView 只对 CALayer 的部分功能进行了封装,而另外一部分如圆角、阴影、边框等特效都须要经过调用 layer 属性来设置。
是否响应点击事件:CALayer 不负责点击事件,因此不响应点击事件,而 UIView 会响应。
不一样继承关系:CALayer 继承自 NSObject,UIView 因为要负责交互事件,因此继承自 UIResponder。
固然还剩最后一个问题,为何要将 CALayer 独立出来,直接使用 UIView 统一管理不行吗?为何不用一个统一的对象来处理全部事情呢?
这样设计的主要缘由就是为了职责分离,拆分功能,方便代码的复用。经过 Core Animation 框架来负责可视内容的呈现,这样在 iOS 和 OS X 上均可以使用 Core Animation 进行渲染。与此同时,两个系统还能够根据交互规则的不一样来进一步封装统一的控件,好比 iOS 有 UIKit 和 UIView,OS X 则是AppKit 和 NSView。
当咱们了解了 Core Animation 以及 CALayer 的基本知识后,接下来咱们来看下 Core Animation 的渲染流水线。
整个流水线一共有下面几个步骤:
Handle Events:这个过程当中会先处理点击事件,这个过程当中有可能会须要改变页面的布局和界面层次。
**Commit Transaction:**此时 app 会经过 CPU 处理显示内容的前置计算,好比布局计算、图片解码等任务,接下来会进行详细的讲解。以后将计算好的图层进行打包发给 Render Server
。
**Decode:**打包好的图层被传输到 Render Server
以后,首先会进行解码。注意完成解码以后须要等待下一个 RunLoop 才会执行下一步 Draw Calls
。
**Draw Calls:**解码完成后,Core Animation 会调用下层渲染框架(好比 OpenGL 或者 Metal)的方法进行绘制,进而调用到 GPU。
**Render:**这一阶段主要由 GPU 进行渲染。
**Display:**显示阶段,须要等 render
结束的下一个 RunLoop 触发显示。
通常开发当中能影响到的就是 Handle Events 和 Commit Transaction 这两个阶段,这也是开发者接触最多的部分。Handle Events 就是处理触摸事件,而 Commit Transaction 这部分中主要进行的是:Layout、Display、Prepare、Commit 等四个具体的操做。
Layout:构建视图
这个阶段主要处理视图的构建和布局,具体步骤包括:
layoutSubviews
方法addSubview
方法添加子视图因为这个阶段是在 CPU 中进行,一般是 CPU 限制或者 IO 限制,因此咱们应该尽可能高效轻量地操做,减小这部分的时间,好比减小非必要的视图建立、简化布局计算、减小视图层级等。
Display:绘制视图
这个阶段主要是交给 Core Graphics 进行视图的绘制,注意不是真正的显示,而是获得前文所说的图元 primitives 数据:
drawRect:
方法,那么会调用重载的 drawRect:
方法,在 drawRect:
方法中手动绘制获得 bitmap 数据,从而自定义视图的绘制。注意正常状况下 Display 阶段只会获得图元 primitives 信息,而位图 bitmap 是在 GPU 中根据图元信息绘制获得的。可是若是重写了 drawRect:
方法,这个方法会直接调用 Core Graphics 绘制方法获得 bitmap 数据,同时系统会额外申请一块内存,用于暂存绘制好的 bitmap。
因为重写了 drawRect:
方法,致使绘制过程从 GPU 转移到了 CPU,这就致使了必定的效率损失。与此同时,这个过程会额外使用 CPU 和内存,所以须要高效绘制,不然容易形成 CPU 卡顿或者内存爆炸。
Prepare:Core Animation 额外的工做
这一步主要是:图片解码和转换
Commit:打包并发送
这一步主要是:图层打包并发送到 Render Server。
注意 commit 操做是依赖图层树递归执行的,因此若是图层树过于复杂,commit 的开销就会很大。这也是咱们但愿减小视图层级,从而下降图层树复杂度的缘由。
Render Server 一般是 OpenGL 或者是 Metal。以 OpenGL 为例,那么上图主要是 GPU 中执行的操做,具体主要包括:
使用 Instrument 的 OpenGL ES,能够对过程进行监控。OpenGL ES tiler utilization 和 OpenGL ES renderer utilization 能够分别监控 Tiler 和 Renderer 的工做状况
离屏渲染做为一个面试高频问题,时常被说起,下面来从头至尾讲一下离屏渲染。
根据前文,简化来看,一般的渲染流程是这样的:
App 经过 CPU 和 GPU 的合做,不停地将内容渲染完成放入 Framebuffer 帧缓冲器中,而显示屏幕不断地从 Framebuffer 中获取内容,显示实时的内容。
而离屏渲染的流程是这样的:
与普通状况下 GPU 直接将渲染好的内容放入 Framebuffer 中不一样,须要先额外建立离屏渲染缓冲区 Offscreen Buffer,将提早渲染好的内容放入其中,等到合适的时机再将 Offscreen Buffer 中的内容进一步叠加、渲染,完成后将结果切换到 Framebuffer 中。
从上面的流程来看,离屏渲染时因为 App 须要提早对部份内容进行额外的渲染并保存到 Offscreen Buffer,以及须要在必要时刻对 Offscreen Buffer 和 Framebuffer 进行内容切换,因此会须要更长的处理时间(实际上这两步关于 buffer 的切换代价都很是大)。
而且 Offscreen Buffer 自己就须要额外的空间,大量的离屏渲染可能早能内存的过大压力。与此同时,Offscreen Buffer 的总大小也有限,不能超过屏幕总像素的 2.5 倍。
可见离屏渲染的开销很是大,一旦须要离屏渲染的内容过多,很容易形成掉帧的问题。因此大部分状况下,咱们都应该尽可能避免离屏渲染。
那么为何要使用离屏渲染呢?主要是由于下面这两种缘由:
对于第一种状况,也就是不得不使用离屏渲染的状况,通常都是系统自动触发的,好比阴影、圆角等等。
最多见的情形之一就是:使用了 mask 蒙版。
如图所示,因为最终的内容是由两层渲染结果叠加,因此必需要利用额外的内存空间对中间的渲染结果进行保存,所以系统会默认触发离屏渲染。
又好比下面这个例子,iOS 8 开始提供的模糊特效 UIBlurEffectView:
整个模糊过程分为多步:Pass 1 先渲染须要模糊的内容自己,Pass 2 对内容进行缩放,Pass 3 4 分别对上一步内容进行横纵方向的模糊操做,最后一步用模糊后的结果叠加合成,最终实现完整的模糊特效。
而第二种状况,为了复用提升效率而使用离屏渲染通常是主动的行为,是经过 CALayer 的 shouldRasterize 光栅化操做实现的。
When the value of this property is
YES
, the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content.
开启光栅化后,会触发离屏渲染,Render Server 会强制将 CALayer 的渲染位图结果 bitmap 保存下来,这样下次再须要渲染时就能够直接复用,从而提升效率。
而保存的 bitmap 包含 layer 的 subLayer、圆角、阴影、组透明度 group opacity 等,因此若是 layer 的构成包含上述几种元素,结构复杂且须要反复利用,那么就能够考虑打开光栅化。
圆角、阴影、组透明度等会由系统自动触发离屏渲染,那么打开光栅化能够节约第二次及之后的渲染时间。而多层 subLayer 的状况因为不会自动触发离屏渲染,因此相比之下会多花费第一次离屏渲染的时间,可是能够节约后续的重复渲染的开销。
不过使用光栅化的时候须要注意如下几点:
一般来说,设置了 layer 的圆角效果以后,会自动触发离屏渲染。可是究竟什么状况下设置圆角才会触发离屏渲染呢?
如上图所示,layer 由三层组成,咱们设置圆角一般会首先像下面这行代码同样进行设置:
view.layer.cornerRadius = 2
复制代码
根据 cornerRadius - Apple 的描述,上述代码只会默认设置 backgroundColor 和 border 的圆角,而不会设置 content 的圆角,除非同时设置了 layer.masksToBounds 为 true(对应 UIView 的 clipsToBounds 属性):
Setting the radius to a value greater than
0.0
causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’scontents
property; it applies only to the background color and border of the layer. However, setting themasksToBounds
property totrue
causes the content to be clipped to the rounded corners.
若是只是设置了 cornerRadius 而没有设置 masksToBounds,因为不须要叠加裁剪,此时是并不会触发离屏渲染的。而当设置了裁剪属性的时候,因为 masksToBounds 会对 layer 以及全部 subLayer 的 content 都进行裁剪,因此不得不触发离屏渲染。
view.layer.masksToBounds = true // 触发离屏渲染的缘由
复制代码
因此,Texture 也提出在没有必要使用圆角裁剪的时候,尽可能不去触发离屏渲染而影响效率:
刚才说了圆角加上 masksToBounds 的时候,由于 masksToBounds 会对 layer 上的全部内容进行裁剪,从而诱发了离屏渲染,那么这个过程具体是怎么回事呢,下面咱们来仔细讲一下。
图层的叠加绘制大概遵循“画家算法”,在这种算法下会按层绘制,首先绘制距离较远的场景,而后用绘制距离较近的场景覆盖较远的部分。
在普通的 layer 绘制中,上层的 sublayer 会覆盖下层的 sublayer,下层 sublayer 绘制完以后就能够抛弃了,从而节约空间提升效率。全部 sublayer 依次绘制完毕以后,整个绘制过程完成,就能够进行后续的呈现了。假设咱们须要绘制一个三层的 sublayer,不设置裁剪和圆角,那么整个绘制过程就以下图所示:
而当咱们设置了 cornerRadius 以及 masksToBounds 进行圆角 + 裁剪时,如前文所述,masksToBounds 裁剪属性会应用到全部的 sublayer 上。这也就意味着全部的 sublayer 必需要从新被应用一次圆角+裁剪,这也就意味着全部的 sublayer 在第一次被绘制完以后,并不能马上被丢弃,而必需要被保存在 Offscreen buffer 中等待下一轮圆角+裁剪,这也就诱发了离屏渲染,具体过程以下:
实际上不仅是圆角+裁剪,若是设置了透明度+组透明(layer.allowsGroupOpacity
+layer.opacity
),阴影属性(shadowOffset
等)都会产生相似的效果,由于组透明度、阴影都是和裁剪相似的,会做用与 layer 以及其全部 sublayer 上,这就致使必然会引发离屏渲染。
除了尽可能减小圆角裁剪的使用,还有什么别的办法能够避免圆角+裁剪引发的离屏渲染吗?
因为刚才咱们提到,圆角引发离屏渲染的本质是裁剪的叠加,致使 masksToBounds 对 layer 以及全部 sublayer 进行二次处理。那么咱们只要避免使用 masksToBounds 进行二次处理,而是对全部的 sublayer 进行预处理,就能够只进行“画家算法”,用一次叠加就完成绘制。
那么可行的实现方法大概有下面几种:
drawRect:
,用 CoreGraphics 相关方法,在须要应用圆角时进行手动绘制。不过 CoreGraphics 效率也颇有限,若是须要屡次调用也会有效率问题。总结一下,下面几种状况会触发离屏渲染:
layer.mask
)layer.masksToBounds
/ view.clipsToBounds
)layer.allowsGroupOpacity
/layer.opacity
)layer.shadow*
)layer.shouldRasterize
)UILabel
, CATextLayer
, Core Text
等)不过,须要注意的是,重写 drawRect:
方法并不会触发离屏渲染。前文中咱们提到过,重写 drawRect:
会将 GPU 中的渲染操做转移到 CPU 中完成,而且须要额外开辟内存空间。但根据苹果工程师的说法,这和标准意义上的离屏渲染并不同,在 Instrument 中开启 Color offscreen rendered yellow 调试时也会发现这并不会被判断为离屏渲染。
通常来讲作点题才能加深理解和巩固,因此这里从文章里简单提炼了一些,但愿能帮到你们:
参考文献: