当前位置: 代码迷 >> 综合 >> [译] Chromium 下一代渲染架构- RenderingNG 架构
  详细解决方案

[译] Chromium 下一代渲染架构- RenderingNG 架构

热度:69   发布时间:2023-12-10 22:58:01.0

我是 Chris Harrelson,是负责 Blink 渲染的工程主管(渲染是指将 HTML 和 CSS 转换为像素)。八年多来,我一直深入研究 Web 渲染性能,我的个人目标是让 Web 能够更快、更轻松、更可靠地提供出色的用户体验。我很高兴地告诉你,我们这八年来构建了一个新的、尖端的 Chromium 渲染引擎架构。实现这一目标是一项巨大的工程,我希望你喜欢它!

2021 年,我们将基本完成该架构的设计、构建和交付。我们称它为 RenderingNG,它是真正的下一代渲染架构,其性能大大优于以前的架构。RenderingNG 已经进行了至少八年,它代表了 Chromium 开发人员的集体工作成果。它为下一代快速、流畅、可靠、响应迅速和交互式的 Web 释放了巨大的潜力。我相信它也是作为一个基线,定义了开发人员可以依赖的 Web 渲染引擎的新的最低标准。

6c012707796013496883eb2e68781ced.png

这篇文章是系列文章中的第一篇,我将解释我们构建了什么、为什么构建它以及它是如何工作的。我会从以下几个方面展开:

  • 北极星目标

  • 成功金字塔:指导我们工作的原则,以及这些原则在实践中的例子

  • RenderingNG 支持的特性和功能

  • RenderingNG 主要项目的顶层概述

北极星目标

RenderingNG 的北极星目标是:浏览器引擎的实现及其大量的渲染 API 不能成为 Web 用户体验的限制因素。 你不应该担心浏览器 Bug 会导致浏览器功能不可靠或破坏页面的渲染。

应该没有奇奇怪怪的性能问题。而且,你不需要因为内置功能的缺失做一些 Work Around。

浏览器应该能够保持正常工作。

我相信 RenderingNG 是朝着这个北极星目标迈出的一大步。在 RenderingNG 之前,我们虽然也添加了一些渲染功能并提高了性能,但费了很大力气才让这些功能对开发人员可靠,并且仍然存在许多性能问题。现在我们有了一个新架构,可以系统地解决这些问题,并且还可以支持以前认为不可行的高级功能。RenderingNG 有如下特点:

  • 具有跨不同平台、设备和操作系统的坚如磐石的核心功能。

  • 具有可预测的、可靠的性能。

  • 能最大限度地利用硬件能力(内核、GPU、屏幕分辨率、刷新率、底层栅格化 API)。

  • 仅执行显示可见内容所需的工作。

  • 内置支持常见的视觉设计、动画和交互设计模式。

  • 为开发者提供 API 以轻松管理渲染成本。

  • 为开发者插件提供渲染流水线扩展点。

  • 优化所有内容 —— HTML、CSS、2D Canvas、3D Canvas、图像、视频和字体。

与其他浏览器渲染引擎的比较

Gecko 和 Webkit 也实现了文章中描述的大部分的架构特性,在某些情况下甚至在 Chromium 之前就添加了它们。这非常棒!任何一种浏览器变得更快和更可靠都是值得庆祝并且具有实际影响的,但最终目标是推进所有浏览器的基线,以便开发人员可以依赖它。

成功金字塔

我的理念是,成功首先要实现可靠性,然后是可伸缩性能,最后是可扩展性。

788f987b0d8e7c0e61aa0fd7acc1a2fd.png

与现实生活中的金字塔一样,每一层都为上一层提供了必要的坚实基础。

可靠性

99e2e9ec3f5fccc75cc4d25a0543bb27.png

如果要实现丰富而复杂的用户体验,我们首先需要的是一个坚如磐石的平台。核心功能和基础必须正常工作,并随着时间的推移继续工作。同样重要的是这些功能组合良好并且没有奇怪的 Edge-Case 或 Bug。

0dfebd81be2b86596881f659fcd5023d.png

因此,可靠性是 RenderingNG 中最重要的一个部分。可靠性是由良好的测试、质量反馈循环、指标和软件设计模式带来的结果。

