显示器如何显示图像?
每个显示器都有固定的刷新频率,通常是 60HZ,即每秒更新 60 张图片。更新的图片都来自于显卡中一个叫前缓冲区的地方。显示器所做的任务就是每秒固定读取 60 次前缓冲区中的图像并将其显示到显示器上。
显卡用来干什么?
显卡负责合成新图像并将其保存到后缓冲区,一旦显卡把合成好的图像写到后缓冲区,系统就会让后缓冲区与前缓冲区互换,以此来保证显示器读取到的是新图像。
通常情况下,显卡更新频率与显示器刷新频率一致。但是在复杂场景中,显卡处理图片的速度会变慢,造成视觉上的卡顿。
帧与帧率
当通过滚动条滚动页面或者通过手势缩放页面时,屏幕上会有动画效果。之所以会有动画效果,是因为在滚动与缩放过程中,渲染引擎会通过渲染流水线生成新的图片,并发送到显卡的后缓冲区。
我们把渲染流水线生成的每一副图片称为一帧,把每秒更新多少帧称为帧率。比如,滚动过程中 1s 更新 60 帧,那么帧率就为 60Hz 或者 60FPS.
要解决卡顿问题,就要解决每帧生成时间过久的问题。为此 Chrome 引入分层与合成机制。
如何生成一帧图像?
三种方式:重排、重绘、合成。
上面三种渲染路径不同,通常渲染路径越长,生成图像花费的时间就越多。
Chrome 合成技术可以用三个词概括:分层、分块、合成。
分层与合成
为什么要引入分层与合成机制?
页面要实现一些复杂的动画效果,从布局树直接生成目标图片的话,每次页面的微小变化都会触发重排与重绘,严重影响页面渲染效率。
为了提升每帧的渲染效率,Chrome 引入了分层与合成机制。
如何理解分层与合成机制?
你可以把一张网页想成是由很多图片叠加在一起的,每个图片就对应一个图层(类似于 PhotoShop 中的图层,可以设置透明度、边框、阴影等),Chrome 的合成器最终将这些图层合成了用于显示页面的图片。
此过程中,将素材分解为多个图层的操作就叫分层,将图层合并的过程就叫合成。
一个页面被分为两个层,当到下一帧渲染时,上一帧可能要实现某些变换(平移、旋转、缩放等),这时合成器只需将这两个层进行相应的变化,合成时间非常短。
Chorme 是怎么实现分层与合成机制的?
生成布局树后,渲染引擎会根据布局树的特点将其转换为层树(分层),层树中每个节点都对应着一个图层,绘制阶段就依赖于层树中的节点。绘制阶段并不是真正的绘制图片,而是将绘制指令组合成一个列表。具体见从输入 URL 到页面展示,中间发生了什么分层部分。
合成操作是在合成线程上完成的,这意味着在执行合成操作时,是不会影响到主线程的。
这也是为什么即使主线程卡住了,CSS 动画依然能执行。
分块
如果说分层是从宏观上提升了渲染效率,那么分块则是从微观层面提升了渲染效率。
通常情况下,页面内容远大于屏幕大小。如果等所有图层都生成完毕再合成,会让合成图片的时间变得更久。
因此合成线程会将每个图层分为大小固定的图块,然后优先绘制靠近视口的图块,这样就可以加速页面显示速度。不过,有时即使只绘制优先级高的图块,也要耗费不少时间,因为中间涉及到纹理上传,从计算机内存上传到 GPU 内存的操作会比较慢。
为了解决这个问题,Chrome 在首次合成图块的时候使用一个低分辨率的图片。
分辨率减少一半,纹理就减少了四分之三。首次展示页面时,展示低分辨率图片,然后合成器继续绘制正常比例的页面内容,当绘制完成后,再替换掉当前显示的低分辨率内容。
利用分层技术优化代码
如果要对某个元素做几何形状变化、透明度变换或者缩放,可以使用 will-change 告诉渲染引擎,如下:
.box { will-change: transform, opacity; }
will-change 会提前告诉渲染引擎 box 元素要做几何变换和透明度变换操作,这是渲染引擎会将该元素单独实现一层,等变换发生时,渲染引擎会通过合成线程直接处理变换,此变换不涉及主线程,故效率高。这就是 CSS 动画比 JS 动画高效的原因。
注意点:will-change 会让渲染引擎为该元素准备独立层,占用的内存也会大大增加,因为从层树开始,后续每个阶段都会多一个层结构,都需要额外内存。
合成线程里完成哪些操作?
能直接在合成线程中完成的任务都不会改变图层的内容,如文字信息的改变,布局的改变,颜色的改变,统统不会涉及,涉及到这些内容的变化就要牵涉到重排或者重绘了。
能直接在合成线程中实现的是整个图层的几何变换,透明度变换,阴影等,这些变换都不会影响到图层的内容。
比如滚动页面的时候,整个页面内容没有变化,这时候做的其实是对图层做上下移动,这种操作直接在合成线程里面就可以完成了。
再比如旋转操作,如果样式里面使用了 will-change ,那么这些 box 元素都会生成单独的一层,那么在旋转操作时,只要在合成线程将这些 box 图层整体旋转到设置的角度,再拿旋转后的 box 图层和背景图层合成一张新图片,这个图片就是最终输出的一帧,整个过程都是在合成线程中实现的。