6、OpenGL - iOS渲染流程

iOS渲染流程

 

目录

  • 1、基础流程介绍
  • 2、GPU图形渲染流水线
  • 3、iOS 下的渲染框架
  • 4、CoreAnimation 渲染

 

面试的过程中经常会遇到类似这样的面试题:

  • APP 从点击屏幕 到完全渲染,中间发生了什么?
  • 一个UIImageView 添加到视图上以后,内部是如何渲染到手机上的。流程是是什么?
  • 如果接解决tableView 有多个cell 的卡顿问题?

iOS的渲染视图的核心是Core Animation
其渲染层次依次为:图层树 -> 呈现树 -> 渲染树

 

1、基础流程介绍

CPU:

系统的运算和控制单元,内部流水线结构使其拥有一定程度的并行计算能力

GPU:

进行绘图运算工作的专用微处理器。GPU能够生产2D/3D 的图形图像和视频,从而能够支持基于窗口的操作系统、图形用户界面、视频游戏、可视化图像引用和视频播放。GPU 具有非常强的并行计算能力。

CPU 具有图形渲染能力,但是由于CPU的主要功能是控制计算单元有限,从性能和速度上看一般不会使用CPU进行图形渲染。
计算机将存储在内存中的形状换成实际绘制在屏幕上的对应的过程称为渲染。渲染过程中最常用的技术就是光栅化。
光栅化就是将数据转化成可见像素的过程,GPU是执行转换过程的硬件部件。由于这个过程设计到屏幕上的每个像素,所以GPU需要被设计成一个高度并行化的硬件部件。
GPU机器相关驱动实现了图形处理中的OpenGL 和 DirectX 模型,从而允许开发者能够轻易地操作硬件。OpenGL 严格来说并不是常规意义上API,而是一个第三方标准,其定义了每个函数应当如何执行,以及他们的输出值。DirectX 是由Microsoft 提供的一套第三方标准。

GPU由纹理(位图)合成为一帧,每一个纹理都会占用一部分显存空间,因此GPU一次处理的纹理有大小限制(最大是图像宽*高*4(RGBA)*2.5)。GPU垂直同步信号的间隔约为16.7ms。
数据上传给GPU需要从内存复制数据到显存,如果是一个大的纹理,会非常耗时。
PNG、JGP这样的压缩图片,解压缩是在CPU进行。


CPU阶段

1、布局(Frame)
2、显示(Core Graphics)
3、准备(QuartCore/Core Animation)
4、通过IPC提交(打包好的图层树以及动画属性)

OpenGL ES阶段

1、生成(Generate)
2、绑定(Bind)
3、缓存数据(Buffer Data)
4、启用(Enable)
5、设置指针(Set Pointers)
6、绘图(Draw)
7、清除(Delete)

GPU阶段

1、接收提交的纹理(Texture)和顶点描述(三角形)
2、应用变换(transform)
3、合并渲染(离屏渲染等)


2、GPU图形渲染流水线

GPU图形渲染流水线的主要工作可以被划分为两个部分
1、把3D坐标转换为2D坐标
2、把2D坐标转变成实际的有颜色的像素

GPU 图形渲染流水线的具体实现分为 6 个阶段

  1. 顶点着色器(Vertex Shader)
  2. 图元装配(Shape Assembly)
  3. 几何着色器(Geometry Shader)
  4. 光栅化(Rasterization)
  5. 片段着色器(Fragment Shader)
  6. 测试与混合(Tests and Blending)

1、顶点着色器(Vertex Shader)
输入顶点数据(Vertex Data),顶点数据是一系列顶点的集合,顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时对顶点属性进行一些基本处理

2、图元装配(Shape Assembly)
将顶点着色器输出的所有顶点作为输入,并将所有的点装配成指定图元的形状。图中则是一个三角形
图元:用于表示如何渲染顶点数据。只有:点、线、三角形 这3种。

3、几何着色器(Geometry Shader)
通过产生新顶点构造出新的图元来生成其他形状

4、光栅化(Rasterization)
把图元映射成屏幕上相应的像素,生成片段。片段是渲染一个像素所需要的所有数据。

