[译] 浏览器帧原理剖析

浏览器帧原理剖析

开发者经常问我关于像素工做流程的某些部分,何时、为何、发生了什么。因此我感受值得提供一些参考,有关于将像素显示在屏幕上的过程里发生了什么。前端

警告:文本是 Blink(译注:Chrome 使用的排版引擎,是 webkit 的分支)和 Chrome 的视角。主线程的大部分任务以某种方式被全部第三方(vendors)任务“共享”,好比布局和样式计算结果,可是总的架构可能不是这样。android

一图胜千言

这是真的,让咱们先看一张图:ios

The process of getting pixels to screen.

将像素放到屏幕上的完整过程。git

下载图片github

进程

这张小图上放了太多内容,因此让咱们详细看些定义。将上图与这些定义结合起来可能会有帮助。web

让咱们从进程开始看:canvas

  • 渲染进程。包裹标签页的容器。包含了多个线程,这些线程一块儿负责了页面显示到屏幕上的各个方面。这些线程有合成线程(Compositor)图块栅格化线程(Tile Worker),和主线程
  • GPU 进程。这是一个单一的进程,为全部标签页和浏览器周边进程服务。当帧被提交时,GPU 进程会将分为图块的位图和其余数据(好比四边形顶点和矩阵)上传到 GPU 中,真正将像素显示到屏幕上。GPU 进程只有一个的线程,叫 GPU 线程,其实是它作了这些工做。

渲染进程中的线程

如今看一下渲染进程中的线程。后端

  • 合成线程(Compositor Thread)。这是最早被告知垂直同步事件(vsync event,操做系统告知浏览器刷新一帧图像的信号)的线程。它接收全部的输入事件。若是可能,合成线程会避免进入主线程,本身尝试将输入的事件(好比滚动)转换为屏幕的移动。它会更新图层的位置,并经由 GPU 线程直接向 GPU 提交帧来完成这个操做。若是输入事件须要进行处理,或者有其余的显示工做,它将没法直接完成该过程,这就须要主线程了。
  • 主线程。在这里浏览器执行咱们熟知和喜欢的那些任务:JavaScript,样式,布局和绘制。(这一点之后会变化,有了 Houdini,咱们能够在合成线程中运行一些代码)主线程荣获“最容易致使 jank 奖”,很大程度上是由于它要作的事情太多了这个事实。(译注:jank 指页面内容抖动卡顿,因为页面内容的更新频率跟不上屏幕刷新频率致使)
  • 合成图块栅格化线程(Compositor Tile Worker)。由合成线程派生的一个或多个线程,用于处理栅格化任务。咱们稍后再讨论。

在许多方面,你都应该把合成线程看作“老大”。虽然这个线程不运行 JavaScript,不进行布局、绘制内容或者其余任务,可是它全权负责启动主线程工做,并将帧运送到屏幕上。若是合成线程不用等待输入事件的处理,就能够在等待主线程完成工做时把帧发送出去。浏览器

你也能够想象 Service WorkerWeb Worker 存在于渲染进程中,虽然我把他们排除在外了,由于他们把事情弄得很复杂。架构

运做过程

The main thread in all its glory.

主线程风貌全览。下载图片

让咱们从垂直同步信号到像素,逐步分析这个过程,而后讨论一下在彻底版本中事件是怎么工做的。记住这一点:浏览器并不须要执行全部步骤,具体状况取决于哪些步骤是必需的。例如,若是没有新的 HTML 要解析,那么解析 HTML 的步骤就不会触发。事实上,一般提高性能的最佳方法,只是简单地移除流程中部分步骤被触发的须要!

