渲染原理学后简短笔记

UIView和CALayer的关系

其中,视图的职责是 创建并管理 图层,以确保当子视图在层级关系中 添加或被移除 时,其关联的图层在图层树中也有相同的操作,即保证视图树和图层树在结构上的一致性。
那么为什么 iOS 要基于 UIView 和 CALayer 提供两个平行的层级关系呢?

其原因在于要做 职责分离,这样也能避免很多重复代码。在 iOS 和 Mac OS X 两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘的交互有着本质的区别,这就是为什么 iOS 有 UIKit 和 UIView,对应 Mac OS X 有 AppKit 和 NSView 的原因。它们在功能上很相似,但是在实现上有着显著的区别。

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

CALayer的介绍

image.png

那么为什么 CALayer 可以呈现可视化内容呢?因为 CALayer 基本等同于一个 纹理。纹理是 GPU 进行图像渲染的重要依据。
在 图形渲染原理 中提到纹理本质上就是一张图片,因此 CALayer 也包含一个 contents 属性指向一块缓存区,称为 backing store,可以存放位图(Bitmap)。iOS 中将该缓存区保存的图片称为 寄宿图。

image.png

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

Custom Drawing 是指使用 Core Graphics 直接绘制寄宿图。实际开发中,一般通过继承 UIView 并实现 -drawRect: 方法来自定义绘制。
虽然 -drawRect: 是一个 UIView 方法,但事实上都是底层的 CALayer 完成了重绘工作并保存了产生的图片。下图所示为 -drawRect: 绘制定义寄宿图的基本原理。

Contents Image
Contents Image 是指通过 CALayer 的 contents 属性来配置图片(例如用 Photoshop 提前做好图片素材直接导入应用)。然而,contents 属性的类型为 id。在这种情况下,可以给 contents 属性赋予任何值,app 仍可以编译通过。但是在实践中,如果 content 的值不是 CGImage ,得到的图层将是空白的。
既然如此,为什么要将 contents 的属性类型定义为 id 而非 CGImage。这是因为在 Mac OS 系统中,该属性对 CGImage 和 NSImage 类型的值都起作用,而在 iOS 系统中,该属性只对 CGImage 起作用。
本质上,contents 属性指向的一块缓存区域,称为 backing store,可以存放 bitmap 数据。

CPU和GPU的区别

image.png

从代码写的UI到展示到屏幕上的大概过程

image.png
image.png

事实上,app 本身并不负责渲染,渲染则是由一个独立的进程负责,即 Render Server 进程。
App 通过 IPC 将渲染任务及相关数据提交给 Render Server。Render 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、视频控制器等相关部件,将图像显示在屏幕上。

屏幕上显示图像的原理

  • 在内存开辟一块和屏幕对应大小的空间,用来把当前屏幕需要展示的内容放到这块内存,然后需要显示的图像经过CRT电子枪(以前的屏幕的机制,这里只是原理)以极快的速度一行一行的扫描,扫描出来就呈现了一帧画面,随后电子枪又会回到初始位置循环扫描,形成了我们看到的图片或视频


    image.png
  • 最初设计只有一块内存,当内存中的数据更新的频率与渲染到屏幕上的频率不一致时(太快或者太慢)就会出现如下断层,如下

image.png

后来为了提高渲染效率和同步问题变成了双缓存,加标志位

  • 补充:CRT显示器是靠电子束激发屏幕内表面的荧光粉来显示图像的,由于荧光粉被点亮后很快会熄灭,所以电子枪必须循环地不断激发这些点。
    首先,在荧光屏上涂满了按一定方式紧密排列的红、绿、蓝三种颜色的荧光粉点或荧光粉条,称为荧光粉单元,相邻的红、绿、蓝荧光粉单元各一个为一组,学名称之为像素。每个像素中都拥有红、绿、蓝(R、G、B)三基色。
    工作原理
    CRT显示器用电子束来进行控制和表现三原色原理。电子枪工作原理是由灯丝加热阴极,阴极发射电子,然后在加速极电场的作用下,经聚焦极聚成很细的电子束,在阳极高压作用下,获得巨大的能量,以极高的速度去轰击荧光粉层。这些电子束轰击的目标就是荧光屏上的三基色。为此,电子枪发射的电子束不是一束,而是三束,它们分别受电脑显卡R、 G、 B三个基色视频信号电压的控制,去轰击各自的荧光粉单元。受到高速电子束的激发,这些荧光粉单元分别发出强弱不同的红、绿、蓝三种光。根据空间混色法(将三个基色光同时照射同一表面相邻很近的三个点上进行混色的方法)产生丰富的色彩,这种方法利用人们眼睛在超过一定距离后分辨力不高的特性,产生与直接混色法相同的效果。用这种方法可以产生不同色彩的像素,而大量的不同色彩的像素可以组成一张漂亮的画面,而不断变换的画面就成为可动的图像。

我写的UI是怎么渲染到内存上的

  • 咱们写的界面会有很多层,就和一棵树一样,渲染的时候会按照从根到叶的顺序,假如我写的界面有三层,那绘制到内存的顺序如下


    image.png

那每一层是怎么写到内存的?如下图

image.png

光栅化需要做的就是如下图


image.png

不透明 VS 透明

当源纹理是完全不透明,最终的颜色和源纹理一样。这就可以节省GPU的很多工作,因为GPU可以简单的复制源纹理而不用合成所有像素值。但是GPU没有办法区别纹理中的像素是不透明的还是透明。

透明的话需要计算
R = S + D * (1 - Sa)
最后的结果是通过源的颜色(最上面的纹理)加目标颜色(下面的纹理) 乘以(1 – 源颜色的透明度)公式里面所有的颜色就假定已经预先乘以了他们的透明度。

离屏渲染(Offscreen rendering)

GPU屏幕渲染有两种方式:

(1)On-Screen Rendering (当前屏幕渲染)

指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行。

(2)Off-Screen Rendering (离屏渲染)
指的是在GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。

当前屏幕渲染不需要额外创建新的缓存,也不需要开启新的上下文,相对于离屏渲染性能更好。但是受当前屏幕渲染的局限因素限制(只有自身上下文、屏幕缓存有限等),当前屏幕渲染有些情况下的渲染解决不了的,就使用到离屏渲染。

相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面:

(1)创建新缓冲区

要想进行离屏渲染,首先要创建一个新的缓冲区。

(2)上下文切换

离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen),等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上有需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。

OpenGL 严格来说并不是常规意义上的 API,而是一个第三方标准(由 khronos 组织制定并维护),其严格定义了每个函数该如何执行,以及它们的输出值。至于每个函数内部具体是如何实现的,则由 OpenGL 库的开发者自行决定。实际 OpenGL 库的开发者通常是显卡的生产商。DirectX 则是由 Microsoft 提供一套第三方标准。

速度对比

https://www.jianshu.com/p/8b0572f2c23a

你可能感兴趣的:(渲染原理学后简短笔记)