iOS页面渲染原理简介

显示器怎么显示图像的?

这里以最原始的CRT显示器为例讲解

  1. 当渲染一帧画面时,电子枪从左到右自上而下的从帧缓冲器中取得一个个像素点进行扫描并显示
  2. 每当开始扫描新的一行时就会发出一个水平同步信号 HSync
  3. 当一帧画面所有行扫描完成后,视频控制器的指针会重新回到显示器的左上角开始一个全新画面的绘制。此时会向系统发出一个垂直同步信号 VSync
  4. 画面的显示过程就是基于VSync 信号进行工作的,每个 VSync 时间点到来时视频控制器就会去帧缓冲器读取新的一帧(切换前后帧)

整体流程是怎样的?

  1. 整个流程是在CPU和GPU共同作用下完成的,其中

    1. CPU主要负责对象创建、布局计算、图像解码等工作,然后将产生的图形数据通过总线BUS提交到GPU
    2. GPU根据CPU提交的数据进行图元装配、着色、渲染等得到位图数据并提交至帧缓冲器中
    3. 视频控制器根据垂直同步信号VSync逐帧读取帧缓冲器的数据并提交至屏幕控制器
    4. 屏幕控制器将像素数据经过数模转换后显示在屏幕上
  2. CPU和GPU特点

    1. CPU设计的比较通用,包括逻辑判断、分支跳转、中断等,侧重于执行低延时、高效的操作
    2. GPU是专门用于处理浮点数的计算,每一部分缓存都连接着一个流处理器(stream processor),非常适合大规模的并行计算

卡顿怎么产生的?

什么是卡顿?

App在使用过程中出现一段时间的阻塞,界面毫无反应,等待不确定时间后才响应触摸或其他事件的现象

怎么产生?

从上面我们知道,每一帧的显示和 VSync 有非常大的关系,而 VSync 基本上都是由硬件决定的,我们几乎无法修改(当然后面也出了动态帧率),可以认为两帧之间的时间是固定的

以60Hz为例来说明, iOS 设备每 16.7 毫秒就需要读取一次帧缓存,在这16.7毫秒内需要完成 CPU 与 GPU 共同的工作

  1. VSync 通知到CPU时,系统图形服务会通过 CADisplayLink/IPC 等机制通知 App,App会根据当前状态进行一些处理(比如视图创建、布局计算、图片解码、文本绘制等),如果 CoreAnimation 有未提交的内容还会执行提交操作
  2. 通过 Context 将数据写入 backing store,然后通过 RenderServer 将工作交给 GPU进行渲染
  3. GPU 将数据转移到 VRAM 中进行分析,然后对多个 view 进行拼接 (compositing),纹理渲染 (texture)等,最终把渲染结果放入到帧缓冲区,等待下一次VSync到来时显示到屏幕上
  4. 如果在此期间CPU或GPU的工作过多导致不能在 16.67ms 内完成一次完整的操作,那么这一帧就会被丢弃,显示器显示的是上一帧的结果,在视觉上看起来就会有卡顿

屏幕撕裂?

单帧缓存问题

如果只有一个帧缓存,GPU渲染完成后,需要等待视频控制器读取后才能渲染下一帧,视频控制器读取帧缓存也需要时间,最终导致GPU做事情的可用时间减少,很容易造成丢帧

双帧缓存

双帧缓存 (前帧缓存后帧缓存) 机制

  1. 视频控制器只会对 前帧缓存 进行读取
  2. GPU 渲染完第一帧后将数据放入前帧缓存,视频控制器开始读取 前帧缓存
  3. 与此同时 GPU 开始渲染下一帧,渲染好后将数据存入后帧缓存,然后立刻将 前后帧缓存 进行调换
  4. 此时如果 GPU 处理速度较快或者视频控制器读取较慢,以至于第一帧还没有读取完的时候就被调换 帧缓存 的话,那么就会造成同一画面上下两个部分是由 前帧缓存 和 后帧缓存 共同组成的。这时画面就会出现撕裂效果

