iOS 图像渲染原理

经过 图形渲染原理 一文,大体可以了解图形渲染过程当中硬件相关的原理。本文将进一步介绍 iOS 开发过程当中图形渲染原理。html

图形渲染技术栈

下图所示为 iOS App 的图形渲染技术栈,App 使用 Core GraphicsCore AnimationCore Image 等框架来绘制可视化内容,这些软件框架相互之间也有着依赖关系。这些框架都须要经过 OpenGL 来调用 GPU 进行绘制,最终将内容显示到屏幕之上。ios

iOS 渲染框架

UIKit

UIKit 是 iOS 开发者最经常使用的框架,能够经过设置 UIKit 组件的布局以及相关属性来绘制界面。git

事实上, UIKit 自身并不具有在屏幕成像的能力,其主要负责对用户操做事件的响应(UIView 继承自 UIResponder),事件响应的传递大致是通过逐层的 视图树 遍历实现的。github

Core Animation

Core Animation 源自于 Layer Kit,动画只是 Core Animation 特性的冰山一角。segmentfault

Core Animation 是一个复合引擎,其职责是 尽量快地组合屏幕上不一样的可视内容,这些可视内容可被分解成独立的图层(即 CALayer),这些图层会被存储在一个叫作图层树的体系之中。从本质上而言,CALayer 是用户所能在屏幕上看见的一切的基础。缓存

Core Graphics

Core Graphics 基于 Quartz 高级绘图引擎,主要用于运行时绘制图像。开发者可使用此框架来处理基于路径的绘图,转换,颜色管理,离屏渲染,图案,渐变和阴影,图像数据管理,图像建立和图像遮罩以及 PDF 文档建立,显示和分析。性能优化

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

Core Image

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

大部分状况下,Core Image 会在 GPU 中完成工做,但若是 GPU 忙,会使用 CPU 进行处理。app

OpenGL ES

OpenGL ES(OpenGL for Embedded Systems,简称 GLES),是 OpenGL 的子集。在前面的 图形渲染原理综述 一文中提到过 OpenGL 是一套第三方标准,函数的内部实现由对应的 GPU 厂商开发实现。

Metal

Metal 相似于 OpenGL ES,也是一套第三方标准,具体实现由苹果实现。大多数开发者都没有直接使用过 Metal,但其实全部开发者都在间接地使用 MetalCore AnimationCore ImageSceneKitSpriteKit 等等渲染框架都是构建于 Metal 之上的。

当在真机上调试 OpenGL 程序时,控制台会打印出启用 Metal 的日志。根据这一点能够猜想,Apple 已经实现了一套机制将 OpenGL 命令无缝桥接到 Metal 上,由 Metal 担任真正于硬件交互的工做。

UIView 与 CALayer 的关系

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

因为这种一一对应的关系,视图层级拥有 视图树 的树形结构,对应 CALayer 层级也拥有 图层树 的树形结构。

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

那么为何 iOS 要基于 UIView 和 CALayer 提供两个平行的层级关系呢?

其缘由在于要作 职责分离,这样也能避免不少重复代码。在 iOS 和 Mac OS X 两个平台上,事件和用户交互有不少地方的不一样,基于多点触控的用户界面和基于鼠标键盘的交互有着本质的区别,这就是为何 iOS 有 UIKitUIView,对应 Mac OS X 有 AppKitNSView 的缘由。它们在功能上很类似,可是在实现上有着显著的区别。

实际上,这里并非两个层级关系,而是四个。每个都扮演着不一样的角色。除了 视图树图层树,还有 呈现树渲染树

CALayer

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

图形渲染原理 中提到纹理本质上就是一张图片,所以 CALayer 也包含一个 contents 属性指向一块缓存区,称为 backing store,能够存放位图(Bitmap)。iOS 中将该缓存区保存的图片称为 寄宿图

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

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

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

Contents Image