为了说明可靠性的重要性,我们在过去八年的大部分时间里都只关注这一个部分。首先,我们对系统建立了深入的了解 —— 从缺陷报告中学习并修复它们,执行全面测试,理解网站的性能需求和 Chromium 性能的限制。然后,我们仔细地逐步设计并推出了关键的设计模式和数据结构。直到那时我们才说为下一代的响应式设计、渲染的可伸缩性能和可扩展性准备好了。

94f9ef06dac3fe7b620ab4e910a0271a.png

这并不是说 Chromium 在那段时间没有任何改进。事实上,情况正好相反!这些年来,随着我们逐步重构和推出每项改进,可靠性和性能稳步持续提高。

测试和指标

在过去的 8 年中,我们增加了数以万计的单元测试、性能测试和集成测试。此外,我们还开发了全面的指标,用来衡量 Chromium 渲染在本地测试、性能基准测试以及真实站点、真实用户和设备在真实环境中的表现。

但是,无论 RenderingNG(或其他浏览器的渲染引擎)多么出色,如果存在大量错误或浏览器之间的行为差异,那么为 Web 开发仍然不容易。为了解决这个问题,我们还最大限度地利用了 Web Platform Tests。这些测试中的每一个都验证了所有浏览器都应该通过的 Web 平台的使用模式。我们还密切监控随着时间推移通过更多测试和提高核心兼容性的指标。

Web Platform Tests 是一项大家共同协作的工作。例如,Chromium 工程师只为 CSS 的特性添加了大约 10% 的测试;其他浏览器提供商、独立贡献者和规范作者贡献了其余部分。建立相互兼容的 Web 需要大家一起努力!

38904e676090d0b3077459f7be6a73b9.png

好的软件设计模式

如果代码易于理解并且设计方式能够最大限度地减少错误的可能性,那么可靠地交付高质量的软件就会容易得多。在后续的文章中,我们将详细介绍 RenderingNG 的软件设计。

可伸缩性能

在速度、内存和功耗方面实现出色的性能是 RenderingNG 下一个最重要的方面。我们希望与所有网站的交互顺畅且响应迅速,但又不牺牲设备的稳定性。

但我们不仅想要性能,我们还想要可伸缩的性能 —— 一种在低端和高端机器上以及跨操作系统平台都能可靠运行的架构。我将其称为 Scaling Up —— 利用硬件设备的所有能力,和 Scaling Down —— 最大化效率并在合适的时候减少对系统的要求。

44a507aaa07078c916535386ffda4b10.png

为了实现这一目标,我们需要最大限度地利用缓存、性能隔离和 GPU 硬件加速,接下来我们一个个来看。为了更具体,我们看一下在网页滚动这个非常重要的交互场景下,这三种手段分别是如何做出贡献的。

缓存

在动态的交互式 UI 平台(例如 Web)中,缓存是显著提高性能的最重要的方法。浏览器中最著名的缓存类型是 HTTP 缓存,但渲染也有许多缓存。滚动场景下最重要的缓存是 GPU 纹理和显示列表的缓存,它们允许滚动非常快,同时最大限度地减少电池消耗并在各种设备上运行良好。

缓存有助于提高电池寿命和动画帧率,但更重要的是它提供了与主线程性能隔离的可能性。

性能隔离

在现代台式计算机上,你永远不必担心后台应用程序会降低你正在工作的应用程序的速度。这是因为抢先式多任务处理,这反过来又是一种性能隔离形式:确保独立任务不会相互拖慢。

在 Web 上,性能隔离的最佳示例是滚动。即使在有很多 JavaScript 慢任务的网站上,滚动也可以非常流畅,因为它运行在不同的线程上,而不必依赖于 JavaScript 和布局线程。我们在 RenderingNG 上投入了大量精力,以确保每个可能的滚动都是线程化的。缓存不止可以优化显示列表,还可以处理更复杂的情况。我们的示例中包括了 fixed 和 sticky 定位的元素、被动事件侦听器和高质量文本渲染。

496b4bc260508dfec0b98259d9e053a4.png

GPU 加速

GPU 使生成像素和绘制到屏幕的速度显着加快 —— 在许多情况下,每个像素都可以与其他像素并行绘制,从而大大提高了速度。RenderingNG 的一个关键组件是使用 GPU 栅格化和绘制。使用所有平台和所有设备上的 GPU 来加速 Web 内容的渲染和动画。这在低端设备或非常高端的设备上尤其重要,因为它们通常拥有比设备的其他部分更强大的 GPU。

cf283b0155f59293f6594ce2c734a93a.png

可扩展性:合适的工具