使用VSync解决撕裂问题

垂直同步技术开启后,GPU 只会在收到 VSync 信号后才会进行操作

图形渲染技术栈

iOS中的Core Graphics、CoreAnimation、CoreImage等框架最终都是通过OpenGL、Metal来调用GPU进行绘制并将内容展示到屏幕上

UIKit

  1. UIKit是开发中使用最多的UI框架,它对底层的渲染框架做了高层级的封装
  2. UIKit中的UIView自身不具备在屏幕成像的能力,主要负责对用户操作事件的响应(UIView 继承自 UIResponder),事件响应的传递大体是通过逐层的 遍历视图树 实现的

Core Animation

  1. 一个复合引擎,用于尽可能快地组合屏幕上不同的可视内容,这些可视内容可被分解成独立的图层(即 CALayer),存储在一个叫做图层树的体系之中
  2. 从本质上而言,CALayer 是用户所能在屏幕上看见的一切的基础。主要职责包含:渲染、构建和实现动画

Metal

苹果独有的,对标OpenGL,用于跨平台渲染2D和3D矢量图形的API,可以直接访问GPU以实现硬件加速渲染

Core Graphics

  1. 基于Quartz的高级绘图引擎,主要用于运行时绘制图像
  2. 可以用来处理基于路径的绘图,转换,颜色管理,离屏渲染,图案,渐变和阴影,图像数据管理,图像创建和图像遮罩以及 PDF 文档创建,显示和分析等

Core Image

  1. 用来处理 运行前创建的图像
  2. Core Image 框架拥有一系列现成的图像过滤器,能对已存在的图像进行高效的处理

Graphics Hardware

图形硬件,这里主要是指GPU

Core Animation Pipeline 渲染流水线

CoreAnimation的渲染流水线如图,并且有如下的特点

  1. 我们的应用程序并不负责渲染,由专门的进程RenderServer进行实际的渲染
  2. App 通过 IPC 将渲染任务及相关数据提交给 RenderServerRenderServer 处理完数据后再传递至 GPU。最后由 GPU 调用 iOS 的图像设备进行显示
  3. App 内容静止时,CoreAnimation 不需要提交内容,CPU 与 GPU 不进行任何相关图像操作!

Application阶段

Handle Events 处理事件

App内处理点击事件,这个过程可能会更新视图树与图层树(改变界面布局、界面层次等)

Commit Transaction 提交事务

App内通过CPU处理显示内容的前置计算 (如视图的创建、布局计算、图片解码、文本绘制等) ,处理完成后将图层数据打包发送给RenderServer进程

以上两个阶段是我们开发中可以进行优化的,Commit Transaction又包括以下步骤

Layout 视图布局

处理视图的构建和布局。这个阶段主要在CPU中进行,通常是CPU限制或者IO限制,可以通过懒加载、简化布局、减少图层等来减少耗时

  1. 调用重载的 layoutSubviews 方法
  2. 创建视图并通过 addSubview 方法添加子视图
  3. 计算视图布局和约束,即所有的 Layout Constraint
Display 绘制视图

通过CoreGraphics进行视图绘制,得到图元信息

  1. 根据上一阶段 Layout 的结果进行绘制得到图元信息

  2. 如果重写了 drawRect: 方法,那么会调用重载的 drawRect: 方法,在 drawRect: 方法中手动绘制得到 bitmap 数据,从而自定义视图的绘制

    1. 正常情况下 Display 阶段只会得到图元 primitives 信息,而位图 bitmap 是在 GPU 中根据图元信息绘制得到的
    2. 如果重写了 drawRect: 方法,这个方法会直接调用 CoreGraphics 的绘制方法得到 bitmap 数据,同时系统会额外申请一块内存,用于暂存绘制好的 bitmap
    3. 由于重写 drawRect: 方法,绘制过程从 GPU 转移到 CPU导致了一定的效率损失。这个过程还会额外使用 CPU 和内存,因此需要高效绘制,否则容易造成 CPU 卡顿或者内存爆炸