5、片段着色器(Fragment Shader)
首先对输入的片段进行裁剪(Glipping)。裁切会丢弃超出视图以外的所有像素,用来提升效率。

6、测试与混合(Tests and Blending)
检测片段的对应的深度值(z坐标),判断这个像素位于其他物体的前面还是后面,决定是否直接丢弃。检测 alpha(透明度)值,来进行对应物体混合。所以在片段着色器中计算出来的一个像素输出的颜色,在渲染多个三角形的时候最后像素颜色有可能不一样。

显示原理:

CPU 计算好显示的内容提交至GPU,GPU渲染完成后将渲染结果存入帧缓存区,视频控制器会按照VSync 信号逐帧读取帧缓冲区的数据,经过数据转换后最终由显示器进行显示。

iOS一般使用的是双缓冲机制,也就是有两个帧缓冲区。GPU会预先渲染一帧放入一个帧缓冲区,用于视图控制器的读取。当下一帧渲染完毕后,GPU会直接把视图控制器的指针指向第二个缓冲区。


iOS 的渲染核心原理主要是:前后帧缓存、VSync(垂直同步)信号、CADisplayLink(显示)

 

3、iOS 下的渲染框架

Core Animation
苹果官网 - Core Animation
Core Animation 以前依赖的是OpenGL ES做GPU渲染,现在大部分都是基于Metal 的

如上图。显示器上面的就是GPU,图像处理单元。
CoreGraphics、CoreAnimation、CoreImage这些是基于OpenGL ES 或则Metal 去驱动GPU进行工作。这里使用双箭头,是因为OpenGL ES / Metal 要实现跨平台,是没有窗口的,需要借助GoreAnimation 等提供窗口、载体

Core Animation,它本质上可以理解为一个复合引擎,主要职责包含:渲染,构建和实现动画。

Core Animation 在RunLoop中注册了一个Observer(观察者),监听BeforeWaiting 和 Exit 事件。这个Observer 的优先级是2,000,000,低于常见的其他 Observer。当一个触发事件到来时,RunLoop 被唤醒,App 中的代码会执行例如创建 和 调整 视图层级、设置UIView 的frame、修改CALayer 的透明度、为视图添加一个动画;这些操作最终都会被CALayer 捕获,并通过CATransaction(事务)提交到一个中间状态去(CATransaction 的文档略有提到这些内容,但并不完整)。当上面所有操作结束后,RunLoop 即将进入休眠(或退出)时,关注该事件的Observer 都会得到通知。这时Core Animation 注册的那个Observer 就会在回调中,把所有的中间状态合并提交到GPU去处理。如果当前有动画,Core Animation 会通过DisplayLink 等机制多次触发相关流程。

 

CPU 渲染

参阅:视图渲染与性能优化

Core Animation 提交会话,包括自己和子树(view hierarchy)的layout 状态等;
RenderServer 解析提交的子树状态,生成绘制指令
GPU 执行绘制指令
显示渲染后的数据

布局(Layer)

调用layoutSubviews 方法
调用addSubview: 方法

显示(Display)

通过drawRect 绘制视图
绘制string (字符串);

准备提交(prepare)

解码图片;
图片格式转换

提交(commit)

打包layers 并发送到渲染server
递归提交子树的layers
如果子树太复杂,会消耗很大,对性能造成影响;

 

CPU渲染职能主要体现在一下5个方面

1、布局计算

吐过你的视图层级过于复杂,当视图呈现或者修改的时候,计算图层帧率就会消耗一部分时间。

2、视图懒加载

iOS只会当视图控制器在视图显示到屏幕上时才会加载它

3、Core Graphics绘制

如果对视图实现了drawRect: 或者 drawLayer:inContext: 方法,或者CALayerDelegate 的方法,那么在绘制任何东西之前都会产生一个巨大的性能开销。为了支持对图层内容的任意绘制,Core Animation必须在内存中创建一个中等大小的寄宿图片。然后一旦绘制解决之后,必须把图片数据通过IPC 传到渲染服务器。

4、图片解压

对于png、jpg这种压缩图片。在绘制到屏幕上的时候,需要先进行解压缩,解压成完整图片尺寸的位图(图片长 * 图片宽 * 4(RGBA 红黄蓝透明度)字节)

