includeios / document

js玄学
27 stars 2 forks source link

浏览器的渲染过程2.0 — Composite #10

Open includeios opened 5 years ago

includeios commented 5 years ago

浏览器的渲染过程2.0 — Composite

前言

上一篇文章: 《浏览器的渲染机制》里,大概介绍了浏览器从下载完第一份document文件,到渲染出整个页面所发生的事情(也称作:关键路径渲染),其中包括:1、构建对象模型(DOM,CSSOM),2、构建渲染树(RenderTree),3、布局,4、渲染。

这一篇文章我们将参考chrome浏览器的内核WebKit, 详细介绍下浏览器呈现引擎的一些概念和渲染中的一个关键环节:Composite(渲染层合并),它所做的事情和带来的效果。

浏览器的主要结构

市面上主流的几款浏览器,基本上都是由下面几个结构组成:

浏览器的主要结构

  1. 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
  2. 浏览器引擎 - 在用户界面和呈现引擎之间传送指令。
  3. 呈现引擎 - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
  4. 网络 - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
  5. 用户界面后端 - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
  6. JavaScript 解释器。用于解析和执行 JavaScript 代码。
  7. 数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

其中值得注意的是,与其他浏览器不同的是:chrome浏览器中每个tab页对应一个呈现引擎实例,每个tab页都是一个单独的进程。

呈现引擎

webkit的呈现引擎,官方提供的一张主流程图是: webkit主流程

其中有些名词和 《浏览器的渲染机制》里提到的不一样,比如:Style Rules,这是webkit自己引入的几个概念,这里先解释一下,方便大家理解:

上面的过程可以合并成:

image

实际场景下,渲染流程可以分为下面三种,layout和point这个过程是可以避免的:

image

image

image

显而易见,第三种渲染流程浏览器的处理是最快的,那什么情况下的渲染是第三种情况呢,这里就不得不提到Compsite

Compsite

上一篇我们提到,浏览器渲染时,对渲染树有着一个RenderObject -> RenderLayout的转换过程:

1

实际上,Chrome拥有两套不同的渲染路径:硬件加速路径和旧软件路径。

硬件加速路径会将一些图层的合成交给GPU处理,比CPU处理更快,而我们的RenderLayout(有些地方又称作PaintLayers)并不能作为GPU的输入,这里会将RenderLayout再转换成GraphicsLayers: image

某些特殊的RenderLayout会被认为是合成层(Compositing Layers),合成层拥有单独的 GraphicsLayer,而其他不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 父层公用一个。

而每个GraphicsLayer(合成层单独拥有的图层) 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后显示到屏幕上。

很绕口对不对= =,人性话来说,与RenderObject转换成RenderLayer一样,浏览器也是根据“某些规则”,选中一些特殊的RenderLayout节点(天选之子),这些节点将被称为Compositing Layers,Compositing Layers与其他的普通节点不一样的是他们拥有自己的GraphicsLayer,而那些没有被选中的节点,会和父层公用一个GraphicsLayer。

每个GraphicsLayer都拥有GraphicsContext,GraphicsContext输出的位图会作为纹理上传到GPU中。

什么是纹理?可以把它想象成一个从主存储器(例如 RAM)移动到图像存储器(例如 GPU 中的 VRAM)的位图图像(bitmapimage)

Chrome 使用纹理来从 GPU上获得大块的页面内容。通过将纹理应用到一个非常简单的矩形网格就能很容易匹配不同的位置(position)和变形(transformation)。这也就是3DCSS 的工作原理,它对于快速滚动也十分有效。

RenderLayer -> GraphicsLayer,合成层的创建标准

