像素的一生

家资是何物,积帙列梁梠。这篇文章主要讲述像素的一生相关的知识,希望能为你提供帮助。
提到浏览器不得不说Chrome,Chrome是Google发行的商业产品,而Chromium是一个开源版本的Chrome,两者很像但是不完全一样。
写这篇文章是想追忆像素的由来,我们且从chrome入手,窥探其内核是如何将web内容转换为像素。
渲染事实上这个转换的过程就是渲染,网页的渲染可以表示为??Content??经过??rendering??最后呈现的过程,即*Code -> 可交互的页面*

简单的说浏览器作为应用,底层分别有content,Blink,V8,Skia等等,一层一层像套娃一样一层引用一层。对比普通应用的项目来说就是不断用第三方库和组件来拼凑应用,Chrome也不例外

  • content可以理解为就是除了浏览器主进程下的书签导航之外,网页内容这一部分,会随着网页不同而变化的部分
  • Blink渲染引擎,应该都听过就是网页的排版引擎,现存的Chrome/Edge都在用,作为开源项目维护,是在渲染进程里,其实现了 Web 平台中API 和 Web 规范的语义。
  • Blink又嵌套了V8 javascript engine来执行JS代码

何为content可以看到content就是WebContents对象,C++代码的一个类。其代表的区域其实是标签页页打开的部分(即下图红色部分)。而浏览器主进程还包含有地址栏、导航按钮、菜单、扩展,安全提示的小弹窗等等。
在Chrome中其安全模型实现的关键:渲染发生在沙盒进程中。这么做的好处在于当渲染进程??render process??挂了不会引起整个浏览器停止服务
渲染进程??render process??包含Blink渲染排版引擎和??Chromium compositor??(图中绿色的CC简写)

作为content来说,其基本构建块是文本、图像、标记(围绕文本)、样式(定义标记的呈现方式)和脚本(可以动态修改上述所有内容)。
当然了,其他类型的内容以特殊的方式呈现比如???video???, ??canvas???, ??WebAssembly???, ??WebGL???, ??WebVR???, ??PDF??, ...,这里不做讨论。

在古早时期,当时的网页只是通过网络以纯文本形式提供的数千行 html、CSS 和 javaScript。
那个时候没有编译或打包的概念,然而这种简单性是网络早期成功的关键。

综上,content就是网页代码最后运行的结果,浏览器开发者工具可以看到最后是一个经过处理后的HTML的结构。而这个HTML在渲染流水线里是一个输入:)
像素的一生像素??pixels??到底是怎么出现的呢?为何一串简简单单普普通通的代码可以获的满屏的色彩,彷佛一念花开,一念世界。


写过C/C++代码的同学知道,我们必须使用操作系统提供的底层API去画图,通过操作系统底层去调用驱动程序,令驱动程序驱动硬件将图形库的像素放到屏幕上。
今天大多数平台上都提供了???OpenGL???的标准化API。在Windows上有一个额外的??DirectX??转换。这些库提供诸如“纹理”和“着色器”之类的低级图形基元,并允许执行类似“在这些坐标处绘制一个三角形到虚拟像素缓冲区”之类的底层操作。未来计划用Vulkan替代Skia来做底层图形化调用。
因此渲染流水线的整个过程就是将输入的HTML、CSS、JS转化为OpenGL调用,最后在屏幕上呈现像素



像素的意义简单来说,像素就是为了可以更加舒服的表达自身的意义,在此认为像素意义在于两种渲染:
  • 初次渲染,将网页内容转化为底层OpenGL调用去显示页面
  • 再次渲染,在JS运行,用户输入,异步请求或者滑动等交互介入后,再次渲染页面起到交互的目的,

随着时间推移,每个渲染阶段的结果会为了提高渲染效率而被缓存下来。此外还有JS API会查询一些渲染数据如某个DOM节点的信息
渲染阶段我们不妨将把渲染管道分成多个阶段,每个阶段都是像素生命周期的一个环节,从图中可以看出原来的??content??内容会被各个阶段??stage??处理为中间数据,最后才呈现为画面呈现出来。

parsing
HTML 标签在文档上强加了一个语义上有意义的层次结构。 例如,一个??< div> ??可能包含两个段落,每个段落都有文本。 所以第一步是解析这些标签来构建一个反映这种结构的对象模型。

DOM
我们常说DOM树的原因,通过一层层铺垫的结构,形似一棵树

其是???JavaScript???操作网页的接口,全称为文档对象模型??Document Object Model??。我们主要关注三个概念:文档、元素、节点
  • 整个文档是一个文档节点
  • 每个标签是一个元素节点
  • 包含在元素中的文本是文本节点
  • 每一个属性是一个属性节点
  • 注释属于注释节点
因此常用的操作DOM的五种方法:
  • getElemenById
  • getElementsByTagName
  • getElementsByClassName
  • getAttribute
  • setAttribute
