[TOC]
TOC前言
整个渲染流程比较多,这篇文章中只记录流程中的步骤,对于每个步骤中的细节不做过多说明,如果想要了解,可以参考笔者的其他文章。
一、整体流程
1.1 计算机图形渲染简单流程
计算机图形渲染简单流程- Application 应用处理阶段:得到图元
- Geometry 几何处理阶段:处理图元,得到新图元
- Rasterization 光栅化阶段:图元转换为片元(像素)
- Pixel 像素处理阶段:处理片元(像素),得到位图
1.2 计算机层次流程图
image-
APP
:图层树,UIView/CALayer -
Core Graphics
:Core Graphics 是一个强大的二维图像绘制引擎,是 iOS 的核心图形库,常用的比如 CGRect 就定义在这个框架下 -
Core Animation
:在 iOS 上,几乎所有的东西都是通过 Core Animation 绘制出来,它的自由度更高,使用范围也更广 -
Core Image
:Core Image 是一个高性能的图像处理分析的框架,它拥有一系列现成的图像滤镜,能对已存在的图像进行高效的处理 -
Metal
:Metal 类似于 OpenGL ES,也是一套第三方标准,具体实现由苹果实现。Core Animation、Core Image、SceneKit、SpriteKit 等等渲染框架都是构建于 Metal 之上的。
二、CPU 阶段:Core Animation
2.1 Core Animation 层级图
Core Animation 层级图- Core Animation,它本质上可以理解为一个复合引擎,主要职责包含:渲染、构建和实现动画。
2.2 CALayer 的层级图
CALayer 的层级- backgroundColor
- contents
- borderWidth / borderColor
【总结】
- 设置
layer.cornerRadius
只会设置 backgroundColor 和 border 的圆角,不会设置 contents - 同时设置
layer.masksToBounds
才会设置 contents 的圆角。(对应view中的clipsToBounds属性)
2.3 UIView 和 CALayer 之间的关系
image【核心关系】
- CALayer 是 UIView 的属性之一,负责渲染和动画,提供可视内容的呈现。
- UIView 提供了对 CALayer 部分功能的封装,同时也另外负责了交互事件的处理。
【区别】
- 层级结构:我们对 UIView 的层级结构非常熟悉,由于每个 UIView 都对应 CALayer 负责页面的绘制,所以 CALayer 也具有相应的层级结构。
- 部分效果的设置:因为 UIView 只对 CALayer 的部分功能进行了封装,而另一部分如圆角、阴影、边框等特效都需要通过调用 layer 属性来设置。
- 点击事件:CALayer 不负责点击事件,所以不响应点击事件,而 UIView 会响应。
- 继承关系:CALayer 继承自 NSObject,UIView 由于要负责交互事件,所以继承自 UIResponder。
2.4 Core Animation Pipeline 渲染流水线
image-
Handle Events
:这个过程中会先处理点击事件,这个过程中有可能会需要改变页面的布局和界面层次。 -
Commit Transaction
:此时 app 会通过 CPU 处理显示内容的前置计算,比如布局计算、图片解码等任务,接下来会进行详细的讲解。之后将计算好的图层进行打包发给Render Server
。 -
Decode
:打包好的图层被传输到Render Server
之后,首先会进行解码。注意完成解码之后需要等待下一个RunLoop
才会执行下一步 Draw Calls。 -
Draw Calls
:解码完成后,Core Animation
会调用下层渲染框架(比如 OpenGL 或者 Metal)的方法进行绘制,进而调用到 GPU。 -
Render
:这一阶段主要由 GPU 进行渲染。 -
Display
:显示阶段,需要等 render 结束的下一个 RunLoop 触发显示。
2.5 Core Animation 之 Commit Transaction 流程
image- Layout:构建视图
- Display:绘制视图
- Prepare:Core Animation 额外工作,图片解码和转换
- Commit:图层打包发送到 Render Server
三、OpenGL 渲染阶段
3.1 OpenGL 渲染流程:蓝宝书
1. OpenGL 渲染流程图
OpenGL 渲染流程图2. 单个流程解释
- ①【必选】:准备阶段提供的数据,例如:位置、颜色、纹理等
- ②【必选】:顶点着色器,执行大量的计算来计算顶点在屏幕上的位置,这里会用到变换矩阵的概念。也可能只作为传递着色器,仅仅是将数据复制并传递到下一个着色阶段;
- ③【可选】:细分控制着色器,使用面片(patch)来描述一个物体的形状,增加几何图元的数量,使外观更加平顺;
- ④【可选】:细分计算着色器,很明显,一个控制,控制那些几何图元需要做哪些设置,一个计算,针对这些要求做计算。例如:修改几何图元类型或者放弃所有的凸缘;
- ⑤【可选】:几何着色,创建新的图元;
- ⑥【必选】:也称为图元装配,将顶点与相关的几何图元之间组织起来,为后面的裁剪和光栅化做准备;
- ⑦【必选】:调整图元,保证相关的像素不会在视口外绘制,这个过程是由OpenGL自动完成的;
- ⑧【必选】:光栅化的作用就是产生片元(像素),光栅化意味着一个片元的生命伊始,可以将片元理解为“候选的像素”、“像素占位”;
-
⑨【必选】:片元着色阶段,通过编程控制屏幕上显示颜色的阶段。通过纹理映射的方式对顶点处理阶段所计算的颜色进行补充。
顶点着色:决定一个图元应该位于屏幕什么位置;
片元着色:使用这些信息来决定片元的颜色应该是什么; -
⑩【必选】:逐片元的操作:独立片元处理阶段,这里会使用深度测试(z缓存)、模板测试来决定一个片元是否可见;
如果一个片元通过了所激活的测试,既可以被绘制到帧缓存里面;
如果一个片元开启了融混模式,则该片元的颜色会与该像素的颜色叠加,形成一个新的颜色值存入帧缓存中。
3. 流程总结
- 四个阶段
红宝书上给出的流程是三排,也就是将顶点数据准备阶段和顶点着色器阶段放在一起了,这里为了方便记忆,我将每一个职责单独分开,成了四个阶段。
- 第一阶段:顶点数据准备阶段,对应流程①;
- 第二阶段:产生图元阶段,对应流程②、③、④、⑤,这四个流程;
- 第三阶段:处理图元,产生片元阶段,对应流程⑥、⑦、⑧,这三个流程;
- 第四阶段:处理片元,渲染上屏阶段,对应流程⑨、⑩,这两个阶段;
- 可编程阶段
- 第一阶段:提供顶点数据肯定是需要用户自己来完成的。
- 第二阶段:在 OpenGL 部分,我们只能对顶点着色器进行修改,而细分着色器、几何着色器,都没有提供相关设置的接口,所以虽然他们是可编程的,但是在 OpenGL 部分,我们也无法处理他们。
- 第四阶段:对片元的处理,颜色着色、混合等,是否满足各种测试,是否需要丢弃这些都是在第四阶段完成的。
- OpenGL 自动处理阶段
- 第三阶段:在第二阶段我们提供了图元数据之后,顶点数据、顶点之间的连接方式、位置、纹理数据等,这些都会交给 OpenGL,由 OpenGL 来处理图元,并在光栅化阶段生成片元。
3.1 OpenGL 渲染架构:蓝宝书【补】
OpenGL 渲染架构 OpenGL 渲染架构名词解释3.2 OpenGL 渲染原理:Render Server
Render Server- GPU 收到
Command Buffer
,包含图元 primitives 信息 - Tiler 开始工作:先通过顶点着色器
Vertex Shader
对顶点进行处理,更新图元信息 - 平铺过程:平铺生成 tile bucket 的几何图形,这一步会将图元信息转化为像素,之后将结果写入 Parameter Buffer 中
- Tiler 更新完所有的图元信息,或者
Parameter Buffer
已满,则会开始下一步 - Renderer 工作:将像素信息进行处理得到
bitmap
,之后存入 Render Buffer -
Render Buffer
中存储有渲染好的 bitmap,供之后的 Display 操作使用
3.3 OpenGL 渲染原理:画家算法
画家算法- 由远及近绘制
- 图层树中,最上层的离屏幕最近,最下层的离屏幕最远
- 图层重叠时无法使用画家算法绘制
3.4 OpenGL 渲染过程:正常和离屏的简易流程
简易渲染流程- 正常流程:将内容渲染完成之后,不停地放入 Framebuffer 中,然后显示屏幕不断地从 Framebuffer 中读取内容,显示实时内容;
- 离屏渲染:创建 Offscreenbuffer >> 将提前渲染好的内容放入其中 >> 等到合适的时机在将 OffScreenbuffer 中的内容进一步叠加、渲染 >> 最后将结果放入 Framebuffer 中;
- 显示屏幕最后获取数据的来源都是 帧缓存Framebuffer;
- Offscreenbuffer 只是一个临时存储渲染数据的地方
3.5 OpenGL 渲染过程:Layer的绘制【正常和离屏】
正常流程- 绘制 sublayer1,绘制完了丢弃
- 绘制 sublayer2,绘制完了丢弃
- 绘制 sublayer3,绘制完了丢弃
- 绘制 sublayer1,存入离屏缓存区
- 绘制 sublayer2,存入离屏缓存区
- 绘制 sublayer3,存入离屏缓存区
- 从离屏缓存区取出 sublayer1
- 从离屏缓存区取出 sublayer2
- 从离屏缓存区取出 sublayer3
- 混合 -> 呈现
3.6 OpenGL 渲染案例:Mask渲染流程【离屏】
Mask渲染流程- 渲染mask,存入离屏缓存区;
- 渲染layer,存入离屏缓存区;
- 读取离屏缓存区的数据,然后进行混合操作,将结果存入帧缓存区;
- 等待下一次 runloop到来,显示到屏幕上;
3.7 OpenGL 渲染案例:UIBlurEffectView渲染流程【离屏】
UIBlurEffectView渲染流程- Content:渲染内容
- capture content:捕获内容
- Horizontal Blur:水平模糊
- Vertical Blur:垂直模糊
- Compositing Pass:合并过程
- 合并完成之后将结果存入帧缓存区,等待下一次 runloop到来,显示到屏幕上
四、显示阶段:帧缓存区到显示、帧缓存区的切换
4.1 位图显示到屏幕流程
位图显示到屏幕流程- 视频控制器(Video Controller)会读取帧缓冲器中的信息
- 经过
数模转换
传递给显示器(Monitor)
- 电子束会从屏幕的左上角开始逐行扫描,屏幕上的每个点的图像信息都从帧缓冲器中读取的位图
4.2 撕裂流程(需要画图)
撕裂流程- 新的位图还没有渲染好;
- 电子束从头开始新的一帧的扫描;
- 电子束扫描到一半的时候,新的位图渲染好了;
- 电子束扫描另一半内容,这一半内容显示的是新的位图,所以造成撕裂现象
4.3 垂直同步 Vsync + 双缓冲机制 Double Buffering
垂直同步信号(vertical synchronisation,Vsync)相当于给帧缓存区加锁:当电子束完成一帧的扫描,将要从头开始扫描时,就会发出一个垂直同步信号,只有当视频控制器接收到 Vsync 之后,才会将帧缓存区中的位图更新为下一帧。
双缓冲机制- 视频控制器读取
primary surface
缓存区里的内容进行显示; - 渲染服务(render server)将内容渲染存入
back Buffer
后台缓存区里; - 等下一次呈现指令到来时,交换缓存区
- 视频控制器读取
back Buffer
的内容 - 渲染服务将内容存入
primary surface
- 视频控制器读取
4.4 掉帧流程
掉帧流程【说明】
- CPU 表示 CPU 处理需要的时长
- GPU 表示 GPU 处理需要的时长
- 两个 Vsync 之间的长度表示两帧之间的间隔时长
- 流程中不管是处理B时GPU耗时更多,还是处理A时CPU耗时更多,在 CPU+GPU 的耗时时长都是大于了两次同步信号之间的间隔时长,所以都会造成掉帧
【过程】
- 第一帧显示 A
- 第二帧显示 A(第一个 Vsync 到来,但是 B 还没有渲染完成,因为同步问题,所以这一次不会更新帧缓存区里的位图,也即当前帧缓存区里面的位图还是 A);
- 第三帧显示 B(CPU+GPU过程 A 没有处理完成)
- 第四帧显示 A(A已经处理完成了)
4.5 三缓冲 Triple Buffering
三缓冲- 双缓存区过程中,CPU和GPU 在处理 B 到 A 的过程中,各自有一定的闲置状态
- 三缓存区,就是充分利用 CPU和GPU 的性能,降低掉帧的概率
网友评论