iOS 界面渲染与优化(一) - CPU与GPU干了啥事儿

2,593 阅读9分钟

iOS 界面渲染与优化(一) - CPU与GPU干了啥事儿

最近在研究界面优化, 看了很多关于iOS界面渲染相关的内容, 这里做一个简单的小结.

1. 计算机的渲染原理

可以参考OpenGL的渲染即可, 或者参考以下文章:

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

ObjC 中国 - 绘制像素到屏幕上 (objccn.io)

2. 成像原理与屏幕卡顿

因此渲染完成以后的渲染的内容在帧缓冲区(framebuffer)中, 需要视频控制将framebuffer中的内容展示到屏幕上. 在每一帧没展示到界面前需要经过如下两个过程:

  1. 在CPU中进行视图创建,删除(layer-tree的调整), layout frame计算, 图像解码, 文本绘制等内容, 本质是操作图层树, 将运行结果存储在CommandBuffer
  2. 将CPU处理结果CommandBuffer提交给GPU, GPU进行渲染

如下图所示, CPU或者GPU任何一个耗时较长就有可能导致丢帧从而导致卡顿.

1623e39ade981eb6.jpg

具体渲染的原理可以参考

iOS 事件处理机制与图像渲染过程 (qq.com),

iOS 保持界面流畅的技巧 | Garan no dou (ibireme.com)

3. Runloop中触发渲染的过程

ios_vsync_runloop

以下是在参考文章中的摘抄:

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

在iOS中是通过CoreAnimation, 框架完成Runloop中的通知监听和后续处理的. CoreAnimation名称很容易让人误导, 在MAC上被称为LayerKit.

CoreAnimation框架会在RunLoop中注册一个 Observer,监听了 BeforeWaiting Exit 事件。这个Observer 的优先级是 2000000,低于常见的其他 Observer。当一个触摸事件到来时,RunLoop 被唤醒,App 中的代码会执行一些操作, 可以称为handleEvent, 常见的操作就是我们开发中实现的那些, 例如:

  1. 创建和调整视图层级, 例如 addSubView, removeSubView等
  2. 设置 UIView 的 frame, 调制autolayout约束等
  3. 修改 CALayer 的透明度
  4. 为视图添加一个动画
  5. 其他可能导致 CALayer - Tree 变化的操作

上面的操作实际是会有一个隐士的CATransaction生成:

隐士[CATransaction begin]; 
CALayer 图层变化
隐士[CATransaction commit];

上面这些操作最终都会被 CALayer 捕获,并通过 CATransaction保存到一个中间状态, 在RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知(这个Observer的优先级非常低, 会在最后执行)。这时 CoreAnimation 注册的那个 Observer 就会在回调中,把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,CA 会通过 DisplayLink 等机制多次触发相关流程。

隐式动画是系统框架自动完成的。

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

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

上面的过程在CPU的调用栈, 可能如下:

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
    QuartzCore:CA::Transaction::observer_callback:
        CA::Transaction::commit();
            CA::Context::commit_transaction();
                CA::Layer::layout_and_display_if_needed();
                    CA::Layer::layout_if_needed();
                          [CALayer layoutSublayers];
                          [UIView layoutSubviews];
                    CA::Layer::display_if_needed();
                          [CALayer display];
                          [UIView drawRect];

4. CPU在界面渲染前干了啥

内容可以参考[WWDC 2014 -Advanced Graphics and Animations for iOS Apps]

这个session已经被apple删了. b站上有人发了这个视频

用另外一个图展示从CPU -> GPU的总流程如下:

  1. CoreAnimation提交会话,包括自己和子树(view hierarchy)的layout状态等;(这一步是在APP中完成的, 通过IPC框架将CA信息提交给CA)

  2. RenderServer解析提交的子树状态, 生成绘制指令; (这一步是在Render Server中, 非APP内部)

  3. GPU执行绘制指令

  4. 显示渲染后的数据

image1.png

其中CPU中会经历如下步骤, CA在Commit Transaction时候实际经过了如下4个步骤:

  1. Layout - 构建视图

    • 调用layoutSubviews方法

    • 调用addSubview:方法
    • 文本计算(size)等
    • AutoLayout根据 Layout Constraint 计算各个view的frame
  2. Display - 绘制视图

    • 在这个阶段程序会创建 layer 的 backing image,无论是通过 setContents 将一个 image 传給 layer,还是通过 drawRect:drawLayer: inContext: 来画出来的。

      所以 drawRect: 等函数是在这个阶段被调用的。

      注意不要混淆这里的Display和最终的display, 并且这个过程在CPU中完成

  3. Prepare - 提交前准备

    • 图像拷贝和解码(image copy + image decode) -- 这里后面会详细讲
    • 尽量使用GPU支持的格式, Apple推荐JPG和PNG
  4. Commit - 打包layers 通过IPC提交给Render-Server(另外一个进程)

    • 打包layers并发送到render-server
    • 递归提交子树的layers (如果view层级过多, 会能看到调用栈中的大量的方法: CA::Layer::commit_if_needed)