一旦我们拥有了可靠性和可伸缩的性能,我们就可以构建大量工具来帮助开发人员扩展 HTML、CSS 和 Canvas 的能力,并且不会牺牲任何来之不易的性能和可靠性。

这些工具包括用于响应式设计、渐进式渲染、平滑度和响应性以及线程渲染的高级用例的内置 API 和 JavaScript 公开 API。

下面这些由 Chromium 支持的 Web API 是由 RenderingNG 实现的,在 RenderingNG 以前被认为是不可行的。 所有这些都是根据开放的规范和与开放的 Web 合作伙伴(其他浏览器的工程师、专家和 Web 开发者)合作开发的。在后续的文章中,我们将深入探讨其中的每一个,并解释 RenderingNG 如何使它们成为可能。

  • content-visibility:允许站点轻松避免渲染屏幕外的内容,并缓存当前未显示的单页应用程序视图的渲染。

  • OffscreenCanvas:允许 Canvas(2D Canvas API 和 WebGL)的渲染在自己的线程上运行,以获得可靠的出色性能。这个项目也是 Web 的另一个重要里程碑 —— 它是第一个允许 JavaScript(或 WebAssembly!)从多个线程呈现单个网页文档的 Web API。

  • Container queries:允许单个组件以响应方式自行布局,从而让即插即用的组件成为可能(目前是实验性的)。

  • Origin isolation:允许网站在 iframe 之间选择更多的性能隔离。

  • Off-main-thread paint worklets:为开发人员提供了一种扩展元素绘制方式的方法,让代码在合成器线程上运行。

除了提供 Web API 之外,RenderingNG 还允许我们发布几个非常重要的“自动功能”,这些功能对所有网站都有好处:

  • Site Isolation:将跨域 iframe 放在不同的 CPU 进程中,以获得更好的安全性和性能隔离。

  • Vulkan、D3D12 和 Metal:比 OpenGL 更有效地使用 GPU 的底层 API。

  • 更多合成动画:SVG,背景颜色。

另外,我们对 RenderingNG 即将推出的功能感到兴奋,包括:

  • Scroll 动画

  • 隐藏但可搜索和可访问的 DOM

  • 共享元素转换

  • 自定义布局

  • 非主线程合成;解耦线程和合成

构成 RenderingNG 的关键项目

以下是 RenderingNG 中的关键项目列表。随后的文章将深入探讨其中的每一个。

CompositeAfterPaint

将合成从样式、布局和绘画中解耦开,从而大大提高了可靠性和可预测的性能,增加了吞吐量,并在不牺牲性能的情况下使用更少的内存。它始于 2014 年,并将于今年(2021年)结束。

c43cdf44469f63dcc2aa2f28d8459247.png

LayoutNG

彻底重写所有布局算法,大大提高了可靠性和更可预测的性能。它始于2016年,计划于今年(2021年)完成。

8ee9b1015198829b2d04a09ac36cbc91.png

BlinkNG

将 Blink 渲染引擎进行系统的清理,并重构为干净的分离的多个流水线阶段。这允许更好的缓存、更高的可靠性以及可重入或延迟渲染的功能,例如 Content-Visibility 和 Container Queries 。它始于 2014 年,并逐步改进并一直持续。它将在 2021 年完成。

无处不在的 GPU 加速

我们为了在所有平台上使用 GPU 栅格化、绘图和动画,做了长期的努力。GPU 为大多数内容提供了巨大的加速,因为每个像素都可以并行处理。这也是提高低端设备性能的有效方法,这些设备往往有 GPU。它始于 2014 年,并于 2020 年完成。

048f85d21333a61c228afa8328a2dc2c.png

线程化滚动、动画和解码

将所有滚动、非布局动画和图像解码移出主线程的长期努力。它始于 2011 年,并且一直在进行中。

4e1343e08eb29ed5e0714ce58e7b2aa5.png

Viz

Viz 是 Chromium 内中心化的负责栅格化和绘图的进程,可以提高吞吐量、优化内存并允许更有效地使用硬件能力。它还具有其他对 Web 开发者来说不太明显但对用户来说非常明显的好处,例如站点隔离和将渲染流水线与浏览器 UI 渲染解耦。它于 2016 年开始,将于 2021 年完成。

e81cc6d6f57b0f9811189244e2365271.png