图层打包

当图层被成功打包,发送到渲染 服务器 之后,CPU仍然要做如下工作:为了显示 屏幕上的图层,Core Animation必须对渲染树中的每个可见图层通过OpenGL循环 转换成纹理三角形。由于GPU并不知晓Core Animation图层的任何结构,所以必须 要由CPU做这些事情。这里CPU涉及的工作和图层个数成正比,所以如果在你的层 级关系中有太多的图层,就会导致CPU没一帧的渲染,即使这些事情不是你的应用 程序可控的。

 

View 和 Layer 的关系

View :(基于CALayer进行封装的)

绘制和动画
布局和子view 的管理
点击事件处理

Layer

只负责显示(渲染 / 动画),显示的是位图。位图会进行静态存储

为什么layer 不能做布局等。因为layer 支持手机 也支持Mac 可以在Appkit 和 UIKit 里面进行渲染,(布局不一样)所以不能做任何布局

Core Animation,它本质上可以理解为一个复合引擎,主要职责包含:渲染,构建和实现动画。

(1)、职责分离
(2)、两个系统交换规则是不一样

 

4、CoreAnimation 渲染流水线

4.1、首先会来到Core Animation

    HandleEvents 事件处理
    Commit Transaction:图片 ->解码(CPU)
    可以细分为四个步骤:

  1. Layout:构建视图布局如 addSubview等操作
  2. Dispatch:重载drawRect:进行视图绘制,该步骤使用CPU与内存
  3. Prepare:主要处理图像的解码与格式转换等操作
  4. Commit: 将Layer 递归打包并发送到Render Server

提交会话包括自己和子树(View hierarchy)的layout状态等;

 

4.2、Render Server:下一个RunLoop进行硬解码。

(Render Server字面意思渲染服务器,这里的服务器并不是我们网络请求的,客户端和服务器,这里)解析提交的子树状态,生成绘制指令。
负责渲染工作,会解析上一个不Commit Transaction 中提交的信息并反序列化成渲染树(render tree),随后根据layer 的各种属性生成绘制指令,并在下一次VSync 信号到来时调用OpenGL 进行渲染。

 

4.3、GPU执行绘制指令

GPU会等待显示器的VSync 信号发出后才进行OpenGL 渲染管线,将3D几何数据转化成2D 的像素图像和光栅处理,随后进行新的一帧的渲染,并将其输出到缓冲区。

 

4.4、Dispatch

显示渲染后的数据
从缓冲区中取出画面,并输出到屏幕上。

 

参阅: https://github.com/imqiuhang/CoreAnimationLearning


Apple官方指导的一句话"当你编写iOS应用的时候,不管你知不知道Core Animation这个东西,你都在使用它",也就是说可能我们赋值backgroundColor的时候“一不小心”的触发了某个“隐式的”特性,因此我们还是决定从官方文档入手,找到这个“隐身”的东西。
Layer Modifications Trigger Animations, Most of the animations you create using Core Animation involve the modification of the layer’s properties. Like views, layer objects have a bounds rectangle, a position onscreen, an opacity, a transform, and many other visually-oriented properties that can be modified. For most of these properties, changing the property’s value results in the creation of an implicit animation whereby the layer animates from the old value to the new value. You can also explicitly animate these properties in cases where you want more control over the resulting animation behavior.
最终我们在文档中找到这么一段话,翻译了一下
图层修改触发动画,您使用Core Animation创建的大多数动画都涉及修改图层的属性。与视图一样,图层对象具有frame,屏幕上的位置,不透明度,变换以及可以修改的许多其他视觉属性。对于大多数这些属性,更改属性的值会导致创建隐式动画,从而将图层从旧值设置为新值。如果希望更多地控制生成的动画行为,也可以显式设置这些属性的动画。
首先我们抓住几个重点
    •    图层修改触发动画 Layer Modifications Trigger Animations
    •    图层属性修改触发隐式动画 changing the property’s value results in the creation of an implicit animation
    •    可以显式设置这些属性的动画 explicitly animate