什么情况下能使元素获得自己的层?虽然 Chrome的启发式方法(heuristic)随着时间在不断发展进步,但是从目前来说,满足以下任意情况便会创建层:

  • 直接原因(direct reason)
    • 硬件加速的 iframe 元素(比如 iframe 嵌入的页面中有合成层)
    • video 元素
    • 覆盖在 video 元素上的视频控制栏
    • 3D 或者 硬件加速的 2D Canvas 元素
    • 硬件加速的插件,比如 flash 等等
    • 在 DPI 较高的屏幕上,fix 定位的元素会自动地被提升到合成层中。但在 DPI 较低的设备上却并非如此,因为这个渲染层的提升会使得字体渲染方式由子像素变为灰阶
    • 有 3D transform
    • backface-visibility 为 hidden
    • 对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition(需要是 active 的 animation 或者 transition,当 animation 或者 transition 效果未开始或结束后,提升合成层也会失效)
    • will-change 设置为 opacity、transform、top、left、bottom、right(其中 top、left 等需要设置明确的定位属性,如 relative 等)
  • 后代元素原因
    • 有合成层后代同时本身有 transform、opactiy(小于 1)、mask、fliter、reflection 属性
    • 有合成层后代同时本身 overflow 不为 visible(如果本身是因为明确的定位因素产生的 SelfPaintingLayer,则需要 z-index 不为 auto)
    • 有合成层后代同时本身 fixed 定位
    • 有 3D transfrom 的合成层后代同时本身有 preserves-3d 属性
    • 有 3D transfrom 的合成层后代同时本身有 perspective
  • overlap 重叠原因,元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

合成层的性能优化:

对于提升为合成层有以下几个好处:

注意:

总结来说,一般一个元素开启硬件加速后会变成合成层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。

那么针对上面这些特点,可以做的性能优化有:

提升动画效果的元素

合成层的好处是不会影响到其他元素的绘制,因此,为了减少动画元素对其他元素的影响,从而减少paint,我们需要把动画效果中的元素提升为合成层。

提升合成层的最好方式是使用 CSS 的will-change属性。从上一节合成层产生原因中,可以知道 will-change 设置为opacity、transform、top、left、bottom、right 可以将元素提升为合成层。对于不支持will-change的浏览器版本,可以加上transform属性,比如:transform: translateZ(0)

使用 transform 或者 opacity 来实现动画效果

上文提到transform和opaciry属性不会引发layout和paint,值得注意的是,只有元素提升到合成层后才不会引发,普通元素修改transform和opacity还是会引发的。

减少绘制区域

对于不需要重新绘制的区域应尽量避免绘制。

比如一个 fix 在页面顶部的固定不变的导航header,当页面内容某个区域 repaint 时,在没有提升为合成层时,整个屏幕包括 fix 的 header 也会被重绘。而对于固定不变的区域,我们期望其并不会被重绘,因此可以通过之前的方法,将其提升为独立的合成层。

如何查看合成层

image

然后页面的合成层会用黄色的边框框出来 image

合成层可能会遇到的问题

由于合成层本身是占用内存的,当浏览器出现大量无法被压缩的合成层时(层爆炸),会导致内存紧张,GPU资源过度消耗等问题,具体可参考CSS3硬件加速也有坑!!!,这篇文章写的。

参考资料

https://fed.taobao.org/blog/2016/04/26/performance-composite/

https://www.html5rocks.com/zh/tutorials/speed/layers/

https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#Global_and_Incremental

https://segmentfault.com/a/1190000014520786

https://div.io/topic/1348

https://developers.google.com/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count

DingkeXue commented 5 years ago

您好,非常喜欢你写的文章。对于渲染流程可以分为三种这我有一个疑问,第二和第三种都是没有布局的,那么浏览器是怎么知道元素的具体位置的呢?

includeios commented 5 years ago

您好,非常喜欢你写的文章。对于渲染流程可以分为三种这我有一个疑问,第二和第三种都是没有布局的,那么浏览器是怎么知道元素的具体位置的呢?

页面在首次打开时一定会进行第一种类型的渲染,布局计算后会将具体的计算信息保存在内存中,之后判断不需要重新计算的话直接拿内存里的计算就好o( ̄▽ ̄)ブ

crise1993 commented 2 years ago

是不是普通文档流默认就是一个复合层,然后图层经过合成线程和光栅化线程处理成位图之后,会交给GPU进行合成渲染,最终呈现在页面?