06-渲染流程(下)

104 阅读5分钟

渲染流程(下)

上一节中,我们了解了渲染流水线中的DOM生成,样式计算、布局三个阶段

这一节,我们深入了解一下其他的流程

分层

现在我们虽然已经有了布局树了,而且每个元素的具体位置都计算出来了,但是还是不能绘制页面

因为页面中存在许多复杂的效果,如3D变化、页面滚动等,为了实现这些效果,浏览器需要为特定的节点生成专用的图层,并生成一颗图层树

打开Chrome的“开发者工具”,选择“Layers”标签,就可以可视化页面的分层情况

图层树和布局树节点之间的关系如下:

图层树和布局树的关系.png

如果一个节点没有对应的层,那么这个节点就从属于父节点的图层上

那么现在,我们怎么知道渲染引擎啥时候才会创建新的层呢,满足以下两点中的一点就能将元素提升为一个单独的图层:

  • 拥有层叠上下文属性的元素会被提升为单独的一层

    定位属性的元素、定义透明属性的元素、使用CSS滤镜的元素等,都拥有层叠上下文属性

层叠上下文属性.png

  • 需要剪裁的地方也会被创建为图层

    此处的剪裁就是文字超出盒子大小部分就会被剪裁

    出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层

图层绘制

在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制

那么渲染引擎具体怎么实现的呢?

实际上,渲染引擎图层的绘制会把一个图层的绘制拆分成多个很小的绘制指令,然后再把这些指令按照顺序组成一个待绘制的列表

绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制

图层绘制.png

开发者工具”的“Layers”标签,选择“document”层,可以实际体验下绘制列表

栅格化操作

在上述过程中,生成了绘制列表,但是绘制列表只是用来记录绘制顺序和绘制指令的列表,实际上绘制操作是由渲染引擎中的合成线程来完成的

主线程会把绘制列表提交给合成线程,然后合成线程就应该完成绘制工作,所以接下来我们了解一下合成线程如何工作?

首先我们先来了解一下视口,视口就是在整个页面上,用户看得到的部分就叫做视口

在有些情况下,有的图层可以很大,比如有的页面使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要

基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是256x256或者512x512

然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的

所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位

渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的

格栅化线程池.png

通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中

GPU操作是运行在GPU进程中,如果栅格化操作使用了GPU,那么最终生成位图的操作是在GPU中完成的,这就涉及到了跨进程操作, 如图:

GPU格栅化.png

可以看到,渲染进程把生成图块的指令发送给GPU,然后在GPU中执行生成图块的位图,并保存在GPU的内存中

合成和显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——DrawQuad,然后将该命令提交给浏览器进程

浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上

到这里,经过这一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面了

相关概念

重排

如果通过JS或者CSS修改了元素的几何位置属性,例如改变元素的宽高等,这样会触发浏览器的重新布局,会导致浏览器需要解析一系列子阶段,这个过程叫做重排

所以重排会产生很大的开销

重绘

重回就是修改像元素的背景颜色这种属性,那么布局阶段就不会被执行,因为没有引起几何位置的变化,所以直接进入绘制阶段,执行之后的一系列子阶段,这个过程就是重绘

相比于重排,重绘省去了布局和分层阶段,所以执行效率会高一点

直接合成阶段

如果我们修改CSS的动画属性,就可以避开重排和重绘直接在非主线程上执行合成动画操作

因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率