image2.png

在CPU完成需要准备的layers并提交给Render-Server以后, 真正的内容就交给GPU来完成了!

这里有一个附加内容, 关于动画 Animation:

Apple的做法很简单直接, 对于每一个Animation, 前期的准备阶段和单独的图像提交基本相同(Layout/Display/Prepare/Commit),但是Render Server在渲染时, 将根据Core Animation的动画参数自动计算出动画所需要的每一帧图像, 然后一帧一帧的渲染显示, 最终呈现的就是动画效果。也就是说App只需要告诉Render Server动画的起始和终止状态, 它自动将中间过程计算出来并自动渲染这个动画。

小tips:

因此从这里能看出针对CPU中处理的内容优化点如下(后面专门开小结归纳常见的优化方法):

  1. handleEvent过程中的操作尽可能少, 不做耗时操作
  2. 布局计算, 文本计算的内容竟可能少, 并且autolayout比frame更加消耗性能
  3. 图像需要内存对齐, 并且图像解码过程提前在子线程完成!!并且UIImageView.size与图片size保持一致!

小tips2:

关于Animation的动画

我们会在后面介绍三种类型的layer-tree的概念, 它们会帮助我们更加深刻的理解动画

简单来说就是: 你只需要操作起始和终止状态的modal Layer属性, Render Server会在presentation Layer中即完成所有中间过程的计算

5. GPU的渲染过程

目前移动设备都是用的Tiled-Based 渲染, 这里列举了常规的渲染流程和一个离屏渲染的流程:

gpu3.png

5.1 普通的Tile-Based渲染流程如下(正常渲染):
  1. CommandBuffer,接受OpenGL ES处理完毕的渲染指令;
  2. Tiler,调用顶点着色器,把顶点数据进行分块(Tiling);
  3. ParameterBuffer,接受分块完毕的tile和对应的渲染参数;
  4. Renderer,调用片元着色器,进行像素渲染;
  5. RenderBuffer,存储渲染完毕的像素;

gpu1.png

5.2 离屏渲染 -- MASK

使用Mask遮罩时, pass1过程执行完成以后, 也需要保存pass1结果, 保存pass2结果, 然后合并成pass3 结果. (普通方式只会有一个storage结果)

  1. 渲染layer的mask纹理,同Tile-Based的基本渲染逻辑;
  2. 渲染layer的content纹理,同Tile-Based的基本渲染逻辑;
  3. Compositing操作,合并1、2的纹理;

gpu2.png

其他的效果, 比如UIVisiualEffectView 效果的性能消耗更加巨大, 被称为昂贵特效!!! 具体离屏渲染过程可以参考: WWDC 2014 -Advanced Graphics and Animations for iOS Apps

6. 性能优化问答

1、帧率一般在多少? 2、是否存在CPU和GPU瓶颈? (查看占有率) 3、额外的使用CPU来进行渲染? 4、是否存在过多离屏渲染? 5、是否渲染过多视图? 6、使用奇怪的图片格式和大小? 7、使用昂贵的特效? 8、视图树上不必要的元素?

参考文章:

WWDC2011 121: understanding uikit rendering

WWDC2012 211: building concurrent user interfaces on ios

WWDC2012 235: iOS App Performance: Responsiveness

WWDC2012 242: iOS App Performance: Memory

WWDC 2012: iOS App Performance: Graphics and Animations

WWDC 2014 -Advanced Graphics and Animations for iOS Apps

WWDC2018 Image and Graphics Best Practices

WWDC2018 iOS Memory Deep Dive

iOS 视图---动画渲染机制探究 - CocoaChina_一站式开发者成长社区

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

bang神强文-iOS图片加载速度极限优化—FastImageCache解析

绘制像素到屏幕上(Getting Pixels onto the Screen译文)

离屏渲染(Offscreen Render)

为iOS设计:图形和性能

iOS性能优化系列篇之“列表流畅度优化”

iOS图像显示原理和卡顿优化

iOS 性能优化总结

ios-rounded-corner

落影 - iOS性能优化——图片加载和处理 (iOS性能优化——图片加载和处理 - 云+社区 - 腾讯云 (tencent.com))

iOS离屏渲染优化(附DEMO)

[[转]iOS 事件处理机制与图像渲染过程](www.cnblogs.com/linganxiong…)

iOS 2D Graphic(1)—— Concept 基本概念和原理

iOS中的图片使用方式、内存对比和最佳实践(juejin.cn/post/684490…)

iOS界面渲染流程分析 - 云+社区 - 腾讯云 (tencent.com)

iOS高效图片 IO 框架是如何炼成的iOS开发-CSDN博客

iOS图片内存管理和性能优化 - 简书 (jianshu.com)

iOS的5种图片缩略技术以及性能探讨 - 简书 (jianshu.com)

JHBlog/加载大图的优化算法.md at master · SunshineBrother/JHBlog (github.com)

探讨iOS 中图片的解压缩到渲染过程 - 简书 (jianshu.com)

iOS 图形性能优化 (juejin.cn)