Life of a Pixel:前端代码如何经过浏览器演化为屏幕显示的像素

Life of a Pixel 原本是 Chromium 团队在入职培训时的培训资料,其目的是为了让新入职的同事可以从大致上快速的了解 Chromium 的架构,而不是纠结于代码逻辑。如今该团队正式将其发布,也是为了对于此感兴趣的工程师可以快速的了解项目,参与项目的开发协做。本视频的内容,从宏观上来讲,就是本演讲的题目 Life of a Pixel,直译就是一个像素点的一辈子,表示该演讲做者但愿观众可以在视频结束后了解,前端的工程师所完成的代码,是如何经过浏览器,变为一个又一个的像素点,以及像素点是如何更新和毁灭的。前端

大致流程

web content (代码) ➡️ magic (渲染) ➡️pixels (像素)python

  • HTML(Hyper-Text Markup Language, 页面结构与内容)
  • CSS(Cascading Style Sheets, 页面样式)
  • JS(JavaScript, 负责页面结构与内容和页面样式的更新)

浏览器真正渲染的内容在红框内,以外的都是非渲染的部分。渲染的引擎能够看作是一个黑箱,在 Chromium 中,咱们把它称为 Blink。git

同时,在渲染时,咱们须要调用图像处理的底层 API 去进行渲染,对于此,有官方的统一标准就是 openGL,可是,对于 Windows,可能还须要转化为 DirectX。对于此,团队正在开发一个新项目名为 Vulkan,为了进行统一化。固然,这种底层的 API 并不能读懂咱们的 HTML 和 CSS,它们只能作一些简单得图像绘制,诸如画一些多边形这种操做。web

因此,咱们再梳理一遍流程。总的来讲就是咱们要将 web content 转化为对于的图像处理的 API,在电脑屏幕上进行绘制。在这个过程当中,为了更好地将已经渲染的图像更新,咱们要设计一种数据结构,可以帮助咱们更新这个页面的结构与内容和页面样式。这些更新就包括咱们熟知的 JavaScript API,用户在输入框输入,异步加载,动画,卷轴移动,页面缩放。后端

初始渲染

parse(解析)

DOM

HTML 的结构,是一种自然的语义化的继承式的结构。语义化是标签所带来的,集成式是树状结构所带来的。咱们能够将 HTML 看作输入进行解析,成为一颗咱们熟知的 DOM 树。很好的诠释了父子间,兄弟间的关系。咱们也能够很直观的从 JavaScript 所暴露出的 DOM API 中发现。浏览器

Style

CSS 由于是 HTML 的装饰,因此天然而然的也是要依附到 DOM 对象上。依附的过程,也就是 CSS 选择的过程。可是因为 HTML 的树状结构,CSS 有时须要写的十分复杂以去高效的匹配到相对应的 DOM 元素。

在 CSS 解析时,解析器会将每个选择器所选择的 CSS 属性名和属性值保存,做为 map,同时视频中说起,CSS 属性名是由 C++ 进行生成的,该 C++ 文件在构建时由 python 脚本自动生成。下一步,称为重计算(recalc),对于全部产生的属性,咱们会计算它们的叠加和,做为每个 DOM 元素的每个属性的值,这个值咱们也称为计算值。也就是最终渲染的结构。这个属性值咱们可使用 Chrome 的 API 或者是 JavaScript DOM API 都可以获得。数据结构

Layout

经过上面得到的计算属性,咱们就能够肯定每个元素在视图上占据的肯定位置。这里举一个简单的例子。就是每个元素是由上向下依次排列的,每个元素的高度由字体的大小和间距所决定。可是,实际状况每每可能比较复杂。

好比一个元素的内容超出了边界。那么该元素会呈现可滚动的特性,这时页面须要实时计算显示的区域。再好比表格布局,浮动,文本分列,flex 布局,writing-mode等属性,都会带来布局的复杂程度。因此,咱们须要一个更加完善的数据结构去存储这样一些状态,使得每一次迭代都可以变得高效和纯粹。

这里引入了新的数据结构,也是对于以前重计算所获得的 Render Tree 的进一步封装,将以前的复杂状况进一步考虑,获得最终每个元素在视图上的最终位置,即 Layout Tree。须要注意的是,不是每个 DOM 元素都对应有一个 Layout Object,好比对于一个 display 属性设置为 none 的元素。同时,也不是每个 Layout Object 都对应于一个 DOM 元素,好比伪元素。

基于 Layout Tree,咱们就能够处理 overflow 等一系列复杂状况。但还有一个个问题是,这种数据结构没有将输入的计算属性和输出的视图位置分离开,因此这里说起了一个正在开发的新项目名为 LayoutNG 就是为了解决这个问题。架构

Paint