因此我们大致知道了我们在修改图层属性的时候(CALayer属于Core Animation框架下,UIView属于UIKit,这个点我们在稍后会有讨论,所以先忽略为什么view不会触发隐式动画)会触发一个隐式动画,也就是我们看到的左边的layer偷偷给自己加的过渡动画。
那么何为隐式动画?隐式动画是如何自动的加入到一个属性的改变过程里的?
这个在官方的指导中没有非常详细的介绍,但是我们通过查阅如何显式的提交动画中也能发现触发隐式动画的关键是Transactions,也就是在CATransaction这个类中,首先查阅这个类,并没有属性可以供我们操作,也只有几个静态方法给我们调用,因此我们先看下文档对于这个类的解释。

CATransaction
Transactions are CoreAnimation's mechanism for batching multiple layer- tree operations into atomic updates to the render tree. Every modification to the layer tree requires a transaction to be part of. CoreAnimation supports two kinds of transactions, "explicit" transactions and "implicit" transactions. Explicit transactions are where the programmer calls [CATransaction begin] before modifying the layer tree, and [CATransaction commit] afterwards. Implicit transactions are created automatically by CoreAnimation when the layer tree is modified by a thread without an active transaction. They are committed automatically when the thread's run-loop next iterates. In some circumstances (i.e. no run-loop, or the run-loop is blocked) it may be necessary to use explicit transactions to get timely render tree updates.

事务
事务是CoreAnimation 将layer tree多个修改操作批量提交给渲染树的机制。 对layer tree的修改都需要事务作为其一部分。
CoreAnimation支持两种事务,显式事务和隐式事务。 显式事务是程序员在修改层树之前调用[CATransaction begin],然后是[CATransaction commit]。 当layer tree 修改时,如果没有有效的事务,CoreAnimation会自动创建隐式事务。
它们在线程的runloop下一次迭代时自动提交。 在一些情况下(没有runloop,或者runloop被阻塞),可能有必要使用显式事务来及时地呈现树更新。 管理了一堆不能访问的事务。CATransaction没有属性或者实例方法,也不能用+alloc和-init方法创建它。只能用+begin和+commit入栈出栈一次事务的提交。
划重点
    •    事务是layer属性修改批量提交给渲染树(后面会提到)的机制
    •    CoreAnimation支持 显式事务 和 隐式事务。
    •    layer每次修改都必须有对应事务
    •    它们在线程的runloop下一次迭代时自动提交(可以看下系统在APP启动时候在runloop中注册的各类回调)
    •    不能init一个事务的实例,只能乖乖调用静态方法

 

参阅

1、iOS开发-视图渲染与性能优化

2、计算机那些事(8)——图形图像渲染原理

3、iOS动画篇:核心动画

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!如果您想在iOS上使用OpenGL来处理AVFrame视频,可以按照以下步骤进行操作: 1. 导入相关库和头文件:在项目中导入OpenGL ES和AVFoundation的框架,并且导入AVFoundation和CoreVideo的头文件。 2. 创建OpenGL上下文:在您的视图控制器中创建一个用于渲染OpenGL上下文。 3. 创建纹理:使用OpenGL的纹理对象来存储视频帧的像素数据。您可以使用glGenTextures()函数创建一个纹理对象,并使用glBindTexture()函数绑定到当前上下文。 4. 将AVFrame数据上传到纹理:使用glTexImage2D()函数将AVFrame的像素数据上传到纹理对象中。您可以使用CVPixelBufferLockBaseAddress()函数锁定CVPixelBuffer的基地址,然后使用glTexSubImage2D()函数将图像数据复制到纹理中。 5. 渲染纹理:使用OpenGL的顶点和片段着色器来渲染纹理。您可以创建一个包含顶点坐标和纹理坐标的顶点缓冲区,并使用glDrawArrays()函数进行渲染。 6. 清理资源:在不需要时,记得释放纹理对象和其他相关资源,以避免内存泄漏。 请注意,以上只是一个简单的示例,实际的实现可能会更加复杂。您可能需要处理视频帧的格式转换、缩放、旋转等操作,以及处理音频等其他方面的内容。同时,如果您希望实现更高级的视频处理功能,您还可以考虑使用其他库或框架,如FFmpeg等。 希望对您有帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值