上表中的术语定义:

  • OOP-D(Out Of Process Display compositor) 进程外显示合成。显示的合成与操作系统中合成是同一种活动。进程外意味着在 Viz 进程中执行,而不是在网页的渲染进程或浏览器 UI 进程中执行。

  • OOP-R(Out Of Process Raster) 进程外栅格化。栅格化就是将显示列表转换为像素。进程外意味着在 Viz 进程而不是网页的渲染进程中执行。

  • SkiaRenderer 一种新的显示合成器实现,可以支持在一系列不同的底层 GPU API(例如 Vulkan、D3D12 或 Metal)上执行。

线程化和加速 Canvas 渲染

这个项目使 OffscreenCanvas 成为可能。它始于 2015 年,将于 2021 年结束。

475b409a6d5e935a05184484b42ec351.png

VideoNG

该项目长期致力于在网络上提供高效、可靠和高质量的视频播放。

eebb4bb6638fa3d8669d10efd921c5ac.png

RenderingNG 架构概述

下面这部分将解释 RenderingNG 的各部件是如何设置的,渲染流水线是如何使用这些部件的。

自顶向下地看,渲染的任务是:

  • 将内容 渲染 为屏幕上的像素。

  • 用 动画 将内容的视觉效果从一种状态变为另一种状态。

  • 滚动 以响应输入。

  • 将输入有效地 路由 到正确的位置,以便 Scripts 和其他子系统可以响应。

要呈现的内容是每个 Tab 的框架树和浏览器 UI。以及,来自触摸屏、鼠标、键盘和其他硬件设备的原始输入事件流。

每个框架包括:

  • DOM 状态

  • CSS

  • 画布

  • 外部资源,例如图像、视频、字体和 SVG

框架是一个 HTML 文档,加上它的 URL。在 Tab 中加载的网页具有顶级框架,顶级框架中包含每个 iframe 子框架以及每个 iframe 的递归 iframe。

视觉效果是对位图的图形操作,这些图形操作比如:滚动、变换、剪辑、过滤、不透明度或这些的混合使用。

架构组件

在 RenderingNG 中,这些任务在逻辑上被分成几个阶段和几个组件。这些组件存在于各种 CPU 进程、线程和这些线程中的子组件中。每个组件都非常重要,它们在为 Web 内容实现可靠性、可伸缩性能和可扩展性方面发挥着重要作用。

渲染流水线的结构

2dd6c27540826f46d80df140d89bc3b1.png

渲染在一个流水线中向前推进,并在过程中创建了许多工件。这个流水线有许多个阶段,每个 阶段 代表在渲染中执行一项明确定义的任务。工件 是作为每个阶段输入或输出的数据结构;图中的输入或输出用箭头表示。

这篇文章不会详细介绍这些工件。这将在下一篇文章中讨论:关键数据结构及其在 RenderingNG 中的作用。

流水线阶段

在上图中,阶段用颜色表示,指示它们在哪个线程或进程中执行:

  • 绿色: 渲染进程的主线程

  • 黄色: 渲染进程的合成器线程

  • 橙色: Viz 进程(一个中心化的负责栅格化和绘图的进程)

在某些情况下,它们可以在不同的地方执行,这就是为什么有些阶段有两种颜色。

这些阶段分别是:

  • Animate: 根据声明的时间线改变 Computed Styles 和 属性树 。

  • Style: 将 CSS 应用于 DOM,并创建 Computed Styles。

  • Layout: 确定屏幕上 DOM 元素的大小和位置,并创建 immutable fragment tree。

  • Pre-paint: 计算属性树,并酌情使任何现有的 显示列表 和 GPU 纹理图块 无效。

  • Scroll: 通过改变属性树来更新文档和可滚动 DOM 元素的滚动偏移量。

  • Paint: 计算一个显示列表,描述如何从 DOM 中栅格化 GPU 纹理图块。

  • Commit: 将属性树和显示列表复制到合成器线程。

  • Layerize: 将显示列表分解为 复合层列表,用于独立的栅格化和动画。

  • Raster、decode 和 paint worklets: 将显示列表、编码图像和绘制工作集代码分别转换为 GPU 纹理图块。

  • Activate: 创建一个 合成器框架,表示如何在屏幕上绘制和定位 GPU 图块,以及任何视觉效果。

  • Aggregate: 将所有可见合成器帧中的合成器帧组合成一个单一的全局合成器帧。

  • Draw: 在 GPU 上执行聚合合成器帧以在屏幕上创建像素。