获得了上个步骤的 Layout 对象也就意味着咱们能够真正的绘制像素了。但注意,这里的绘制也仅仅是语义上的,并无落实到屏幕上。咱们会再一次将 Layout 对象转化为一个一个矩形和其对于的颜色,而且将其按堆栈的形式显示,并不是是 DOM 的出现顺序。而且每一次渲染都是根据某一种属性,好比 PPT 中简化的几种,背景,浮动,前景,轮廓。在示例中虽然带有 blue class 的元素在 green class 以后,但是 green class 中的文字却显示在最前,缘由也就是 foreground 属性在视图的位置上,要先显示。

Raster

这里咱们获得了元素最终在视图的显示信息,咱们就须要真正的进行预渲染。在这一步,咱们会将屏幕上每个像素点的颜色生成为一个 32 位的二进制码,分别表示 4 中颜色,同时咱们能够利用 GPU 进行渲染加速,这里还要说起的是使用 Google 自主开发的 skia 项目进行图像处理渲染,经过最底层的 OpenGL。同时 skia 做为一个单独的项目,也支持了其余的大型项目,好比安卓系统。

gpu

最后须要注意的是真正再经过 skia 调用的 openGL API 会经过 CommandBuffer 调用 GPU 进程,进行真实的渲染。也就是说,真实的渲染是独立出去的,当这一部分进程宕机时咱们能够快速的重启。同时以前也提到过,这里咱们会将 openGL 转化为 DirectX 在 Windows 平台上,经过 Angle 这个库。固然,Vulkan 的开发就是为了统一,同时,开发者也在尝试着将 skia 调用这个模块也放入 GPU 的进程。

总结

咱们假设初次渲染已经完成,可是,对于前端的快速发展,大量的逻辑已经由后端转往前端实现,DOM 的更新变得异常频繁。简单地说,咱们须要在原有 DOM 上作适量的改动从新渲染。为了避免从新将上图的整个流程所有再次进行,这里咱们就须要将其中的某些状态保留,提升更新效率。异步

更新渲染

引入

在更新渲染时,有时咱们会缩放页面,区域滚动,或者是有动画。在这类型的状况下,若是渲染速率低于60帧,那么人眼看到会变得有些卡顿。布局

因此咱们要尽量判断出在上节提到的每个步骤中,有哪些元素是须要改变的,哪些不须要是能够从新利用的,作到效率的优化。这也是在技术实现中也被考虑到的地方。

可是,实际状况是,有时一个大的区域所有改变,那么咱们不得不对这个大的区域进行所有从新渲染,好比区域滚动。

还要注意的是 JavaScript 的设计是单线程的,也就意味着在渲染时,加入有 JS 脚本的执行,就会阻塞当前的渲染。

解决方案 compositing

基于以前提到的种种问题,Chromium 团队提出了 compositing 这种解决方案。目的就是优化性能。有点相似于 Photoshop,简单得说,有两点:

  1. 页面分红独立的层,每一层之间的渲染是独立的
  2. 单独使用一个线程(impl)去渲染层

在咱们进行动画,滚动,缩放等操做时,浏览器会监听用户的输入行为,在 impl 线程上进行工做,使得主线程执行 JavaScript,互不干扰,可是假如 impl 线程发现这个事件没法处理,则仍是会交还给主线程。

在实现层这个概念时仍是会借鉴初次渲染的数据结构,也就是树,称为 Layer Tree。它是命名在 cc(Chromium compositor)下,主要数据信息由以前的 Layout Tree 继承而来。注意,这里还有一个 PaintLayer Tree, 相似于一个中间状态,将一个 Layout Object 进行分层,而且赋予其功能,例如对子元素进行裁切或者是施加别的效果。

天然而然,咱们将会在 layout 和 paint 这两个阶段中加入 compositing update 去加快大区域从新渲染,得到 layer tree。须要注意的是,如今团队中正在进行一个工程,称为 slimming paint,将 layer tree 的创建放在 paint 阶段后,目的是为了将每一层 layer 的创建变得更加独立,而且创建属性树,提取出独立或者公共的属性,尽量地将其放到真正像素级渲染以前。当 impl 线程的 paint 阶段结束后,就能够通知主线程进行同步,有点相似于使用 git 在不一样分支上合并代码。

在 raster 以前还有一步优化,对于大面积滚动视图,没有必要一开始将全部的内容所有变换成 bitmaps,咱们只须要将视窗中的先进行转化,在这里有一个 tiling manager,它负责将区域分块,就像地板上的瓦块同样,随着滚动区域的变化,将相邻区域的瓦块优先渲染。

全部主要的阶段已经大致介绍完毕。欢迎补充和加深!

感谢张冀韬同窗将演讲内容梳理成文章并于掘金首发。

相关文章
相关标签/搜索