Prepare 准备

Core Animation 额外的工作,主要进行图像解码和转换等操作

Commit 提交

将(模型树)图层打包并通过IPC发送到 RenderServer进程。会递归操作图层树,比较耗时,所以我们的图层树要尽可能避免过深

RenderServer阶段

Decode

对已经打包的图层数据进行解码,解码后是一堆GPU可以执行的指令,等待下一个RunLoop时执行Draw Calls

Draw Calls

解码完成后,调用OpenGL或者Metal的API进行绘制,这些API会操作GPU

GPU阶段

Render

与GPU通信,执行真正的图形渲染。根据当前 iOS 设备的屏幕计算图像像素位置以及像素 alpha 通道混色计算等等。然后将最终的bitmap放入缓存帧中

Display阶段

Display

Render结束后的下一个RunLoop,显示渲染内容

渲染与Runloop

  1. iOS的显示系统由 VSync 信号驱动,而 VSync 信号由硬件时钟生成,基本不会变动
  2. iOS图形服务接收到 VSync 信号后,通过IPC通知到App内
  3. App启动后CoreAnimation注册了一个优先级为2000000(值越大优先级越低 )的Observer,用于监听主线程Runloop的BeforeWaitingExit事件
  4. 当一个触摸事件到来时,Runloop被唤醒,App中的代码会执行一些操作(视图层级、frame、透明度、动画等调整),这些操作会同步操作CALayer,然后通过CATransaction提交到一个中间状态
  5. 当上面所有操作结束后,RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知,此时CoreAnimation 注册的那个Observer就会在回调中把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,Core Animation 会通过 CADisplayLink 等机制多次触发相关流程。

为什么有UIView和CALayer两套API,它们之间有什么关系?

之所以有UIView和CALayer两套API,主要是为了做到职责分离

UIView

  1. UIView继承自UIResponder,主要负责绘制与动画、布局与子View管理、点击事件的处理,在iOS平台是UIKit框架,在MacOS平台是AppKit框架
  2. 另外还负责创建并管理图层,以确保当子视图在层级关系中 添加或被移除 时,其关联的图层在图层树中也有相同的操作,即保证视图树和图层树在结构上的一致性

UIView绘制原理

整个过程是发生在Application阶段的CommitTransaction中的Display步骤

  1. 当调用[UIView setNeedsDisplay]时,实际上会直接调用底层layer的同名方法[layer setNeedsDisplay]
  2. CoreAnimation捕获到layer-tree的变化, 提交一个CATransaction,在当前Runloop将要结束时触发Runloop的Observer回调,在回调中调用[CALayer display]进行当前视图的真正绘制流程
  3. [CALayer display]内部会先判断这个layer的delegate是否会响应displayLayer:方法,如果不响应就会进入系统绘制流程中。如果能够响应,实际上是提供了异步绘制的入口

系统绘制流程

本质是创建一个 backing storage 的流程

  1. 在CALayer内部,系统会先创建 backing store(CGContextRef)并将CGContextRef推入Graphics context stack(因此 CGContextRef是可以嵌套的)。每个layer都会有一个context,这个context指向一块缓存区被称为backing store
  2. CALayer调用[CALayer drawInContext:],将上一步创建的context作为参数传入
  3. 如果有Delegate, 则调用delegate的- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法(默认会将创建的CGContextRef传入),否则调用[UIView drawRect:]方法,此时已经在CGContextRef环境中,在drawRect中通过UIGraphicsGetCurrentContext() 获取到的就是CALayer创建的CGContextRef
  4. drawRect方法是在CPU执行的,在它执行完之后就通过context将数据(通常情况下这里的最终结果会是一个bitmap, 类型是 CGImageRef)写入backing store,然后通过RenderServer交给GPU去渲染,从而将backing store中的bitmap数据显示在屏幕上

异步绘制流程

