iOS 渲染流程

作者: 卖馍工程师 | 来源:发表于2020-07-09 11:33 被阅读0次

图形渲染技术栈

iOS图形渲染技术栈

如图,App 使用Core GraphicsCore AnimationCore Image 等框架来绘制可视化内容,这些软件框架相互之间也有着依赖关系。这些框架都需要通过 OpenGL 来调用 GPU 进行绘制,最终将内容显示到屏幕之上。

iOS 渲染技术框架

  • UIKit

我们日常开发中使用的用户交互组件都来自于 UIKit Framework,我们通过设置 UIKit 组件的 Layout 以及 BackgroundColor 等属性来完成日常的界面绘画工作。

其实 UIKit Framework 自身并不具备在屏幕成像的能力,它主要负责对用户操作事件的响应UIView继承自UIResponder),事件响应的传递大体是经过逐层的视图树遍历实现的。

那么我们日常写的 UIKit 组件为什么可以呈现在 iOS 设备的屏幕上呢?-->Core Animation

  • Core Animation

Core Animation 其实是一个令人误解的命名,它是从一个叫做 Layer Kit 这么一个不怎么和动画有关的名字演变而来的,所以做动画仅仅是 Core Animation 特性的冰山一角。

Core Animation 本质上可以理解为是一个复合引擎,旨在尽可能快的组合屏幕上不同的显示内容。这些显示内容被分解成独立的图层(即 CALayer),这些图层会被存储在一个叫做图层树的体系之中。从本质上而言,CALayer 才是你所能在屏幕上看见的一切的基础。

  • Core Graphics

Core Graphics Framework 基于 Quartz 高级绘图引擎。它提供了具有无与伦比的输出保真度的低级别轻量级 2D 渲染。您可以使用此框架来处理基于路径的绘图,转换,颜色管理,离屏渲染,图案,渐变和阴影,图像数据管理,图像创建和图像遮罩以及 PDF 文档创建,显示和分析。

在 Mac OS X 中,Core Graphics 还包括用于处理显示硬件,低级用户输入事件和窗口系统的服务。

当开发者需要在 运行时创建图像 时,可以使用 Core Graphics去绘制。与之相对的是 运行前创建图像,例如用 Photoshop 提前做好图片素材直接导入应用。相比之下,我们更需要Core Graphics去在运行时实时计算、绘制一系列图像帧来实现动画。

  • Core Image

Core Image与 Core Graphics 恰恰相反,Core Graphics 用于在 运行时创建图像,而Core Image 是用来处理 运行前创建的图像 的。Core Image框架拥有一系列现成的图像过滤器,能对已存在的图像进行高效的处理。

大部分情况下,Core Image 会在 GPU 中完成工作,但如果 GPU 忙,会使用 CPU 进行处理。

  • OpenGL ES

OpenGL ES(OpenGL for Embedded Systems,简称 GLES),是 OpenGL 的子集。OpenGL 是一套第三方标准,通常面向图形硬件加速处理单元(GPU)渲染 2D 和 3D 计算机图形。OpenGL ES 专为智能手机,平板电脑,视频游戏机和 PDA 等嵌入式系统而设计 。

  • Metal

Metal类似于 OpenGL ES,也是一套第三方标准,具体实现由苹果实现。大多数开发者都没有直接使用过Metal,但其实所有开发者都在间接地使用 Metal。Core Animation、Core Image、SceneKit、SpriteKit 等等渲染框架都是构建于Metal之上的。

UIView 与 CALayer 的关系

在前面的 Core Animation 简介中提到 CALayer 才是用户所能在屏幕上看见的一切的基础。为什么 UIKit 中的视图能够呈现可视化内容?就是因为 UIKit 中的每一个 UI 视图控件其实内部都有一个关联的 CALayer,也就是 backing layer。

正是因为这种一一对应的关系,所以 CALayer 也是树形结构的,我们称之为 图层树。视图层级拥有 视图树 的树形结构,对应 CALayer 层级也拥有 图层树 的树形结构。

视图的职责就是 创建并管理 这个图层,以确保当子视图在层级关系中 添加或被移除 的时候,其关联的图层在图层树中也有相同的操作,即保证视图树和图层树在结构上的一致性。

那么为什么 iOS 要基于 UIView 和 CALayer 提供两个平行的层级关系呢?为什么不用一个简单的层级关系来处理所有事情呢?

原因在于要做职责分离,这样也能避免很多重复代码。在 iOS 和 Mac OS X 两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘的交互有着本质的区别,这就是为什么 iOS 有 UIKit 和 UIView,而 Mac OS X 有 AppKit 和 NSView 的原因。他们功能上很相似,但是在实现上有着显著的区别。

实际上,这里并不是两个层级关系,而是四个,每一个都扮演不同的角色,除了视图树图层树之外,还存在呈现树渲染树

  • CALayer

那么为什么CALayer 可以呈现可视化内容呢?因为 CALayer 基本等同于一个 纹理纹理 是 GPU 进行图像渲染的重要依据。

纹理 本质上就是一张图片,因此 CALayer 也包含一个 contents 属性指向一块缓存区,称为 backing store,可以存放位图(Bitmap)。iOS 中将该缓存区保存的图片称为 寄宿图

图形渲染流水线支持从顶点开始进行绘制(在流水线中,顶点会被处理生成纹理),也支持直接使用纹理(图片)进行渲染。相应地,在实际开发中,绘制界面也有两种方式:一种是 手动绘制;另一种是 使用图片

对此,iOS 中也有两种相应的实现方式:

  • 使用图片:contents image
  • 手动绘制:custom drawing

Contents Image

Contents Image 是指通过 CALayer 的 contents 属性来配置图片。然而,contents 属性的类型为 id。在这种情况下,可以给 contents 属性赋予任何值,app 仍可以编译通过。但是在实践中,如果 content 的值不是 CGImage ,得到的图层将是空白的。

既然如此,为什么要将 contents 的属性类型定义为 id 而非 CGImage。这是因为在 Mac OS 系统中,该属性对 CGImage 和 NSImage 类型的值都起作用,而在 iOS 系统中,该属性只对 CGImage 起作用。

本质上,contents 属性指向的一块缓存区域,称为 backing store,可以存放 bitmap 数据。

Custom Drawing

Custom Drawing 是指使用 Core Graphics 直接绘制 寄宿图。实际开发中,一般通过继承 UIView 并实现 -drawRect:方法来自定义绘制。

虽然 -drawRect: 是一个 UIView 方法,但事实上都是底层的 CALayer 完成了重绘工作并保存了产生的图片。下图所示为-drawRect:绘制定义 寄宿图 的基本原理。

  • UIView 有一个关联图层,即 CALayer
  • CALayer 有一个可选的 delegate 属性,实现了 CALayerDelegate 协议。UIView作为CALayer 的代理实现了 CALayerDelegae 协议。
  • 当需要重绘时,即调用-drawRect:CALayer请求其代理给予一个 寄宿图 来显示。
  • CALayer 首先会尝试调用 -displayLayer:方法,此时代理可以直接设置 contents 属性。
- (void)displayLayer:(CALayer *)layer;
  • 如果代理没有实现 -displayLayer: 方法,CALayer 则会尝试调用 -drawLayer:inContext: 方法。在调用该方法前,CALayer 会创建一个 空的寄宿图(尺寸由 bounds 和 contentScale 决定)和一个 Core Graphics 的绘制上下文,为绘制寄宿图做准备,作为 ctx 参数传入。
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
  • 最后,由Core Graphics 绘制生成的寄宿图会存入 backing store

Core Animation 流水线

通过前面的介绍,我们知道了CALayer的本质,那么它是如何调用 GPU 并显示可视化内容的呢? 下面我们就需要介绍一下 Core Animation 流水线的工作原理。

事实上,app 本身并不负责渲染,渲染则是由一个独立的进程负责,即 Render Server进程。

App 通过 IPC 将渲染任务及相关数据提交给 Render ServerRender Server 处理完数据后,再传递至GPU。最后由GPU 调用 iOS 的图像设备进行显示。

Core Animation 流水线的详细过程如下:

  • Handle Events:首先,由 app 处理事件,如:用户的点击操作,在此过程中 app 可能需要更新 视图树,相应地,图层树 也会被更新。
  • Commit Transaction:其次,app 通过 CPU 完成对显示内容的计算,如:视图的创建、布局计算、图片解码、文本绘制等。在完成对显示内容的计算之后,app 对图层进行打包,并在下一次 RunLoop 时将其发送至Render Server,即完成了一次 Commit Transaction 操作。
  • Decode: 打包好的图层被传输到Render server之后,首先会进行解码。解码完成之后需要等待下一个RunLoop才会执行Draw Calls
  • Draw Calls: 解码完成后,Core Animation(Render Server)会调用下层渲染框架( Open GL/Metal)的方法进行绘制,进而调用到GPU。
  • GPU则在物理层上完成了对图像的渲染。
  • 最终,GPU 通过Frame Buffer视频控制器等相关部件,将图像显示在屏幕上。

对上述步骤进行串联,它们执行所消耗的时间远远超过 16.67 ms,因此为了满足对屏幕的 60 FPS 刷新率的支持,需要将这些步骤进行分解,通过流水线的方式进行并行执行,如下图所示。

Commit Transaction

Core Animation Pipeline 的整个管线中 iOS 常规开发一般可以影响到的范围也就仅仅是在 Application 中布局 UIKit 视图控件间接的关联 Core Animation 图层这一级,即 Commit Transaction 之前的一些操作

那么在 Commit Transaction 之前我们一般要做的事情有哪些?

  • Layout
  • Display
  • Prepare
  • Commit

Layout

