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

本文涉及的产品
.cn 域名,1个 12个月
简介: 进程、线程网页的主要进程显示系统基础知识渲染进程的线程渲染进程主线程

回头再看,所有的困难都是奖赏

简明扼要

  1. 如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程
  2. 一个典型的显示系统中,一般包括CPU、GPU、Display三个部分
  3. 屏幕刷新频率取决于硬件的固定参数(一般不会改变)
  4. 帧率 (Frame Rate) : GPU 在一秒内绘制操作的帧数(页面卡顿和帧率有关)
  5. 垂直同步信号被排版线程接收到,新的屏幕渲染开始
  6. 在主线程中,按照出现先后,依次出现了
    1. DOM Tree
    2. Render Tree (DOM Tree + 指定元素样式信息)
    3. Layout Tree (由Render Tree 中可见元素组成)
    4. Layer Tree (由Layout Tree中层叠上下文相同的元素组成一个叶子节点)

一图胜千言


文章概要

  1. 进程、线程
  2. 网页的主要进程
  3. 显示系统基础知识
  4. 渲染进程的线程
  5. 渲染进程主线程

1. 进程、线程

在开始介绍浏览器工作流程的时候,我们需要简单说一下:进程、线程。

进程:某个应用程序的执行程序。

线程:常驻在进程内部并负责该进程部分功能的执行程序。

当你启动一个应用程序,对应的进程就被创建。进程可能会创建一些线程用于帮助它完成部分工作,新建线程是一个可选操作。在启动某个进程的同时,操作系统(OS)也会分配内存以用于进程进行私有数据的存储。该内存空间是和其他进程是互不干扰的。

Inside look at modern web browser

有人的地方就会有江湖,如果想让多人齐心协力的办好一件事,就需要一个人去统筹这些工作,然后通过大喇叭将每个人的诉求告诉对方。而对于计算机而言,统筹的工作归OS负责,OS通过Inter Process Communication (IPC)的机制去传递消息。

Inside look at modern web browser


2. 网页的主要进程

针对一个网页,存在很多进程,如下图所示。

我们来着重解释一下和页面渲染相关的进程。

渲染进程 :Chrome 的默认策略是,每个标签对应一个Render Process。它包含很多线程,这些线程一起负责将页面显示在屏幕上。例如:排版线程(Compositor)、图块工作线程(Tile Worker)还有主线程。

针对渲染进程还有一点需要说明,就是进程复用

如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程。官方把这个默认策略叫 process-per-site-instance


