前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Chromium 最新渲染引擎--RenderingNG

Chromium 最新渲染引擎--RenderingNG

作者头像
前端柒八九
发布2022-08-25 14:39:34
1.6K0
发布2022-08-25 14:39:34
举报
文章被收录于专栏:柒八九技术收纳盒

前言

大家好,我是柒八九。好久没有更文了(2周),一来是项目活比较多,二来空余时间在系统学习其他的东西,现在还未达到写文章总结的阶段,先做一个剧透,是关于WebAssemblyVue3原理的,后期会有一些列总结和教程。👉 「敬请期待」

所以总而言之,最近更文懈怠了。

但是,但是,但是,转折来了。今天给大家带来了一个关于Chromium最新「渲染架构」 RenderNG的译文。(其实这是一些列文章中一篇,后期也会有另外文章的择重翻译)。

V8如何处理JS的文章中,我们简短的介绍过浏览器的发展历史,并且还有几个奇怪的知识点。

  1. 「Chromium本身就是一个浏览器」
  2. 「Chrome浏览器一般选择Chromium的稳定版本作为它的基础」

既然,ChromiumChrome之间存在不清不楚的关系。所以,针对Chromium的研究其实就是对Chrome后续最新技术方向的尝鲜。毕竟,Chrome在当前浏览器份额中一家独大。掌握了它,就相当于掌握了,浏览器最新技术的发展脉络。

「last but not least」,也不知道大家在平时页面开发之余,是否对浏览器渲染页面的「内部机制」产生过兴趣。如果有的话,想必大家肯定查阅过很多资料。

例如:(如果连接失效,请在文末查找)

文章地址

年份

推荐⭐️数

How Browsers Work

2011

⭐️

Inside look at modern web browser

2018

⭐️⭐️⭐️ 需要🪜

Life of a Pixel

2020

⭐️⭐️⭐️⭐️有点晦涩难懂需要🪜

页面是如何生成的(宏观角度)

2022

⭐️⭐️⭐️⭐️⭐️

❝其实页面是如何生成的(宏观角度)是参考各种资料的一个汇总,也算是自我总结。然后,见文知意,该篇文章是从「宏观角度」讲述了浏览器是如何处理页面的。 ❞

而这篇文章的原文是负责Blink中渲染引擎研发的主管所写。无论是从专业角度和时间新鲜程度(2021年)都「墙裂推荐」

同时,为了行文方便,并且这也算是知识的「二次加工」,此篇文章不会原封不动的进行机械式翻译。会有一些其他资料的补充和修改。望周知。如果想看原文,文章最后会保有连接。(但是需要🪜)

简明扼要

  1. 每个「tab」中被渲染的页面内容是一个「树形结构」的数据格式(frame)
  2. 每一个frame结构包含: - DOM 数据信息 - CSS - 画布信息Canvas - 「其他资源」,例如图片image/视频video/字体font/SVG等
  3. 渲染过程主要涉及到 1. 渲染进程中的主线程 2. 渲染进程中的合成线程 3. viz进程(也叫GPU进程)
  4. 使用一些CSS3的属性,可以在渲染最开始的阶段(Animate)开启「硬件加速」
    1. transform
    2. opacity
    3. filter
    4. will-change
  5. 布局Layout阶段 一些「非可视化」的 DOM 元素不会插入布局树中 例如“head”元素/如果元素的 display 属性值为“none”,那么也不会显示在呈现树中(但是 visibility 属性值为“hidden”的元素仍会显示)
  6. 图层化Layerize/栅格化/图片解码Raster/Decode 都是发生在渲染进程的合成线程中
  7. 在同一时刻只有被唤起的页面才会占用浏览器进程
  8. 线程有助于实现「管道并行化」「多重缓冲」
  9. 渲染进程主线程Mian Thread: 1. 主要负责运行脚本 2. 管理事件循环vent loop、 3. 负责「文档生命周期」 4. 脚本「事件调度」 5. 「解析」HTML、CSS和其他数据格式
  10. 渲染进程合成线程Compositor Thread: 1. 处理「事件的输入」 2.「优化」页面的内容的滚动和动画效果 3. 对页面内容进行「图层化」处理 3.对图片进行解码处理 4. 绘画工作单元代码 5. 进行栅格化操作
  11. 渲染进程主线程 Blink 渲染器Blink renderer:
    • 本地frame树local frame tree:
    • 「DOM /Canvas API」:
    • 文档生命周期运行器document lifecycle runner
    • 输入事件探测input event hit testing
  12. 「每个局部框架树local frame tree都有自己的Blink渲染器组件」

一图胜千言

RenderNG 渲染流程

文章概要

  1. 前置知识简讲
  2. 浏览器架构
  3. 组件结构
  4. 代码分析

前置知识简讲

高屋建瓴的对「渲染流程」做一个归纳的话,其实它兼顾了四个「方向」

  1. 「页面内容」渲染成屏幕中的像素
  2. 处理页面中的「视觉效果」
  3. 处理页面滚动scroll
  4. 将输入事件input event有效地输送到正确的地方

❝每个「tab」中被渲染的页面内容是一个「树形结构」的数据格式(frame) 其中包含浏览器自带的UI(例如:输入栏、书签栏等) ❞

输入事件input event的事件流来源很多,例如触摸屏幕、键盘输入还有其他的硬件设备。

❝每一个frame结构包含: - DOM 数据信息 - CSS -画布信息Canvas - 其他资源,例如图片image/视频video/字体font/SVG等 ❞

「一个frame结构代表一个HTML文档(包含URL信息)」

被浏览器加载的 Web 应用,存在一个「顶层frame」,还有若干子frame。(如果有的话)。

「视觉效果」是一种应用于位图bitmap的图形操作,例如常规的滚动scroll/剪切clip/转换transform/过滤filter/透明处理opacity/混合blend

浏览器架构

渲染流程

渲染流程

可以将渲染流程想象成一个拥有很多关键节点的「流水线操作模式」。在每一个节点都会对来自上一个节点的“原料”进行深度加工,最终会将初始原料HTML文档渲染成屏幕中的图像信息。(每个关键节点中都会有自己特定的数据格式,这个我们会有一篇文章介绍)

❝渲染过程主要涉及到 1. 渲染进程中的主线程 2. 渲染进程中的合成线程 3. viz进程(也叫GPU进程) ❞

关键节点介绍

在渲染流程的图中,用不同颜色来标识该阶段可能会被不同的线程或者进程所执行。

颜色

所在进程/线程

绿色

渲染进程中的主线程

黄色 (黄色)

渲染进程中的合成线程

橘色

viz进程(也叫GPU进程)

在某些阶段,可能会被多个地方所执行,所以该阶段可能存在多个颜色。

然后,我们来简单介绍一下每个节点:

  1. 动画Animate:根据「声明性」的描述,随着时间的推移改变「计算样式」computed style或者修改「属性树」property tree,然后开启GPU的「硬件加速」 例如:使用一些CSS3的属性 1. transform 2. opacity 3. filter 4. will-change
代码语言:javascript
复制
.element {
    transform: rotateZ(360deg);
    transform: translate3d(0, 0, 0);
}
  1. 样式生成Style: 将CSS信息嵌入到DOM树中 👉 并且生成「计算样式」(computed style)
  2. 布局Layout: 决定屏幕中每个DOM元素的大小(size)和位置(position) 👉 并且生成「不可变fragment树」 (immutable fragment tree) 👉 「非可视化」的 DOM 元素不会插入布局树中 例如“head”元素/如果元素的 display 属性值为“none”,那么也不会显示在呈现树中(但是 visibility 属性值为“hidden”的元素仍会显示)
  3. 重绘Pre-paint: 计算「属性树」(property trees)并且「酌情」使任何现有的显示列表(display list)和GPU纹理瓦片失效。 👉 生成「属性树」(property trees)
  4. 滚动Scroll:通过修改属性树(上一阶段生成的数据信息),来更新文档或者可滚动元素的偏移量
  5. 绘制Paint: 计算显示列表(display list)用于描述如何从DOM中栅格化(raster) GPU纹理瓦片 👉 生成「显示列表」(display list)
  6. 提交阶段Commit: 将属性树和显示list 复制一份,并且打包到送到「合成线程」中 👆🏻 上面大部分在渲染进程的主线程中
  7. 图层化Layerize: 将显示列表分解成一个合成的图层列表(composited layer list),用于独立的栅格化(rasterization)和动画制作 👉关键数据 「图层列表」
  8. 栅格、解码和绘图工作单元:分别将「显示列表」「编码图像」「绘画工作单元代码」转化为GPU纹理 👉关键数据 「GPU纹理」
  9. 启动Activate:创建一个合成frame (compositor frame),代表如何绘制和定位GPU纹理到屏幕,以及相关的视觉效果 👆🏻 上面大部分在渲染进程的合成线程中

11.合成Aggregate:将「所有」可见合成frame的合成 frame 合并为一个「单一的、全局的」合成器frame。12. 绘制Draw:在GPU上执行聚合的合成frame,在屏幕上创建像素。

👆🏻 上面大部分在Viz进程中

在渲染流程中,有些阶段是可以被跳过的。例如:动画、滚动等可以跳过 布局、重绘、和绘制阶段。这也解释了在渲染流程图中动画(animate)/滚动(scroll)阶段存在两个颜色(绿色/黄色)。

❝如果针对某些可视化效果,能够跳过布局、重绘和绘制阶段,那么它们可以「直接」跳过渲染主线程然后运行在合成线程中。也就是我们常说的 「硬件加速」。 ❞

进程和线程

CPU 进程

多个CPU进程的使用实现了「站点之间和浏览器状态的性能和安全隔离」,以及与GPU硬件的稳定性和安全隔离。

进程关系

  • 「渲染进程(render process)」:对「网站」进行渲染、动画、滚动和数据输入的处理。 👉「存在多个渲染进程」
  • 「浏览器进程(browser process)」:对「浏览器UI」进行渲染、动画和数据输入的处理,并且负责将数据转发到正确的渲染进程。 👉 浏览器UI包含:地址栏、tab 名称和网站图标等。 👉「只有一个浏览器进程」
  • 「Viz 进程(Viz process」:聚合来自多个渲染进程和浏览器进程的数据信息。 👉它使用GPU进行光栅和绘制 👉「只有一个Viz 进程」

通常,tab和渲染进程是「一一对应」的,也就是说一个tab会有属于自己的渲染进程。但是,如果多个tabs之间属于「同一站点」并且 A 页面「打开」了 B页面。此时 A/B是「共用」一个渲染进程的。具体介绍,可以看之前写的文章。页面是如何生成的(宏观角度)

「整个Chromium中只存在一个Viz 进程」。毕竟,通常只有一个GPU和屏幕可供绘制。

由于浏览器可以有很多标签和窗口,而且都有浏览器UI像素需要绘制,你可能会问:为什么只有一个浏览器进程?

原因是:

「在同一时刻只有被唤起的页面才会占用浏览器进程」

事实上,「不可见的浏览器标签大多被停用,并丢掉所有的GPU内存」

线程

❝线程有助于实现「管道并行化」「多重缓冲」

渲染进程中的线程分类

  • 「主线程(Mian Thread)」: 1. 主要负责运行脚本 2. 管理「事件循环」event loop)、 3. 负责文档生命周期 4. 「脚本事件调度」 5. 「解析」HTML、CSS和其他数据格式
    • 「主线程辅助线程(helper)」:创建需要编码或解码的图像位图(bitmap)和二进制数据(Blob)
    • 「Web Works」:运行耗时脚本 Web Worker
  • 「合成线程(Compositor Thread)」: 1. 处理事件的输入 2. 优化页面的内容的滚动和动画效果 3. 对页面内容进行「图层化」处理 3.对图片进行解码处理 4. 绘画工作单元代码 5. 进行栅格化操作。
    • 「合成线程辅助线程(helper)」:协助Viz的光栅任务,并执行图像解码任务、绘制工作程序
  • 「媒体、音频输出线程」:对视频和音频流进行同步解码 👉「视频线程与主渲染管道并行执行」

将主线程和合成器线程分开,对于将动画和滚动与主线程工作的性能隔离至关重要。

「每个渲染进程只有一个主线程」,即使同一网站的多个标签或frame可能最终出现在同一进程中。然而,在各种浏览器API中执行的工作是有性能隔离的。例如,Canvas API中图像位图和Blobs的生成在一个主线程辅助线程中运行。

同样地,「每个渲染进程只有一个合成器线程」。一般来说,只有一个并不是问题,因为合成器线程上所有「真正昂贵的操作」都被委托给合成器工作线程或Viz进程,而且这些工作可以与输入路由、滚动或动画「并行进行」

「合成器工作线程的数量取决于设备的能力」。例如,台式机一般会使用更多的线程,因为它们有更多的CPU内核,而且比移动设备的电池限制要少。

❝渲染过程的「线程架构」是三种不同优化模式的应用。 - 「辅助线程」:将「耗时任务」的子任务发送给其他线程,以保持父线程对同时发生的其他请求的响应。主线程的辅助线程和合成器的辅助线程是这种技术的好例子。 - 「多重缓冲」:在渲染新内容的同时显示以前渲染的内容,以「隐藏渲染的延迟」。合成器线程使用这种技术。同样的我们在页面是如何生成的(宏观角度)中的双缓存中介绍过此类技术细节。 - 「管线并行化」:在多个地方「同时运行」渲染管线。这就是为什么滚动和动画可以很快,即使主线程的渲染更新正在发生,因为滚动和动画可以并行运行。 ❞


浏览器进程

浏览器进程架构

  • 「渲染和合成线程」:响应浏览器用户界面中的输入,将其他输入「导航」到正确的渲染组件中,并且对浏览器UI进行排版和绘制
  • 「渲染和合成辅助线程」:执行「图像」解码任务或解码任务。

❝浏览器进程的渲染和合成线程与渲染进程的代码和功能类似,只是主线程和合成器线程被合并为一个 ❞

Viz 进程

Viz 进程架构

  • 「GPU主线程」: 将显示列表(display list)和视频帧「光栅化」为GPU纹理,并将合成线程生成的若干frame合并成一个并绘制到屏幕上。
  • 「显示合成器线程」:聚合并优化来自「每个渲染进程」的合成信息,加上浏览器进程,形成一个单一的合成器frame,以便向屏幕展示。

「栅格化」「页面绘制」通常发生在同一个线程上,因为它们都依赖于GPU资源,而且很难可靠地多线程使用GPU。也就是它们都位于GPU主线程。

显示合成器是在一个不同的线程上,因为它需要在「任何时候」都有反应,并且不阻塞任何可能导致GPU主线程变慢的来源。导致GPU主线程速度变慢的一个原因是对非Chromium代码的调用,例如供应商特定的GPU驱动程序,这些代码可能以难以预测的方式变慢。

组件结构

在每个渲染过程主线程或合成器线程中,都有一些「逻辑组件」,它们以结构化的方式相互作用。

渲染进程主线程中的组件结构

  • Blink 渲染器Blink renderer:
    • 本地frame树local frame tree:代表本地frame树和frame内的DOM
    • 「DOM /Canvas API」:包含所有这些API的实现
    • 文档生命周期运行器document lifecycle runner:执行渲染操作,一直到提交阶段(commit)
    • 输入事件探测input event hit testing:执行「命中测试」以找出事件所针对的DOM元素,并运行事件调度算法和默认行为
  • 渲染事件循环调度器和运行器rendering event loop scheduler and runner:决定在事件循环中运行什么,什么时候运行。它以与设备显示相匹配的节奏来安排渲染的发生。

local frame tree

frame树是指主页面和它的子iframe,是从主页面递归生成的。

❝如果一个frame是在一个渲染进程中渲染的,那么它就是该进程的「本地框架」,否则就是「远程框架」。 ❞

根据帧的渲染过程为其着色。在前面的图片中,绿色的圆圈是一个渲染过程中的所有帧;红色的是第二个,而蓝色的是第三个。

「一个局部框架树local frame tree是框架树中相同颜色的连接组件」。图片中共有四个局部框架树:两个用于站点A,一个用于站点B,一个用于站点C,

「每个局部框架树local frame tree都有自己的Blink渲染器组件」

一个局部框架树的Blink渲染器可能与其他局部框架树处于同一渲染过程中,也可能不在同一渲染过程中。

渲染进程合成线程中的组件结构

  • 数据处理器data handler:维护一个合成的图层列表layer list、显示列表display lists和属性树property tree
  • 生命周期运行器lifecycle runner:运行渲染管道的动画animate、滚动scroll、合成composite、光栅化raster、解码decode和激活activate步骤 👉「动画和滚动可以在主线程和合成线程中发生」
  • 输入和命中测试处理程序input and hit test handler:在合成线程下执行「输入处理」「命中测试」,以确定滚动手势是否可以在合成器线程上运行,以及命中测试应该针对哪个渲染过程。

代码分析

存在如下结构的文档信息。包含三个标签页(foo/bar/baz)。

Tab1:foo.com

代码语言:javascript
复制
<html>
  <iframe id=one src="foo.com/other-url"></iframe>
  <iframe id=two src="bar.com"></iframe>
</html>

foo.com引用了自身文档中的子页面 和另外一个标签页(bar.com)

Tab2:bar.com

代码语言:javascript
复制
<html>
 …
</html>

Tab3:baz.com

代码语言:javascript
复制
<html>
 …
</html>

标签页对应的进程、线程和组件结构关系

在文章开头讲过,「渲染流程」兼顾了四个「方向」

  1. 「页面内容」渲染成屏幕中的像素
  2. 处理页面中的「视觉效果」
  3. 处理页面滚动(Scroll)
  4. 将输入事件(input event)有效地输送到正确的地方

然后我们通过几个例子来讲解一下,它们是如何实现的。

修改主frame中的DOM

❝该过程涉及多个iframe 1. 主frame => foo.com 2. bar.com 所以涉及到了多个合成帧的融合处理 ❞

  1. foo.com页面渲染过程中,主线程Main Thread中某个脚本script修改了部分DOM结构
  2. Blink 渲染器 告诉 合成器compositor 它需要开始渲染操作
  3. 合成器compositor 告诉Viz它需要进行渲染
  4. Viz将渲染的「开始信号」传回给合成器。
  5. 合成器将启动信号继续「向前转发」给Blink渲染器
  6. 「主线程」事件循环运行器event loop runner启动指定文档的生命周期方法
  7. 主线程将「第6步运行结果」发送给合成器线程 ==> 表明DOM的变更处理已经在主线程处理完
  8. 「合成线程」事件循环运行器event loop runner启动「对应」合成的生命周期
  9. 如果存在光栅任务raster tasks都被送到Viz进程进行光栅处理
  10. Viz在GPU中对内容进行光栅化处理
  11. Viz 将内容光栅完成后,将结果返回给合成器
  12. 一个合成帧compositor frame被送往Viz显示合成器线程
  13. Viz 为foo.combar.com和浏览器UI的渲染帧「合并」成一个合成帧
  14. Viz为绘制该合成帧做安排schedules
  15. Viz将合成帧绘制到「屏幕上」

处理页面中视觉效果

❝只涉及到一个页面:即 bar.com

  1. bar.com渲染过程的合成器线程在其事件循环运行器event loop runner中通过「突变」现有的属性树property trees来触发一个动画。然后「重新运行」合成器的生命周期。
  2. 一个合成帧compositor frame被送往Viz显示合成器线程
  3. Viz 为bar.com和浏览器UI的渲染帧「合并」成一个合成帧
  4. Viz为绘制该合成帧做安排schedules
  5. Viz将合成帧绘制到「屏幕上」

处理页面滚动scroll

❝只涉及到一个页面:即 baz.com

  1. 在 浏览器进程browser process产生一系列的输入事件(鼠标、触摸或键盘)
  2. 「每个」事件都被「传送」baz.com的渲染进程合成器线程compositor thread
  3. 合成器决定是否向主线程「转发」事件信息
  4. 如果满足条件,将事件转发给渲染进程主线程main thread
  5. 主线程调用特定的输入事件监听器(pointerdown、touchstar、pointermove、touchmove或wheel),看监听器是否会在事件上调用preventDefault
  6. 主线程根据事件中是否调用preventDefault来决定将事件返回给合成器
  7. 如果没有调用,将输入事件「回退」给浏览器进程
  8. 浏览器进程browser process通过将其与其他近期其他事件event结合起来,将其转换为滚动手势scroll gesture
  9. 滚动手势scroll gesture再次被传送到的渲染进程合成器线程compositor thread
  10. 滚动信息在这里起作用,并且bar.com渲染进程的「合成器线程」在其合成器事件循环compositor event loop中触发了一个动画。然后在属性树property tree中改变滚动偏移,并「重新运行」合成器生命周期。它还告诉主线程启动一个滚动事件
  11. 一个合成帧compositor frame被送往Viz显示合成器线程
  12. Viz 为baz.com和浏览器UI的渲染帧「合并」成一个合成帧
  13. Viz为绘制该合成帧做安排schedules
  14. Viz将合成帧绘制到「屏幕上」

处理输入事件input event

❝在bar.com中执行click事件 ❞

  1. 在浏览器进程browser process中产生了一个输入事件(鼠标、触摸或键盘)。它执行了命中测试hit test,以确定bar.com 对应的渲染进程应该接收该点击事件,并将其发送到那里
  2. bar.com的合成器线程compositor thread将点击事件导航到bar.com的渲染主线程main thread,并安排一个合成器事件循环compositor event loop任务来处理它
  3. bar.com的主线程的输入事件探测input event hit testing通过测试来确定iframe中的哪个DOM元素被点击,并「唤起」一个点击事件供脚本观察。
  4. 后续的操作就和修改DOM的后续操作一样了。

后记

「分享是一种态度」,这篇文章,是一篇译文,算是一个自我学习过程中的一种记录和总结。主要是把自己认为重要的点,都罗列出来。同时,也是为大家节省一下「排雷和踩坑的时间」。当然,可能由于自己认知能力所限,有些点,没能表达很好。

参考资料:

  1. 原文地址 需要🪜
  2. [How Browsers Work](https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/)
  3. Inside look at modern web browser(https://developer.chrome.com/blog/inside-browser-part1/)
  4. Life of a Pixel(https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.ga884fe665f_64_6)
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端柒八九 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 简明扼要
  • 一图胜千言
  • 文章概要
  • 前置知识简讲
  • 浏览器架构
    • 渲染流程
      • 关键节点介绍
      • 进程和线程
        • CPU 进程
          • 线程
        • 浏览器进程
          • Viz 进程
          • 组件结构
            • 渲染进程主线程中的组件结构
              • 渲染进程合成线程中的组件结构
                • Tab1:foo.com
                • Tab2:bar.com
                • Tab3:baz.com
            • 代码分析
              • 修改主frame中的DOM
                • 处理页面中视觉效果
                  • 处理页面滚动scroll
                    • 处理输入事件input event
                    • 后记
                    相关产品与服务
                    GPU 云服务器
                    GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档