如果某次渲染不需要渲染流水线的某个阶段,是可以跳过的。例如,视觉效果和滚动的动画可以跳过布局、预绘制和绘制。这就是为什么动画和滚动在图中用黄色和绿色点标记的原因。如果布局、预绘制和绘制可以跳过视觉效果,它们可以完全在合成器线程上运行并跳过主线程。

浏览器 UI 渲染在这里没有直接描述,但可以被认为是同一流水线的简化版本(实际上它的实现共享大部分代码)。视频(也未直接描述)通常通过独立代码进行渲染,该代码将帧解码为 GPU 纹理图块,然后插入合成器帧和绘制步骤。

进程和线程结构

Chromium 架构中的 CPU 进程和线程比此处显示的要多。这些图表仅关注那些对渲染至关重要的图表。

CPU 进程

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

d4f2d2b8c98bfdb6361a51a25e537070.png
  • Render Process(渲染进程)为单个站点和 Tab 完成渲染、动画、滚动和路由输入。渲染进程可以有多个。

  • Browser Process(浏览器进程)为浏览器 UI 完成渲染、动画和路由输入(包括 URL 栏、Tab 标题和图标),并将所有剩余的输入路由到合适的渲染进程。只有一个浏览器进程。

  • Viz Process (Viz 进程)聚合来自多个渲染进程和浏览器进程的合成。它使用 GPU 进行栅格化和绘图。只有一个 Viz 进程。

不同的站点有不同的渲染进程。(实际上,在桌面设备上总是如此,在移动设备上是尽可能。我会在下面写“总是”,但这个警告始终适用。)

同一站点的多个 Tab 或窗口通常有不同的渲染进程,除非它们是相关的(一个打开另一个)。如果桌面设备内存不足,Chromium 可能会将来自同一站点的多个 Tab 放入同一个渲染进程中,即使它们不相关。

在单个 Tab 中,来自不同站点的 frames 始终处于彼此不同的渲染进程中,但来自同一站点的 frames 始终处于相同的渲染进程中。从渲染的角度来看,多渲染进程的重要优势在于跨站 iframe 和 Tab 实现了性能隔离。此外,origins 可以选择更加隔离。

所有 Chromium 都只有一个 Viz 进程。毕竟,通常只有一个 GPU 和一个屏幕可供绘制。面对 GPU 驱动程序或硬件中的错误,将 Viz 分离到自己的进程中有助于提高稳定性。它也有利于安全隔离,这对于像 Vulkan 这样的 GPU API 很重要。一般来说,这对于安全性也很重要。

既然浏览器可以有很多 Tab 和窗口,而且它们都有要绘制的浏览器 UI 像素,你可能想知道:为什么只有一个浏览器进程?原因是同时只有一个 Tab 获得焦点;事实上,不可见的浏览器 Tab 大多被停用并丢弃所有 GPU 内存。然而,复杂的浏览器 UI 渲染功能也越来越多地在渲染进程中实现(称为 WebUI)。这不是出于性能隔离的原因,而是为了利用 Chromium 的 Web 渲染引擎的易用性。

在较旧的 Android 设备上,当在 WebView 中使用时,渲染和浏览器进程是共享的(这通常不适用于 Android 上的 Chromium,仅适用于 WebView)。在 WebView 上,浏览器进程也是和 embedding app 共享的,WebView 只有一个渲染进程。

有时还有用于解码受保护视频内容的实用程序。上面没有描述这个过程。

线程

线程有助于实现性能隔离和提升响应能力,不管是长任务、流水线并行化,还是多重缓冲,线程都能帮上忙。

f65f7d612ccf7250829e499134904ea4.png
  • Main Thread(主线程)运行脚本、渲染的事件循环、文档生命周期、命中检查、脚本事件调度、HTML、CSS 等数据的解析。

Main Thread Helpers 执行任务,例如创建需要编码或解码的图像位图和 blobs。

Web Workers 运行脚本和 OffscreenCanvas 的渲染事件循环。

  • Compositor Thread(合成器线程)处理输入事件,执行网页内容的滚动和动画,计算网页内容的最佳分层,并协调图像解码、绘制工作集和栅格化任务。

Compositor Thread Helpers 协调 Viz 栅格化任务,并执行图像解码任务、绘制工作集和备用栅格化。

  • Media、Demuxer & Audio output Thread 解码、处理和同步视频和音频流。(请记住,视频与主渲染流水线并行执行。)

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

