网页渲染过程

现代浏览器的结构

image

常见的渲染引擎

渲染引擎:能够能够将HTML/CSS/JavaScript文本及相应的资源文件转换成图像结果

渲染引擎的种类:

浏览器 渲染引擎
IE、Edge(旧) Trident
FireFox Gecko
Safari WebKit
Chrome/Chromium,Opera,Edge(新) Blink(Webkit fork)

渲染引擎结构与工作流程

已HTML/JavaScript/Css等文件作为输入,以可视化内容作为输出

image

image

Webkit 网页渲染过程

  1. HTML 解释器 解析 HTML 文档生成 DOM 树。
  2. CSS 解释器 解析生成 CSS 树(CSSOM)
  3. 结合 CSSOM 和 DOM树 构建 RenderObject 树,并对 RenderObject 进行布局计算,并把结果保存到 RenderObject 中
  4. 根据 RenderObject 树和 CSSOM 相关属性构建 RenderLayer 树,主要用于网页分层与渲染合成。
  5. 合成图层,结合软件或硬件(GPU)进行渲染。

image

网页的层次结构是指网页中的元素分布在不同的层次中,也就是说某些元素可能不同于它的父元素所在的层次,因为某些原因,Webkit需要为改元素和它的子女建立一个新层。

如下demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
//css
<style>
video,div,canvas{
-webkit-transform: rotateY(30deg) rotateX(-45deg);
}
</style>

//html
<video src="avideo.mp4"></video>
<div>
<canvas></canvas>
<canvas></canvas>
</div>

image

  • 根节点所在的层叫做“根层”,是最大的层
  • 层1是“video”所在的层,“video”需要创建一个新的层,而不是使用它的父元素所在的层。这是因为,“video”是用来播放视频,为它创建一个新层可以有效的处理视频解码器和浏览器之间的交互和渲染
  • 层2是 “div” 元素,“div” 独立分层的原因是因为 “div” 需要进行3D变换。

Webkit为复杂元素创建新层,有助于渲染引擎在处理上的方便和高效。

以下元素或样式会触发元素独立分层:

  1. 根元素
  2. position 不为static
  3. transform
  4. 半透明
  5. canvas
  6. video
  7. overflow 不为visible

RenderLayer

从上面网页的渲染过程来看,RenderObject树 构建完后就会构建 RenderLayer。RenderLayer对象其实就是上面提到的层的概念。层最后会进行合成从而进行绘制。

WebKit 会为网页的层次创建相应的 RenderLayer 对象。当某些类型 RenderObject 的节点或者具有某些 CSS 样式的 RenderObject 节点出现的时候,WebKit 就会为这些节点创建 RenderLayer 对象。一般来说,某个 RenderObject 节点的后代都属于该节点,除非 WebKit 根据规则为某个后代 RenderObject 节点创建了一个新的 RenderLayer 对象。

RenderLayer 树是基于 RenderObject 树建立起来的一棵新树,根据上面的描述,可以得出这样的结论:RenderLayer 节点 和 RenderObject 节点不是一一对应关系,而是一对多的关系。

image

那么哪些情况下的 RenderObject 节点需要建立新的 RenderLayer 节点呢?以下是基本规则:

  • DOM 树的 Document 节点对应的 RenderView 节点。
  • DOM 树中的 Document 的子女节点,也就是 HTML 节点对应的 RenderBlock 节点。
  • 显式的指定 CSS 位置的 RenderObject 节点。
  • 有透明效果的 RenderObject 节点。
  • 节点有溢出(overflow)、alpha 或者反射等效果的 RenderObject 节点。
  • 使用 Canvas 2D 和 3D(WebGL)技术的 RenderObject 节点。
  • Video 节点对应的 RenderObject 节点。

每个 RenderLayer 节点包含的 RenderObject 节点其实是一棵 RenderObject 子树。RenderLayer 节点的使用可以有效地减小网页结构的复杂程度,并在很多情况下能够减少重新渲染的开销。理想情况下,每个 RenderLayer 对象都有一个后端类,用来存储该 RenderLayer 对象绘制的结果。

在 WebKit 创建 RenderObject 树之后,WebKit 也会创建 RenderLayer 树。当然某些 RenderLayer 节点也有可能在执行 JavaScript 代码时或者更新页面的样式时被动态创建。

与 RenderObject 类不同的是,RenderLayer 类没有子类,它表示的是网页的一个层次,并没有“子层次”的说法。

网页渲染

在完成构建 DOM 树之后,WebKit 索要做的事情就是构建渲染的内部表示并使用图形库将这些模型绘制出来。提到网页的渲染方式,目前主要有三种方式,第一种是软件渲染,第二种是硬件加速渲染,第三种是混合模式。

每个 RenderLayer 对象可以被想象成图像中的一个层,各个层一同构成了一个图像。在渲染的过程中,浏览器也可以作同样的理解。每个层对应网页中的一个或者一些可视元素,这些元素都绘制内容到该层上。

如果绘图操作使用 CPU 来完成,那么称之为软件绘图。如果绘图操作由 GPU 来完成,称之为 GPU 硬件加速绘图。理想情况下,每个层都有个绘制的存储区域,这个存储区域用来保存绘图的结果。最后,需要将这些层的内容合并到同一个图像之中,我们称之为合成(Compositing),使用了合成技术的渲染称之为合成化渲染。

下图是网页的三种渲染方式:

image