Layout阶段计算工作是在CPU完成的,主要进行 视图构建,包括:LayoutSubviews方法的重载,addSubview:方法填充子视图等。

在 Layout 阶段我们能做的是把 constraint 写的尽量高效,iOS 的 Layout Constraint 类似于 Android 的 Relative Layout。

iOS 的 Layout Constraint 在书写时应该尽量少的依赖于视图树中同层级的兄弟视图节点,它会拖慢整个视图树的 Layout 计算过程。

Display

Display 阶段主要进行 视图绘制,其实这里的 Display 仅仅是我们设置 iOS 设备要最终成像的图元数据而已。重载视图的 drawRect:方法可以自定义 UIView 的显示,其原理是在 drawRect:方法内部绘制 bitmap

重载 drawRect: 方法绘制 bitmap 过程 使用 CPU 和 内存

所以重载 drawRect: 使用不当会造成 CPU 负载过重,App 内存飙升等问题。

Prepare

这个步骤属于附加步骤,一般处理图像的解码 & 转换等操作。

Commit

Commit 阶段主要将图层进行打包,并将它们发送至 Render Server。

Commit 操作会递归执行,由于图层和视图都是以树形结构存在的,当图层树过于复杂时 Commit 操作的开销也会非常大。

CATransaction

CATransaction 是 Core Animation 中用于将多个图层树操作分配到渲染树的 原子更新 中的机制,对图层树的每个修改都必须是事务的一部分。

CATransaction 类没有属性或者实例方法,并且也不能用 +alloc-init方法创建它,我们只能用类方法 +begin+commit分别来入栈或者出栈。

事实上任何可动画化的图层属性都会被添加到栈顶的事务,你可以通过 +setAnimationDuration:方法设置当前事务的动画时间,或者通过 +animationDuration方法来获取时长值(默认 0.25 秒)。

Core Animation 在每个 RunLoop 周期中自动开始一次新的事务,即使你不显式地使用[CATransaction begin]开始一次事务,在一个特定 RunLoop 循环中的任何属性的变化都会被收集起来,然后做一次 0.25 秒的动画(CALayer 隐式动画)。

CATransaction 支持嵌套

Animation渲染原理

对于 App 用户交互体验提升最明显的工作莫过于使用动画了,那么 iOS 是如何处理动画的渲染过程的呢?

iOS 动画的渲染也是基于上述 Core Animation 流水线完成的。

日常开发中如果不是特别复杂的动画我们一般会使用 UIView Animation 实现,iOS 将 UIView Animation 的处理过程分为以下三个阶段:

  • Step 1:调用 animateWithDuration:animations:方法
  • Step 2:在 Animation Block 中进行LayoutDisplayPrepareCommit
  • Step 3:Render Server 根据 Animation 逐帧渲染

原理是animateWithDuration:animations:内部使用了 CATransaction 来将整个 Animation Block 中的代码作为原子操作commit 给了 RunLoop。

总结

结合上下文不难梳理出一个 iOS 最基本的完整渲染经过(Rendering pass)。

文章摘自:iOS 图像渲染原理

拓展阅读:深入理解 iOS Rendering Process

相关文章

  • Flutter 究竟是如何渲染的?

    之前,写了一篇《iOS 浅谈GPU及“App渲染流程”》阐述了iOS端App的渲染流程。其中包括三种渲染方式,分别...

  • iOS中的渲染流程解析及屏幕卡顿

    iOS中的渲染 在iOS中渲染的整体流程如下所示 App通过调用CoreGraphics、CoreAnimatio...

  • iOS面试题大全

    iOS面试题大全-点亮你iOS技能树 iOS 事件处理机制与图像渲染过程 iOS界面渲染流程分析 wechat ...

  • 渲染和iOS的离屏渲染

    iOS下的渲染流程 知识点: 整个渲染流程,可以分为OpenGL client(客户端)和OpenGL serve...

  • iOS 界面绘制渲染的流程

    参考文档和资料 Demo 流程图 参考链接 界面渲染的整体流程 界面渲染续之CALayer的显示流程 iOS7编程...

  • iOS渲染流程

    屏幕显示图像的原理 位图(Bitmap) : 是一种数据结构。一个位图是由若干个像素组成,每个像素的颜色信息由RG...

  • iOS 渲染流程

    图形渲染技术栈 如图,App 使用Core Graphics、Core Animation、Core Image ...

  • iOS渲染流程

    下图所示为图形渲染技术栈,App 使用Core Graphics、Core Animation、Core Imag...

  • iOS绘制与渲染--渲染流程

    视图渲染框架 UIKit是常用的框架,显示、动画都通过CoreAnimation。CoreAnimation是核心...

  • iOS 图形渲染流程

    iOS开发中,经常创建很多视图控件来进行界面布局,那这些视图控件是怎么显示到屏幕上的呢?其实在设置了 UIView...

网友评论

    本文标题:iOS 渲染流程

    本文链接:https://www.haomeiwen.com/subject/bkzaqktx.html