DOM(Document Object Model)本质上是一棵树,树有父子,邻居的关系,而且这棵树是暴露API给JS调用,JS可以查询和修改这棵树。JS引擎V8通过??bindings??的系统将DOM包装为DOM API供给Web开发者调用

在生产、学习的过程中,我们不可避免的需要在同一份文档中夹带多份DOM树,树多了就成了森林,对于森林的处理则是采用影子树???shadow tree??的形式对当前的DOM树进行套娃。
还记得我们在使用入vue中经常会采用的一种特性,??v-slot??,其本质上就是应用了影子树,

如下图的示例,自定义元素custom element有shadow tree。ShadowRoot的子元素其实被嵌入到slot元素里了

本质上最后是在遍历树后合成视图,也就是两棵树合并为一棵树
style
构建 DOM 树后,下一步是处理 CSS 样式。CSS 选择器选择其属性声明应应用于的 DOM 元素子集。
![image](E91BB520A37C4614AA7C8C8BB491BF78)
通过style这个属性,我们可以对像素进行各种个性化处理,如旋转跳跃、浮动变色、黯淡闪现等等,当然了这些属性也不能太浪,有可能会出现一些使用上的冲突,因此现在前端工程中定义了一种新的专门的编程语言,可以为CSS增加一些编程的特性,编译后成正常的CSS文件。具有无需考虑浏览器的兼容问题,让CSS更加简洁,适应性更强,可读性更佳,更易于代码的维护等诸多好处。
目前常见的css的预处理器
  • less
  • scss
  • sass
实现原理
CSS 解析器根据每个活动样式表构建样式规则模型。
样式规则以各种方式索引以进行有效查找。
如上图所示属性类在构建时由python脚本自动生成,以声明方式定义了所有样式属性,如右上侧??css_properties.json??经过py脚本转化为??.cc??文件

样式表可能位于项目工程中???< style> ??元素、单独加载的资源 (styles.css) 或由浏览器提供。

样式解析(或重新计算)从活动样式表中获取所有已解析的样式规则,并计算每个 DOM 元素的每个样式属性的最终值。 这些存储在一个名为???ComputedStyle?? 的对象中,本质上它只是从样式属性到值的映射。

我们可以在开发者工具中发现所有 DOM 元素的???ComputedStyle???。它也暴露在??Javascript???中。 这些都是基于??Blink???的??ComputedStyle???对象,注意到有时候一些属性增加了布局??layout???数据。我们还可以通过??getComputedStyle???的??JSAPI??去获取。

layout
在构建了 DOM 并计算了所有style之后,下一步是确定所有元素的视觉几何形状。
对于这个块级元素,我们正在计算一个矩形的坐标,该矩形对应于该元素占据的内容区域的几何区域,如计算??x??,??y??,??width??,??height??这些数据

在最简单的情况下,布局按 DOM 顺序一个接一个地放置块,垂直下降。 我们称之为“块流”。


文字和内联元素如???< span> ??则是左右浮动的,而且内联元素会被行尾打断(自动换行)。当然也有从右到左的语言,比如阿拉伯语和希伯来语

当然了布局也包括字体的排列,因为布局需要考虑文本在那里进行换行, ???Layout???  ?使用名为 ??HarfBuzz???  ?的开源文本库来计算每个字形的大小和位置,这决定了文本的总宽度。字体成型必须考虑到排版特征,如字距调整 ??letter-spacing???  ?和连字。

布局可以计算单个元素的多种边界矩形。例如,当存在溢出时, ???Layout???  ?将同时计算边界框和布局溢出。如果节点的溢出是可滚动的, ??Layout??  还会计算滚动边界并为滚动条预留空间。最常见的可滚动DOM节点是文档本身

表格元素或样式需要更复杂的布局,这些元素或样式指定诸如将内容分成多列、位于一侧且内容在其周围流动的浮动对象、或文本垂直而不是水平排列的东亚语言。
请注意 DOM 结构和 ???ComputedStyle??  ?值(如“float: left”)如何作为布局算法的输入。
此外渲染流水线的每个阶段都会使用到前面阶段的结果
![image](54929B36E302485FBC111030C450CC73)
通过遍历DOM树创建渲染树???LayoutTree???,节点一一对应。布局树中的节点实现布局算法。根据所需的布局行为,??LayoutObject???有不同的子类。比如??LayoutBlockFlow???就是块级??Flow??的文档节点。样式更新阶段也构建布局树。
在样式解析最后结束时需要构建布局树??LayoutTree??,布局阶段遍历布局树,对布局树每个节点??LayoutObject??执行布局,计算几何数据、换行符,滚动条等。
??DOM??节点跟??Layout??节点不一定是一一对应