每个渲染进程只有一个主线程,即使来自同一站点的多个 Tab 或框架可能最终在同一个进程中。但是,在各种浏览器 API 中执行的工作存在性能隔离。例如,Canvas API 中图像位图和 blob 的生成在主线程辅助线程中运行。

同样,每个渲染进程只有一个合成器线程。只有一个通常不是问题,因为合成器线程上所有真正昂贵的操作都委托给合成器工作线程或 Viz 进程,并且这项工作可以与输入路由、滚动或动画并行完成。合成器工作线程协调在 Viz 进程中运行的任务,但由于 Chromium 无法控制的原因(例如驱动程序错误),任何地方的 GPU 加速都可能失败。在这些情况下,工作线程将以备用模式在 CPU 上完成工作。

合成器工作线程的数量取决于设备的功能。例如,台式机通常会使用更多线程,因为它们具有更多的 CPU 内核,并且比移动设备更少受电池限制。这是可伸缩的示例。

值得注意的是,渲染进程线程架构是三种不同优化模式的应用:

  • 辅助线程 : 将长时间运行的子任务发送到其他线程,以保持父线程响应同时发生的其他请求。主线程助手和合成器助手线程就是这种技术的好例子。

  • 多重缓冲 : 在渲染新内容的同时显示之前渲染的内容,以隐藏渲染的延迟。合成器线程使用这种技术。

  • 流水线并行化 : 同时在多个地方运行渲染流水线。这就是滚动和动画可以快速的方式,即使正在发生主线程渲染更新,因为滚动和动画可以并行运行。

浏览器进程

af43ecacd591175bbc496177e619958b.png
  • Render & Compositing Thread 响应浏览器 UI 中的输入,将其他输入路由到正确的渲染进程;布局和绘制浏览器 UI。

  • Render & Compositing Thread Helper 执行图像解码任务和备用栅格化或解码。

浏览器进程的渲染和合成线程类似于渲染进程的代码和功能,只是主线程和合成线程合二为一。在这种情况下只需要一个线程,因为主线程没有长任务,不需要进行性能隔离。

Viz 进程

7cdb15da7737099c1ee056ee4de7fcc0.png
  • GPU Main Thread 将显示列表和视频帧栅格化到 GPU 纹理图块中,并将合成器帧绘制到屏幕上。

  • Display Compositor Thread 将来自每个渲染进程以及浏览器进程的合成内容聚合并优化到单个合成器帧中,以呈现到屏幕上。

Raster 和 Draw 通常发生在同一个线程上,因为它们都依赖于 GPU 资源,并且很难可靠地使多线程来使用 GPU(更容易地多线程访问 GPU 是开发新 Vulkan 标准的动机之一)。在 Android WebView 上,由于 WebViews 是嵌入到 Native App 中的,因此有一个单独的 OS 级别的渲染线程用于绘图。其他平台将来可能会有这样的线程。

显示合成器位于不同的线程上,因为它需要始终响应,并且不会阻塞 GPU 主线程上任何可能的减速源。GPU 主线程速度变慢的原因之一是调用非 Chromium 代码,例如供应商特定的 GPU 驱动程序,这可能会以难以预测的方式变慢。

组件结构

在每个渲染进程主线程或合成器线程中,都有软件组件以结构化方式相互交互。

渲染进程主线程组件

9147df062b6c8be65777b9a67ec9d499.png
  • Blink renderer:

local frame tree fragment 代表本地 frame 树和 frame 内的 DOM 。

DOM & Canvas API 组件包含所有这些 API 的实现。

document lifecycle runner 执行渲染流水线步骤,执行到 commit 这一步,包括 commit。

input event hit testing and dispatching 组件执行命中测试以找出事件所针对的 DOM 元素,并运行输入事件分派算法和默认行为。

  • rendering event loop scheduler and runner 决定在事件循环中运行什么,以及何时运行。它以与显示设备匹配的节奏安排渲染。

6bd2908d2be6d2a97938b4e96ea236fc.png

Local frame tree fragments 有点复杂。回想一下,框架树是主页面及其子 iframes、子 iframes 的子 iframes,一直递归下去。对渲染进程来说,如果一个 frame 在该进程中渲染,则它是本地的,否则它是远程的。 你可以想象根据渲染进程为 frame 着色。在上图中,绿色圆圈是在同一个渲染进程中渲染的 frame;红色的、蓝色的同理。