Contents Image 是指经过 CALayercontents 属性来配置图片。然而,contents 属性的类型为 id。在这种状况下,能够给 contents 属性赋予任何值,app 仍能够编译经过。可是在实践中,若是 content 的值不是 CGImage ,获得的图层将是空白的。

既然如此,为何要将 contents 的属性类型定义为 id 而非 CGImage。这是由于在 Mac OS 系统中,该属性对 CGImageNSImage 类型的值都起做用,而在 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 会建立一个空的寄宿图(尺寸由 boundscontentScale 决定)和一个 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 流水线的详细过程以下:

  • 首先,由 app 处理事件(Handle Events),如:用户的点击操做,在此过程当中 app 可能须要更新 视图树,相应地,图层树 也会被更新。
  • 其次,app 经过 CPU 完成对显示内容的计算,如:视图的建立、布局计算、图片解码、文本绘制等。在完成对显示内容的计算以后,app 对图层进行打包,并在下一次 RunLoop 时将其发送至 Render Server,即完成了一次 Commit Transaction 操做。
  • Render Server 主要执行 Open GL、Core Graphics 相关程序,并调用 GPU
  • GPU 则在物理层上完成了对图像的渲染。
  • 最终,GPU 经过 Frame Buffer、视频控制器等相关部件,将图像显示在屏幕上。

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

Commit Transaction

在 Core Animation 流水线中,app 调用 Render Server 前的最后一步 Commit Transaction 其实能够细分为 4 个步骤:

  • Layout
  • Display
  • Prepare
  • Commit

Layout

Layout 阶段主要进行视图构建,包括:LayoutSubviews 方法的重载,addSubview: 方法填充子视图等。

Display

Display 阶段主要进行视图绘制,这里仅仅是设置最要成像的图元数据。重载视图的 drawRect: 方法能够自定义 UIView 的显示,其原理是在 drawRect: 方法内部绘制寄宿图,该过程使用 CPU 和内存。

Prepare

Prepare 阶段属于附加步骤,通常处理图像的解码和转换等操做。

Commit

Commit 阶段主要将图层进行打包,并将它们发送至 Render Server。该过程会递归执行,由于图层和视图都是以树形结构存在。

动画渲染原理

iOS 动画的渲染也是基于上述 Core Animation 流水线完成的。这里咱们重点关注 app 与 Render Server 的执行流程。

平常开发中,若是不是特别复杂的动画,通常使用 UIView Animation 实现,iOS 将其处理过程分为以下三部阶段:

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

参考

  1. Getting Pixels onto the Screen中文版(iOS 开发:绘制像素到屏幕)
  2. 深刻理解 iOS Rendering Process
  3. iOS Core Animation: Advanced Techniques中文译本
  4. 关于drawRect
  5. iOS 绘图与动画原理剖析
  6. WWDC 2014 Session 419: Advanced Graphics and Animations for iOS Apps

扩展阅读

  1. 离屏渲染优化详解:实例示范+性能测试
  2. Mastering Offscreen Render
  3. Optimizing 2D Graphics and Animation Performance
  4. Polishing Your Interface Rotation Animations
  5. Core Animation Essentials
  6. Understanding UIKit Rendering
  7. iOS: Rendering the UI
  8. iOS 事件处理机制与图像渲染过程
  9. iOS 动画篇:核心动画
  10. GPU Framebuffer Memory: Understanding Tiling
  11. iOS 保持界面流畅的技巧
  12. OpenGL ES 框架详细解析(八) —— OpenGL ES 设计指南
  13. iOS 开发-视图渲染与性能优化
  14. iOS 视图、动画渲染机制探究
  15. iOS 事件处理机制与图像渲染过程
  16. iOS界面渲染流程
  17. 界面渲染的总体流程
  18. iOS图像处理之Core Graphics和OpenGL ES初见
  19. WWDC 2012 Session 506: Optimizing 2D Graphics and Animations Performances
  20. WWDC 2011 Session 421: Core Animation Essentials
  21. WWDC 2011 Session 129: Practical Drawing for iOS Developers
相关文章
相关标签/搜索