同一站点:根域名(wl.com)加上协议(例如,https:// 或者http://),还包含了该根域名下的所有子域名和不同的端口  

A.wl.com  

wl.com  

B.wl.com:789789 这三个域名就是同一站点。

GPU 进程:用于服务所有标签页和浏览器主进程的进程。当页面数据(frame)被提交(commit)到GPU进程时,GPU进程继续对数据进行处理,使其变成图块(tiles)和其他数据(DrawQuad命令)并传输到系统GPU组件中的后缓冲区,提交完成之后,GPU 会将后缓冲区和前缓冲区互换位置,也就是前缓冲区变成了后缓冲区,后缓冲区变成了前缓冲区,此时刚才提交的像素和图片就显示在浏览器上了。


3. 显示系统基础知识

在一个典型的显示系统中,一般包括CPU、GPU、Display三个部分, CPU负责计算帧数据,把计算好的数据交给GPU,GPU会对图形数据进行渲染,渲染好后放到buffer(图像缓冲区)里存起来,然后Display(屏幕或显示器)负责把buffer里的数据呈现到屏幕上。

单缓存,从缓存映射到屏幕

Note: 在计算机中每启动一个应用程序,OS会为其分配指定的CPU和GPU模块

基础概念

  • 屏幕刷新频率
    一秒内屏幕刷新的次数(一秒内显示了多少帧的图像),单位 Hz(赫兹),如常见的 60 Hz。刷新频率取决于硬件的固定参数(不会变的)。
  • 逐行扫描:
    显示器并不是一次性将画面显示到屏幕上,而是从左到右边,从上到下逐行扫描,顺序显示整屏的一个个像素点,不过这一过程快到人眼无法察觉到变化。以 60 Hz 刷新率的屏幕为例,这一过程即 1000 / 60 ≈ 16ms
  • 帧率 (Frame Rate) :
    表示 GPU 在一秒内绘制操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟GPU最多绘制 60 帧画面。帧率是动态变化的,例如当画面静止时,GPU 是没有绘制操作的,屏幕刷新的还是buffer中的数据,即GPU最后操作的帧数据。
  • 画面撕裂(tearing):
    一个屏幕内的数据来自2个不同的帧,画面会出现撕裂感。

双缓存

画面撕裂原因

屏幕刷新频是固定的,比如每16.6ms从buffer取数据显示完一帧,理想情况下帧率和刷新频率保持一致,即每绘制完成一帧,显示器显示一帧。但是CPU/GPU写数据是不可控的,所以会出现buffer里有些数据根本没显示出来就被重写了,即buffer里的数据可能是来自不同的帧的, 当屏幕刷新时,此时它并不知道buffer的状态,因此从buffer抓取的帧并不是完整的一帧画面,即出现画面撕裂。

简单说就是Display在显示的过程中,buffer内数据被CPU/GPU修改,导致画面撕裂。

双缓存

那咋解决画面撕裂呢?答案是使用 双缓存

由于图像绘制屏幕读取使用的是同个buffer,所以屏幕刷新时可能读取到的是不完整的一帧画面。

双缓存,让绘制和显示器拥有各自的buffer:GPU 始终将完成的一帧图像数据写入到 Back Buffer,而显示器使用 Frame/Front Buffer,当屏幕刷新时,Frame Buffer 并不会发生变化,当Back buffer准备就绪后,它们才进行交换。如下图:

双缓存,CPU/GPU写数据到Back Buffer,显示器从Frame Buffer取数据

VSync(垂直同步信号)

问题又来了:什么时候进行两个buffer的交换呢?

假如是 Back buffer准备完成一帧数据以后就进行,那么如果此时屏幕还没有完整显示上一帧内容的话,肯定是会出问题的。看来只能是等到屏幕处理完一帧数据后,才可以执行这一操作了。

当扫描完一个屏幕后,设备需要重新回到第一行以进入下一次的循环,此时有一段时间空隙,称为VerticalBlanking Interval(VBI)。那,这个时间点就是我们进行缓冲区交换的最佳时间。因为此时屏幕没有在刷新,也就避免了交换过程中出现 screen tearing的状况。

VSync(垂直同步)是VerticalSynchronization的简写,它利用VBI时期出现的vertical sync pulse(垂直同步脉冲)来保证双缓冲在最佳时间点才进行交换。另外,交换是指各自的内存地址,可以认为该操作是瞬间完成。

所以说V-sync这个概念并不是Google首创的,它在早年的PC机领域就已经出现了。


4. 渲染进程的线程

我们前面对浏览器各个进程做了简单的介绍。接下来对渲染进程做一个详细的分析。

排版线程 (Compositor Thread)

排版线程是第一个接触到垂直同步信号的线程。同时,它也会接收到输入(input)事件。排版线程会对一些针对类似滚动事件的输入事件进行拦截,使其不会被传入到主线程,而是通过更新图层位置和直接将页面信息(frames)提交到GPU线程,并由GPU直接输出该页面。而一些常规的输入事件(相比较滚动事件)或者一些需要可视化的工作,排版线程会将其转发到主线程来处理。

我们可以将图片显示的过程类比成一个玩具工厂的生产流水线。例如,客户想要一批冰墩墩的订单。首先,需要和厂商的业务员(小西 Compositor)进行沟通交流,在小西确认了该批订单的量和批次(是否是滚动类事件等),决定到底是通过主厂(主线程)还是该公司的附属厂(GPU线程)进行该批次产品的生成。

在这个过程中,业务员(小西)起到了决定性作用,虽然他不负责具体的生产工作,但是他能决定工厂流水线何时启动(初始化主线程)

主线程

主线程负责一些我们比较熟知的任务:js的执行(通过V8)/样式的生成/页面布局操作(回流)还有绘制操作(重绘)。在主线中存在很多耗时且不可控的操作,如果这些操作影响到帧率,使其变小,从而就会发生卡顿现象(janky)。

图块工作线程(Tile Worker)

该类线程是由排版线程负责管理。根据排版线程的工作任务多少,而决定构建对应的图块工作线程。 而图块工作线程专门用于栅格化(Rasterization)的专用线程。


5. 渲染进程主线程

  • 页面渲染起始标识:
    当垂直同步信号被排版线程接收到,新的屏幕渲染开始
  • 输入事件回调:
    输入事件的数据信息从排版线程向主线程的事件回调中传递。所有输入事件的回调(touchmove/scroll/click)应该先被调用,并且每帧都应该触发,但是这不是必须的;存在指定的调度器来对这些回调进行调用,调度器的使用方式受OS控制。同时,在用户触发事件和事件真正的在主线程中被触发之间存在一些延迟。
  • rAF(requestAnimationFrame):
    这是一个用于屏幕视觉更新的理想的位置。因为,在此处能够获取到垂直同步事件最新的输入数据。其他类型的视觉更新,比如样式计算都比这个时间点滞后,所以该时间点是处理突变元素信息变更的最好时机。但是,人无完人,金无足赤。这个阶段是无法获取到任何计算后的样式信息(el.style.backgroundImage)或者布局属性(el.style.offsetWidth)的。
  • 解析HTML (Parse HTML):
    通过指定的解析器,将不能被浏览器识别的HTML文本,转换为浏览器能识别的数据结构:DOM对象。DOM本质上是一种接口(API),是专门操作网页内容的API标准。
    DOM把整个页面映射为一个多层节点结构,HTML或XML页面中的每个组成部分都是某种类型的节点。借助DOM提供的API,开发人员可以删除、添加、替换或修改任何节点。
    此过程,发生在页面加载阶段或者代码中调用指定API后(appendChild)。
  • 重新计算样式 :对新生成被修改的元素进行样式信息计算。此过程可能触发整个DOM树的整体计算也可以是局部小范围的计算过程,取决于被改动的元素的位置。例如,改动body元素的属性,就会发生整个DOM树的重新计算。 将元素样式和DOM元素结合起来,就会生成Render Tree
  • 布局(Layout):
    计算每个可视元素的位置信息(距离视口的距离和元素本身大小)。并生成对应的Layout Tree。
  • 更新图层树 (Update Layer Tree):
    在 Render 树的基础上,我们会将拥有相同z 坐标空间的 Layout Objects归属到同一个渲染层(Paint Layer)中。Paint Layer 最初是用来实现stacking context(层叠上下文):它主要来保证⻚面元素以正确的顺序合成。
  • 绘制 (Paint):
    该过程包含两个过程,第一个过程是绘制操作(painting),该过程用于生成任何被新生成或者改动元素的绘制信息(包含图形信息和文本信息);第二个过程是栅格化(Rasterization),用于执行上一个过程生成的绘制信息。

  • 页面合成 (Composite):
    将图层信息(layer)和图块信息提交(commit)到合成线程中。并且在合成线程中会对一些额外的属性进行解释处理。例如:某些元素被赋值will-change或者一些使用了硬件加速的绘制方式(canvas)。
  • 栅格化 (Rasterize) :
    在绘制阶段(Paint)生成的绘制记录(Paint Record)被合成线程维护的图块工作线程(Tile Worker)所消费。而这个工作线程数量受平台和设备的制约。例如,在Android 系统中存在一个工作线程,在桌面应用中存在四个。栅格化是根据图层来完成的,而每个图层由多个图块组成。
  • 页面信息提交:
    当页面中所有的图层都被栅格化,并且所有的图块都被提交到排版线程,此时排版线程将这些信息连同输入数据(input data)一起打包,并发送到GPU线程。
  • 页面显示:
    当前页面的所有信息在GPU中被处理,GPU会将页面信息传入到双缓存中的后缓存区,以备下次垂直同步信号到达后,前后缓存区相互置换。然后,此时屏幕中就会显示想要显示的页面信息。

在主线程中,按照出现先后,依次出现了

1. DOM Tree

2. Render Tree (DOM Tree + 指定元素样式信息)

3. Layout Tree (由Render Tree 中可见元素组成)

4. Layer Tree (由Layout Tree中层叠上下文相同的元素组成一个叶子节点)

额外的奖赏

  • requestIdleCallback:如果在当前屏幕刷新过程中,主线程在处理完上述过程后还有剩余时间(<16.6ms),此时主线程会主动触发requestIdleCallback。这是用于执行一些非必要的用户回调。

渲染层(Paint Layer)分类

渲染层也可以主要分为以下 3 类,各个渲染层的主要形成原因如下所示:

  1. kNormalPaintLayer
  • 根元素(HTML)
  • position 值为 absolute 或 relative,且 z-index 不为 auto 的元素
  • position 值为 fixed 或 sticky 的元素
  • flex 容器的子元素,且 z-index 值不为 auto
  • grid 容器的子元素,且 z-index 值不为 auto
  • mix-blend-mode 属性值不为 normal 的元素
  • 以下任意属性值不为 none 的元素:
  • transform
  • filter
  • perspective
  • clip-path
  • mask/mask-image/mask-border
  • isolation 属性值为 isolate 的元素
  1. kOverflowClipPaintLayer
  • overflow 不为 visible
  1. KNoPaintLayer
  • 不需要 paint 的 PaintLayer,比如一个没有视觉属性(背景、颜色、阴影等)的空 div

资料参考

  1. The Anatomy of a Frame 需要🪜
  2. Inside look at modern web browser 需要🪜 (国内版
  3. “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 特殊说明,针对Vsync的那段,我直接拿来主义了
  4. 渲染层(Paint Layer)分类
相关文章
优化是一种习惯●出发点是"站在靠近临界"的地方
优化是一种习惯●出发点是"站在靠近临界"的地方
49 0
|
3月前
|
数据建模 测试技术 数据库
仓储设计实现问题之提出仓储的建模时要从问题空间角度看待如何解决
仓储设计实现问题之提出仓储的建模时要从问题空间角度看待如何解决
19 1
|
6月前
|
前端开发 关系型数据库 定位技术
WEBGIS系统整体设计
WEBGIS系统整体设计
88 6
WEBGIS系统整体设计
|
6月前
|
前端开发 UED CDN
从前端工程师的视角看待用户体验优化
在当今互联网高度竞争的时代,用户体验优化已经成为各个企业追求的目标之一。作为前端工程师,我们不仅要关注页面的美观和交互设计,更要深入了解用户行为和需求,从而为用户提供更好的体验。
|
数据采集 搜索推荐 安全
如何做独立站Shopify优化?
答案是:足够多的GPB外链+足够多的优质内容。 Shopify 是一种流行的电商平台,很多商家都选择使用 Shopify 来建立和运营他们的在线商店。 然而,要想在竞争激烈的电商市场中脱颖而出,你需要对你的 Shopify 网站进行优化。 以下是一些有用的 Shopify 优化技巧。 提升网站速度 Shopify 网站的加载速度直接影响到用户体验和搜索引擎排名。 你需要采取措施来提升你的 Shopify 网站的加载速度。
150 0
如何做独立站Shopify优化?
|
供应链 安全 智能硬件
|
监控 安全 测试技术
从TMMI角度谈谈质量度量
在TMMi体系中,缺陷逃逸率是用来评估交付质量的衡量指标,如果该值低于某个阈值,则可以判断交付质量的好坏。
|
前端开发 人机交互 UED
浅谈前端视角下的用户体验
浅谈前端视角下的用户体验
1468 0
浅谈前端视角下的用户体验
|
开发者 容器
「站在上帝的角度」谈谈Element组件结构-InputNumber
「站在上帝的角度」谈谈Element组件结构-InputNumber