一个 Local frame tree fragment 是指 frame 树中既相互连接又有相同颜色的部分。上图中有四个 local frame trees:站点 A 两个,站点 B 一个,站点 C 一个。每个 local frame tree 都有自己的 Blink 渲染器。Local frame tree 的 Blink 渲染器可能与其他 local frame tree 在同一渲染进程中,也可能不在同一渲染进程中(如前所述,它由渲染进程的选择方式决定)。

渲染进程合成器线程结构

ca30d00795e7cecdd97ba626f8eaf6f1.png

渲染进程合成器组件包括:

  • 一个 data handler,维护合成层列表、显示列表和属性树 。

  • 一个 lifecycle runner,运行渲染流水线的动画、滚动、合成、栅格化以及解码和激活的步骤。(请记住,动画和滚动可以在主线程和合成器线程中发生。)

  • 一个 input and hit test handler 执行输入处理和命中测试,以确定滚动是否可以在合成器线程上运行,以及应该针对哪个渲染进程进行命中测试。

实践中的一个例子

现在让我们通过一个例子来具体说明这个架构。在此示例中,有三个 Tab:

Tab 1: foo.com
<html><iframe id=one src="foo.com/other-url"></iframe><iframe id=two src="bar.com"></iframe>
</html>

Tab 2:bar.com

<html>…
</html>

Tab 3:baz.com

<html>…
</html>

这些 Tab 的进程、线程和组件结构将如下所示:

ec43eeece0a099a0440227b2b063b4c7.png

现在让我们看一下渲染的四个主要任务中的一个示例,你可能还记得:

  • 将内容 渲染 为屏幕上的像素。

  • 用 动画 将内容的视觉效果从一种状态变为另一种状态。

  • 滚动 以响应输入。

  • 将输入有效地 路由 到正确的位置,以便 Scripts 和其他子系统可以响应。

为 Tab 1 渲染更改后的 DOM,步骤如下:

7f172f8eb0214d321a7de7e8faa188df.png
  • 开发人员脚本在 foo.com 的渲染进程中更改 DOM。

  • 主线程告诉合成器线程它需要进行渲染。

  • 合成器线程告诉 Viz 它需要进行渲染。

  • Viz 向合成器线程发出渲染开始的信号。

  • 合成器线程将开始信号转发到主线程。

  • 主线程事件循环运行器运行文档生命周期。

  • 主线程将结果发送到合成器线程。

  • 合成器事件循环运行器运行合成生命周期。

  • 任何栅格化任务都会发送到 Viz 进行栅格化(这些任务通常不止一项)。

  • Viz 在 GPU 上栅格化内容。

  • Viz 确认栅格化任务的完成。注意:Chromium 通常不等待栅格化完成,而是使用称为同步令牌的东西,必须在第 15 步执行之前完成。

  • 合成器帧被发送到 Viz。

  • Viz 聚合了 foo.com 渲染进程、bar.com iframe 渲染进程和浏览器 UI 的合成器帧。

  • Viz 安排绘制。

  • Viz 将聚合的合成器帧绘制到屏幕上。

在 Tab 2 上使用 CSS transform transition 动画,步骤如下 :

69b45183c9f194e8bc252973b60fb9ac.png
  • bar.com 渲染进程的合成器线程通过改变现有属性树在其合成器事件循环中标记动画。然后重新运行合成器生命周期。(可能会发生栅格化和解码任务,但此处未描述。)

  • 合成器帧被发送到 Viz。

  • Viz 聚合了 foo.com 渲染进程、bar.com 渲染进程和浏览器 UI 的合成器帧。

  • Viz 安排绘制。

  • Viz 将聚合的合成器帧绘制到屏幕上。

在 Tab 3 上滚动网页,步骤如下:

383acca42b4f73590b351235e83e9018.png
  • 一系列 input 事件(鼠标、触摸或键盘)进入浏览器进程。

  • 每个事件都被路由到 baz.com 的渲染进程合成器线程。

  • 合成器线程确定主线程是否需要知道事件。

  • 如有必要,将事件发送到主线程。

  • 主线程触发 input 事件监听器(pointerdown、touchstart、pointermove、touchmove 或 wheel)以查看是否会调用 preventDefault。

  • 主线程返回是否 preventDefault 到合成器线程。

  • 如果不是,则将输入事件发送回浏览器进程。

  • 浏览器进程通过将输入事件与其他最近事件相结合,将事件转换为滚动手势。

  • 滚动手势再次发送到 baz.com 的渲染进程合成器线程。

  • 在合成器线程中执行滚动,baz.com 渲染进程的合成器线程在其合成器事件循环中标记一个动画。然后,这会改变属性树中的滚动偏移并重新运行合成器生命周期。它还告诉主线程触发一个 scroll 事件(此处未描述)。

  • 合成器帧被发送到 Viz。

  • Viz 聚合了 foo.com 渲染进程、bar.com 渲染进程和浏览器 UI 的合成器帧。

  • Viz 安排绘制。

  • Viz 将聚合的合成器帧绘制到屏幕上。