一般情况下一个DOM节点会有一个LayoutObject,但是有时候 ???LayoutObject???  ?是没有DOM节点与之对应的。
比如上图,span标签外部没有section标签嵌套,但是LayoutTree会自动创建 ???LayoutBlock???  ?的匿名节点与之对应,再比如样式有 ??display:none???  ?的样式,那么也不会创建对应的 ??LayoutTree???  。
最后,如果是 ???shadowTree???  ?的话,其 ??LayoutObject???  ?节点可能会在不同的 ??TreeScope??  里

layout引擎的未来??LayoutNG??代表下一代的布局引擎,2020年布局引擎还在过渡阶段,所以有中间形态,如上图包含了??LayoutObject??和??LayoutNGMixin??混合节点。未来所有节点都会变成??LayoutNG??的??layout object??

NG节点的更新主要是因为之前的节点包含了输入、输出还有布局算法的信息,也就是说单个节点可以看到整棵树的状态(节点有可能需要获取父节点的宽高数据,但是父节点正在递归子节点布局中,实际上还没确定最后的布局)。

而新的NG节点对输入和输出做了明显的区分,而且输出是???immutable??的,可以缓存结果

布局结果指向描述物理几何的片段树,如图一个???NGLayoutResult???对应几??个NGPhysicalFragment???,对应右上角的几个矩形图形,如果??NGLayoutResult??没变化则对应整块都不会变化。

实例大家且看这段代码会渲染出什么效果
< div style="max-width: 100px">
< div style="float: left; padding: 1ex"> F< /div>
< br> The < b> quick brown< /b> fox
< div style="margin: -60px 0 0 80px"> jumps< /div>
< /div>

如图所示

其对应的DOM树如下图所示

那如果用???LayoutTree???来表示呢?其实Layout树和DOM树很像,节点几乎是一一对应的,但是注意这里??anonymous??匿名节点被创建出来,它只有一个块级子元素。一个布局节点只能拥有块级元素或者内联元素其中之一
图中的子元素前面两个其实共享了匿名??LayoutNGBlockFlow??,也就是说有共同的父节点

paint
绘制??paint??阶段创建绘制指令列表??paint ops list??
绘制指令??paint op??可以理解为在某些坐标用什么颜色画一个矩形类似的意思,
每个布局对象??LayoutObejct??可以有多个显示项目,对应于其视觉外观的不同部分,如背景、前景、轮廓等

正确的绘制顺序非常重要,这样当元素重叠时,它们才能正确堆叠。顺序可以由样式控制,而不是完全依靠DOM的先后顺序

每个绘制阶段???paint phase???都需单独遍历堆叠上下文??staking context??。
一个元素甚至可能部分位于另一个元素的前面,部分位于另一个元素的后面。这是因为绘制在多个阶段中运行,每个绘制阶段都对自己的子树进行遍历。

例子且看这段代码渲染出来的效果
< style> #p
position: absolute; padding: 2px;
width: 50px; height: 20px;
left: 25px; top: 25px;
border: 4px solid purple;
background-color: lightgrey;
< /style>
< div id=p> pixels < /div>


我们不妨分析一下这个指令的解析过程,一个样式和DOM节点渲染出来的结果,包含了四个绘制指令paint ops:
  • ??document??背景色绘制
  • 块级元素的背景色绘制
  • 块级元素的前景色绘制(包含文本的绘制)

文本绘制操作包含文本块的绘制,其中包含每个字的字符和偏移量以及字体。如图这些数据都是HarfBuzz计算后得到的???raster??
中文说的栅格化或者光栅化,本文取PS图层右键的栅格化为译文。熟悉PS的会知道矢量图形栅格化后放大图形会"糊"是不做栅格化处理直接放大矢量图形则不会。原因就是栅格化后只记录了单像素点的??rgba??值,放大后本来一个点数据要填满N个点,图像就"糊"

raster
??raster???  ?将绘制指令转化为位图,可以把显示列表里的绘制操作执行的过程,成为任务,也称栅格化。比如PS里的合并图层任务,主要区别就是本来矢量的图任务后会变成位图 ??bitmap???  ?,后面再缩放就会模糊。
生成的位图 ???bitmap???  ?中的每个单元格都包含对单个像素的颜色和透明度进行编码的位。这里用十六进制 ??FFFFFFFF???  ?表示一个点的 ??rgba???  值
其还对嵌入在页面中的图像资源进行解码。 绘制操作引用压缩数据(JPEG、PNG 等),然后 raster 调用适当的解码器对其进行解压缩。

GPU加速【像素的一生】GPU还可以运行生成位图的命令(“加速栅格化”)。请注意,此时这些像素还没有出现在屏幕上
???raster??  ?产生的位图数据存储在GPU内存中,通常是OpenGL纹理对象引用的GPU内存。
过去通常是存在内存里再传给GPU,但是现代GPU可以直接运行着色器shader并在GPU上生成像素,这种情况称为“加速栅格化”。但是两个结果都是一致的,最终内存(主存或者GPU内存)里拥有位图 ???bitmap??  ?

raster通过Skia发出GL调用

    推荐阅读