第一种软件渲染实际上用的是一块 CPU 内存空间。第二种和第三种方式,都是使用了合成化的渲染技术,也就是使用 GPU 硬件来加速合成这些网页层,合成的工作都是由 GPU 来做,这里成为硬件加速合成(Accelerated Compositing)。但是,对于每个层,这两种方式有不同选择。其中某些层,第二种方式使用 CPU 来绘图,另外一些层使用 GPU 来绘图。对于使用 CPU 来绘图的层,该层的结果首先当然保存在 CPU 内存中,之后被传输到 GPU 的内存中,这主要是为了后面的合成工作。第三种渲染方式使用 GPU 来绘制所有合成层。第二种方式和第三种方式其实都属于硬件加速渲染方式。

为什么会有三种渲染方式,这时因为三种方式各有各的优缺点和适用场景,在介绍它们之前,先了解一些渲染方面的基本知识。

首先,对于常见的 2D 绘图操作,使用 GPU 来绘图不一定比使用 CPU 绘图在性能上有优势,例如绘制文字、点、线等,原因是 CPU 的使用缓冲机制有效减少了重复绘制的开销而且不需要考虑与 GPU 并行。其次,GPU 的内存资源相对 CPU 的内存资源来说比较紧张,而且网页的分层使得 GPU 的内存使用相对比较多。鉴于此,就目前的情况来看,三者都存在是有其合理性的,下面分析一下它们的特点:

  • 软件渲染是目前很常见的技术,也是浏览器最早使用的渲染方式。这一技术比较节省内存,特别是宝贵的 GPU 内存,但是软件渲染只能处理 2D 方面的操作。简单的网页没有复杂绘图或者多媒体方面的需求,软件渲染方式就比较合适来渲染该类型的网页。问题是,一旦遇上了 HTML 5 的很多新技术,软件渲染显然无能为力,一是因为能力不足;二是因为性能不好,例如视频、Canvas 2D 等。所以,软件渲染技术被用的越来越少,特别是在移动领域。软件渲染同硬件加速渲染另外一个很不同的地方就是对更新区域的处理。当网页中有一个更新小型区域的请求(如动画)时,软件渲染可能只需要计算一个极小的区域,而硬件渲染可能需要重新绘制其中的一层或者多层,然后再合成这些层。硬件渲染的代价可能会大得多。
  • 对于硬件加速的合成化渲染方式来说,每个层的绘制和所有曾的合成均使用 GPU 硬件来完成,这对需要使用 3D 绘图的操作来说特别合适。这种方式下,在 RenderLayer 树之后,WebKit 还需要建立更多的内部表示,目的是支持硬件加速机制,这显然会消耗更多的内存资源。但是,一方面,硬件加速机制能够支持现在所有的 HTML5 定义的 2D 或者 3D 绘图标准;另一方面,关于更新区域的讨论,如果需要更新某个层的一个区域,因为软件渲染没有为每一层提供后端存储,因而它需要将和这个区域有重叠部分的所有层次的相关区域一次从后往前重新绘制一遍,而硬件加速渲染只需要重新绘制更新发生的层次,因而在某些情况下,软件渲染的代价又变得更大。当然,这取决于网页的结构和渲染策略。
  • 软件绘图的合成化渲染方式结合了前面两种方式的优点,这时因为很多网页可能既包含基本的 HTML 元素,也包含一些 HTML5 新功能,使用 CPU 绘图方式来绘制某些层,使用 GPU 来绘制其他一些层。原因当然是前面所述的基于性能和内存方面综合考虑的结果。

硬件加速机制——合成层

对于 GPU 绘图而言,通常不像软件渲染那样只是计算其中更新的区域,一旦有更新请求,如果没有分层,引擎可能需要重新绘制所有的区域,因为计算更新部分对 GPU 来说可能耗费更多时间。当网页分层之后,部分区域的更新可能只在网页的一层或者几层,而不需要将整个网页都重新绘制。通过重新绘制网页的一个或者几个层,并将它们和其他之前绘制完的层合成起来,既能使用 GPU 的能力,又能够减少重绘的开销。

一个 RenderLayer 对象如果需要后端存储,它会创建一个 RenderLayerBacking 对象,该对象负责 RenderLayer 对象所需要的各种存储。理想情况下,每个 RenderLayer 都可以穿件自己的后端存储,但事实上并不是所有 RenderLayer 都有自己的 RenderLayerBacking 对象。如果一个 RenderLayer 对象被 WebKit 依照一定的规则创建了后端存储,那么该 RenderLayer 被称为合成层。

哪些 RenderLayer 对象可以是合成层呢?如果一个 RenderLayer 对象具有以下的特征之一,那么它就是合成层。

  • RenderLayer 具有 CSS 3D 属性或者 CSS 透视效果。
  • RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的视频解码技术的 HTML5video元素。
  • RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的 Canvas 2D 元素或者 WebGL 技术。
  • RenderLayer 使用了 CSS 透明效果的动画或者 CSS 变换的动画。
  • RenderLayer 使用了硬件加速的 CSS Filters 技术。
  • RenderLayer 使用了剪裁(Clip)或者反射(Reflection)属性, 并且它的后代中包括一个合成层。
  • RenderLayer 有一个 Z 坐标比自己小的兄弟节点,且该节点是一个合成层。

至于为什么这么做,有以下三个原因:

  1. 为了合并一些 RenderLayer 层,这样可以减少内存的使用量。
  2. 合并之后,尽量减少合并带来的重绘性能和处理上的困难。
  3. 对于那些使用单独层能够显著提升性能的 RenderLayer 对象,可以继续使用这些好处,例如使用 WebGL 技术的 Canvas 元素。

参考: