《Life of a Pixel》——浏览器渲染流程概要

本文是 Chrome 团队新人入职学习资料《Life of a Pixel》的概要版,首发于我的博客( 点此查看),欢迎关注。
原文 Slides 地址: https://bit.ly/lifeofapixel
中文字幕演讲视频地址: https://www.bilibili.com/vide...

《Life of a Pixel》内容讲的是开发者编写的 web 内容(也就是通常所说的 HTML+CSS+JS 以及 image、video 等其他资源)渲染为图形并呈现到屏幕上的整个过程。我将其演讲内容分为以下三个部分,第一个是静态渲染过程,讲述一个完整的从 content 到 pixel 的渲染过程;第二个是动态更新过程,讲述浏览器如何高效更新页面内容。

概览

《Life of a Pixel》——浏览器渲染流程概要_第1张图片
首先看一下整个过程的概览。在了解详细内容前,我们也大概知道浏览器最终是通过调用 GPU 完成像素到屏幕的绘制。但这个过程中有很多的步骤。注意概览图中浏览器的渲染进程是放在沙箱进程中由 Blink 处理的,这也是其安全策略。

静态渲染过程

《Life of a Pixel》——浏览器渲染流程概要_第2张图片
这一页的内容对于广大前端从业者来说应该都比较熟悉。首先是 HTML 通过 HTMLDocumentParser 转换为 DOM 树,CSS 通过 CSSParser 转换为 StyleRule 集。每个 StyleRule 包含 CSSSelector 和 CSSPropertyValue,当然二者间存在对应关系。再加上浏览器提供的每种类型元素的 DefaultStyle,经过一系列的计算(这一步称为 recalc)生成所有元素包含所有 style 属性值的 ComputedStyle,如右上角的图所示。ComputedStyle 也通过开发者工具和 JS API 暴露了出来,相信大家也不陌生。

《Life of a Pixel》——浏览器渲染流程概要_第3张图片
接下来一步就是 layout。layout 的功能是根据上一步得到的所有元素的 computedStyle,将所有元素的位置布局计算好。每个元素在这一步会生成一个 LayoutObject,简单来说其包含四个属性:x、y、width 及 height 用于标识其布局位置。layout 最简单的情况就是,所有的块按照 DOM 顺序从上往下排列,也就是我们常说的流。layout 也包含很复杂的情况,比如带有 overflow 属性的元素,浏览器会计算其 border-box 的长宽和实际内容的长宽。如果设置为 scroll 并且内容超出,还要为其预留滚动条的位置。此外, float、flexbox 等布局也会使得 layout 变复杂。。所以为了解决复杂性的问题,layout 阶段浏览器首先会生成一个和 DOM 树节点大致一一对应的 layout 树,然后遍历该树,将经过计算后得出的位置布局数据填入节点。对于这个过程,Chrome 团队认为没有很好地分离输入和输出,因此下一代的 layout 系统会进行重构,使得分层更加清晰。

《Life of a Pixel》——浏览器渲染流程概要_第4张图片
然后进入 paint 阶段。需要注意的是这一步并不是真的绘制,只是生成对应的指令。对于每个 LayoutObject,浏览器会生成一个列表,列表中的每一项记录着绘制指令(比如画个红色的矩形)。记住这个待绘制列表项,后面会出现很多次。绘制按照堆栈也就是 z 轴的顺序在多个阶段进行。每个阶段只根据当前元素对应的属性(background->floats->foregrounds->outlines)进行绘制。注意,绘制并不严格是按照上述四种元素属性顺序,此处只作举例说明。

《Life of a Pixel》——浏览器渲染流程概要_第5张图片
下面就进入 raster 阶段,中文名为栅格化。栅格化的操作将上一步 paint 阶段每个 LayoutObject 存储的绘制指令列表中的每一项转换为颜色值的位图。位图中的每一项存储着 RGBA 值,对应着一个像素。位图存在于 GPU 内存中,还没有显示到屏幕上。GPU 除了用来存位图信息,还能执行生成位图的命令,也就是说栅格化过程可通过 GPU 进行,Chrome 默认开启 GPU 栅格化。GPU 栅格化的过程如下:浏览器调用 Skia 库,Skia 库对绘制指令建立单独的缓冲区以进行指令的转译处理,这一过程结束后缓冲区内容被释放输出并生成 OpenGL 调用。至此,这些 OpenGL 调用还存在于渲染沙箱进程,需要通过命令缓冲区机制代理传输到 GPU 进程执行。使用 GPU 进程的原因一是需要绕过渲染器沙箱的限制,二是将 OpenGL 程序如果不稳定或有安全漏洞,隔离开使其不至于影响浏览器的稳定性。在未来演进上,栅格化处理将转移至 GPU 进程中进行,以提升性能。同时 Vulkan 也会被支持。(注:Skia 是一个独立的图形处理函数库,其对硬件做了一层抽象,可以执行一系列相对底层 OpenGL 更复杂的指令。OpenGL 是跨语言跨平台的系统级绘图API。Vulkan 是下一代的绘图 API,旨在替代 OpenGL。)