请注意,第一个之后的每个输入事件都可以跳过步骤三和四,因为滚动已经开始,此时脚本可以通过 scroll 事件观察它,但不再中断。

在 Tab 1 上的 iframe #two 中的超链接上路由事件:click,步骤如下:

04df103476bafec960cca015005c03f5.png
  • 一个 input 事件(鼠标、触摸或键盘)进入浏览器进程。它执行近似命中测试以确定 bar.com iframe 渲染进程应该接收点击,并将其发送到那里。

  • bar.com 的合成器线程将 click 事件路由到 bar.com 的主线程,并安排渲染事件循环任务来处理它。

  • bar.com 的主线程的输入事件处理器执行命中测试,以确定 iframe 中的哪个 DOM 元素被单击,并触发一个 click 事件以供脚本使用。如果发现没有 preventDefault,就导航到超链接。

  • 加载超链接的目标页面后,将呈现新状态,其步骤类似于上面的“呈现更改的 DOM”示例。(此处未描述这些后续更改)

  • 原文: https://juejin.cn/post/7067778081699659806

总结

呼~我们聊了好多细节。如你所见,在 Chromium 中渲染非常复杂!记住和内化所有部分可能需要很多时间,所以如果它看起来难以承受,请不要担心。

最重要的一点是,有一个概念上简单的渲染流水线,通过仔细的模块化和对细节的关注,它被分割成许多独立的组件。然后将这些组件拆分为并行进程和线程,以最大限度地提高可伸缩性能和可扩展性的机会。

这些组件中的每一个都在实现现代 Web 应用程序所需的所有性能和功能方面发挥着关键作用。很快,我们将深入探讨它们中的每一个,以及它们所扮演的重要角色。

但在此之前,我还将解释本文中提到的关键数据结构(在渲染管线图两侧以蓝色表示的数据结构),它们对于 RenderingNG 来说与组件一样重要。

最后, 送人玫瑰,手留余香,觉得有收获的朋友可以点赞,关注一波 ,我们组建了高级前端交流群,如果您热爱技术,想一起讨论技术,交流进步,不管是面试题,工作中的问题,难点热点都可以在交流群交流,为了拿到大Offer,邀请您进群,入群就送前端精选100本电子书以及下方前端精选资料 添加 下方小助手二维码就可以进群。让我们一起学习进步.

748fac086fee2895d7e09f7fd101b46d.png

de8248bdd72fe1bc1ac8adbbb32bacbe.png


推荐阅读

(点击标题可跳转阅读)

[极客前沿]-你不知道的 React 18 新特性

[极客前沿]-写给前端的 K8s 上手指南

[极客前沿]-写给前端的Docker上手指南

[面试必问]-你不知道的 React Hooks 那些糟心事

[面试必问]-一文彻底搞懂 React 调度机制原理

[面试必问]-一文彻底搞懂 React 合成事件原理

[面试必问]-全网最简单的React Hooks源码解析

[面试必问]-一文掌握 Webpack 编译流程

[面试必问]-一文深度剖析 Axios 源码

[面试必问]-一文掌握JavaScript函数式编程重点

[面试必问]-阿里,网易,滴滴,头条等20家面试真题

[面试必问]-全网最全 React16.0-16.8 特性总结

[架构分享]- 微前端qiankun+docker+nginx自动化部署

[架构分享]-石墨文档 Websocket 百万长连接技术实践

[自我提升]-Javascript条件逻辑设计重构

[自我提升]-送给React开发者十九条性能优化建议

[自我提升]-页面可视化工具的前世今生

[大前端之路]-连前端都看得懂的《Nginx 入门指南》

[软实力提升]-金三银四,如何写一份面试官心中的简历

觉得本文对你有帮助?请分享给更多人

关注「React中文社区」加星标,每天进步

bf7b2eef4210069100a5332391fef870.png   

点个赞??,顺便点个 在看 支持下我吧