CALayer

功能

  1. 负责渲染和动画,提供可视内容的呈现
  2. CALayer 是用户所能在屏幕上看见的一切的基础,基本等同于纹理 ,本质上是一张图片,包含一个 contents 属性指向一块缓存区,称为 backing store,可以存放渲染流水线渲染好的位图(Bitmap)。iOS 中将该缓存区保存的图片称为 寄宿图
  3. 当设备屏幕进行刷新时,会从 CALayer 中读取生成好的 bitmap,进而呈现到屏幕上,由于历史原因只接受CGImage或者NSImage

图层树

CALayer和UIView一样,有自己的树状结构,也有自己的sublayers与superLayer

iOS实际上会有三种图层树

model tree:模型树
  1. 存储各个树节点的model信息。比如常见的frame, affineTransform, backgroundColor等等
  2. 这些model数据都是在APP开发中可以设置的,对于view/layer的修改都能反应在模型树中
presentation tree:呈现树
  1. 这是一个中间层,APP无法主动操作
  2. 这个层的内容是iOS系统在Render Server中生成的
  3. CAAnimation 的中间态就是在这一层上更改属性来完成动画的分动作
render tree:渲染树
  1. 对应提交到render server上进行显示的树

View渲染完整流程

资料

一文看懂:手机屏幕背后的原理技术 picture.iczhiku.com/weixin/mess...

iOS 的图形绘制原理 www.hanleylee.com/articles/pr...

深入理解 iOS Rendering Process juejin.cn/post/684490...

一文读懂iOS图像显示原理与优化 juejin.cn/post/685041...

iOS 图像渲染原理 chuquan.me/2018/09/25/...

计算机那些事(8)------图形图像渲染原理 chuquan.me/2018/08/26/...

谈 UIKit 和 CoreAnimation 在 iOS 渲染中的角色(下) mp.weixin.qq.com/s/lCMJ7BR6e...

谈 UIKit 和 CoreAnimation 在 iOS 渲染中的角色(上) mp.weixin.qq.com/s/PgdH8x8nr...

关于iOS离屏渲染的深入研究 zhuanlan.zhihu.com/p/72653360

iOS Rendering 渲染全解析(长文干货) juejin.cn/post/684490...

iOS 保持界面流畅的技巧 blog.ibireme.com/2015/11/12/...

iOS 界面渲染与优化(一) - CPU与GPU干了啥事儿 juejin.cn/post/698250...

iOS界面渲染与优化(二) - UIView与渲染 juejin.cn/post/698250...

iOS界面渲染与优化(三) - 图像在渲染中的优化 juejin.cn/post/698250...

iOS界面渲染与优化(四) - 离屏渲染与优化总结 juejin.cn/post/698290...

iOS视图渲染解析

相关推荐
openinstall4 小时前
A/B测试如何借力openinstall实现用户价值深挖?
android·ios·html
二流小码农4 小时前
鸿蒙开发:资讯项目实战之项目初始化搭建
android·ios·harmonyos
Hierifer9 小时前
跨端实现 DSBridge 源码解析
前端·ios
CocoaKier10 小时前
你的开发者账号已经被盯上了,如何区分钓鱼邮件
ios·google·apple
程序员小刘12 小时前
鸿蒙跨平台开发:打通安卓、iOS生态
android·ios·harmonyos
大熊猫侯佩13 小时前
SwiftUI 5.0(iOS 17.0,macOS 14.0+)新 Inspector 辅助视图之趣味漫谈
macos·ios·swiftui
二流小码农1 天前
鸿蒙开发:资讯项目实战之项目框架设计
android·ios·harmonyos
hepherd1 天前
Flutter - 原生交互 - 相机Camera - 曝光,缩放,录制视频
flutter·ios·dart
Android研究员1 天前
HarmonyOS实战:List拖拽位置交换的多种实现方式
android·ios·harmonyos
YungFan1 天前
Xcode26新特性与iOS26适配指南
ios·xcode