以上过程揭示了静态渲染,也就是从 web content 到内存中的像素的整个流程。但是实际过程中页面是不断更新的,包括滚动、动画、js 等都会改变页面内容。一个完整的渲染过程是很昂贵的,如何高效更新也是讨论的重点。

动态更新过程

《Life of a Pixel》——浏览器渲染流程概要_第6张图片
首先明确一个概念,帧。涉及到时间时,每一帧是当前 Web 内容的完整呈现,通常,如果每秒低于 60 帧,滚动和动画就会显得有些卡顿。

《Life of a Pixel》——浏览器渲染流程概要_第7张图片
第一个优化方向最容易想到,即跟踪改变的部分,复用没有改变的部分。因此针对第一部分提到的 style、layout、paint、raster,浏览器都做了精细化跟踪失效的处理,每一帧都会复用前一帧没有变化的部分,只有被标记了需要变更的部分才会进行重新处理。

《Life of a Pixel》——浏览器渲染流程概要_第8张图片
由于 JS 和渲染都存在于主线程中,因此如果 JS 占据主线程做了耗时的操作,即使渲染很快,页面看起来仍然是比较卡顿的。所以这又引出了下一个优化点,compositing,中文名合成。

《Life of a Pixel》——浏览器渲染流程概要_第9张图片
合成包含两个概念,一是将页面分解成多个 layer,二是将这些 layer 在另一个线程中合成。layer 类似 PS 中图层的概念,可以独立于其他 layer 进行变换和栅格化。开发者工具中对其也有直观的展示。合成线程需要能够处理用户可能导致页面发生变化的输入事件比如(变换、剪切、滚动、特效),因为这些操作涉及了复合图层的改变。这样可以和主线程执行 js 互不干扰。但是当合成线程无法处理某个输入事件时,还是会由主线程来处理。layer 的存储依然是通过树形结构实现。合成更新是新出现的生命周期,出现在 layout 之后 paint 之前。每个 layer 都被单独绘制,因此其也有属于自己的绘制指令列表。未来,Chrome 可能会将合成图层生命周期放到 paint 后面。

《Life of a Pixel》——浏览器渲染流程概要_第10张图片
主线程的绘制阶段完成后,主线程上的 layer tree 将会被复制到合成线程上,合成完毕后再返回主线程。整个过程类似 git 中分支代码的合并。

《Life of a Pixel》——浏览器渲染流程概要_第11张图片
合成线程中,在对图层进行栅格化之前,还会有一步 tiling 的操作,也就是将 layer 拆分为多个小图块(tile),目的是为了防止出现某些情况下,某个滚动 layer 很长,但实际只需要展示当前容器内的一小块,如果整个 layer 进行栅格化将会比较浪费资源。复杂管理分块的模块叫 tile manager,它会随着滚动区域的变化,优先创建相邻的图块。所有图块栅格化完成后,合成线程将绘制 quads(四边形绘制)。一个 quad 类似于在屏幕上绘制一个图块的指令,其引用在内存中生成的栅格图块,然后被封装,由渲染进程提交到浏览器进程,这些就是每个动画帧。

《Life of a Pixel》——浏览器渲染流程概要_第12张图片
这里为了实现可以一边可以执行前一个提交的图块绘制任务,一边继续等待新的任务,合成线程还做了一些优化,实现了一个 pending layer tree。其接收 commit,当其准备好绘制后,会被激活(activation)从而复制到 active layer tree 上进行绘制任务。

《Life of a Pixel》——浏览器渲染流程概要_第13张图片
浏览器拿到渲染进程发来的动画帧之后,结合非内容区的其他渲染进程(比如浏览器 UI),调用 OpenGL 指令绘制最终的画面。

总结

《Life of a Pixel》——浏览器渲染流程概要_第14张图片
最后还是这张图,快速过一下每个步骤,web 内容、生成 DOM 树、解决样式问题、更新布局、生成合成图层、把图层绘制到待显示项列表中、把图层树提交给合成线程、把图层切分为小图块、对图块进行栅格化操作、把 pending layer tree 复制到 active layer tree、把树绘制成 quads、提交 quad 到浏览器进程、通过 GPU 进程调用 GL 指令绘制像素至屏幕上。

以上,就是一个像素的一生奇妙之旅。

你可能感兴趣的:(渲染,前端,浏览器)