一样值得注意的是,上图中 RecalcStyles 和 Layout 下方指向 requestAnimationFrame 的红色箭头。在代码中刚好触发这两个状况是彻底可能的。这种状况叫作强制同步布局(或强制同步样式,Forced Synchronous Layout 和 Forced Synchronous Styles),一般于性能不利。

  1. 开始新的一帧。垂直同步信号触发,开始渲染新的一帧图像。

  2. 输入事件的处理。从合成线程将输入的数据,传递到主线程的事件处理函数。全部的事件处理函数(touchmovescrollclick)都应该最早触发,每帧触发一次,但也不必定这样;调度程序会尽力尝试,可是是否真的每帧触发因操做系统而异。从用户交互事件,到事件被交付主线程,两者之间也存在延迟。

  3. requestAnimationFrame。这是更新屏幕显示内容的理想位置,由于如今有全新的输入数据,又很是接近即将到来的垂直同步信号。其余的可视化任务,好比样式计算,由于是在本次任务以后,因此如今是变动元素的理想位置。若是你改变了 —— 好比说 100 个类的样式,这不会引发 100 次样式计算;它们会在稍后被批量处理。惟一须要注意的是,不要查询进行计算才能获得的样式或者布局属性(好比 el.style.backgroundImageel.style.offsetWidth)。若是你这样作了,会致使从新计算样式,或者布局,或者两者都发生,进一步致使强制同步布局,乃至布局颠簸

  4. 解析 HTML(Parse HTML)。处理新添加的 HTML,建立 DOM 元素。在页面加载过程当中,或者进行 appendChild 操做后,你可能看到更多的此过程发生。

  5. 从新计算样式(Recalc Styles)。为新添加或变动的内容计算样式。可能要计算整个 DOM 树,也可能缩小范围,取决于具体更改了什么。例如,更改 body 的类名影响可能很大,可是值得注意的是浏览器已经足够智能了,能够自动限制从新计算样式的范围。

  6. 布局(Layout)。计算每一个可见元素的几何信息(每一个元素的位置和大小)。通常做用于整个文档,计算成本一般和 DOM 元素的大小成比例。

  7. 更新图层树(Update Layer Tree)。这一步建立层叠上下文,为元素的深度进行排序。

  8. Paint。过程分为两步:第一步,对全部新加入的元素,或进行改变显示状态的元素,记录 draw 调用(这里填充矩形,那里写点字);第二步是栅格化(Rasterization,见后文),在这一步实际执行了 draw 的调用,并进行纹理填充。Paint 过程记录 draw 调用,通常比栅格化要快,可是两部分一般被统称为“painting”。

  9. 合成(Composite):图层和图块信息计算完成后,被传回合成线程进行处理。这将包括 will-change、重叠元素和硬件加速的 canvas 等。

  10. 栅格化规划(Raster Scheduled)栅格化(Rasterize):在 Paint 任务中记录的 draw 调用如今执行。过程是在合成图块栅格化线程(Compositor Tile Workers)中进行,线程的数量取决于平台和设备性能。例如,在 Android 设备上,一般有一个线程,而在桌面设备上有时有 4 个。栅格化根据图层来完成,每层都被分红块。

  11. 帧结束:各个层的全部的块都被栅格化成位图后,新的块和输入数据(可能在事件处理程序中被更改过)被提交给 GPU 线程。

  12. 发送帧:最后,但一样很重要的是,图块被 GPU 线程上传到 GPU。GPU 使用四边形和矩阵(全部经常使用的 GL 数据类型)将图块 draw 在屏幕上。

福利时间

  • requestIdleCallback:若是在帧结束时,主线程还有点时间,requestIdleCallback 可能会被触发。这是作些非必要工做的好机会,好比标记分析数据。若是你不熟悉 requestIdleCallbackGoogle Developers 上的入门知识能帮到你。

两种图层

在工做流程中深度的排序有两种版本。

首先是层叠上下文,好比有 2 个绝对定位的重叠的 div。更新图层树(Update Layer Tree) 是流程的一部分,保证 z-index 和相似的属性受到重视。

而后是合成图层,在上述流程较后的位置,多用于绘制元素。可使用空 transform 技巧(译注:指使用 translateZ(0,0) 强制开启硬件加速),或者 will-change: transform 将一个元素提高为合成图层,这样就能轻松地使用 transform 动画(有利于动画效果!)。可是若是存在重叠元素,浏览器也可能须要建立额外的合成图层,来保持由 z-index 或者其余属性指定的深度顺序。有趣!

扩展阅读

实质上,上面概述的过程都是在 CPU 中完成的。只有最后一部分,图块被上传和移动的过程,是在 GPU 中完成的。

然而,在 Android 上,像素流在栅格化时有所不一样:GPU 用得更多一些。在 GPU 着色器上用 GL 命令执行 draw 调用,而不是在合成图块栅格化线程中进行栅格化。

这就是所谓的 GPU 栅格化,是一种下降绘制(paint)成本的方法。在 Chrome DevTools 中启用 FPS Meter(FPS 计数),你能够查看页面是否使用了 GPU 栅格化。

The FPS meter indicating GPU Rasterization is in use.

FPS 计数面板显示了正在使用 GPU 栅格化。

其余资源

若是你但愿深刻研究,还有不少的资料,好比如何避免在主线程工做,或者浏览器渲染更深刻的运做机理。但愿这些资料能帮到你:

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索