前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >像素的一生

像素的一生

原创
作者头像
Yerik
修改2022-05-09 12:35:18
1.4K0
修改2022-05-09 12:35:18
举报
文章被收录于专栏:烹饪一朵云烹饪一朵云

提到浏览器不得不说Chrome,Chrome是Google发行的商业产品,而Chromium是一个开源版本的Chrome,两者很像但是不完全一样。

写这篇文章是想追忆像素的由来,我们且从chrome入手,窥探其内核是如何将web内容转换为像素。

渲染

事实上这个转换的过程就是渲染,网页的渲染可以表示为Content经过rendering最后呈现的过程,即Code -> 可交互的页面

渲染.png
渲染.png

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

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

何为content

可以看到content就是WebContents对象,C++代码的一个类。其代表的区域其实是标签页页打开的部分(即下图红色部分)。而浏览器主进程还包含有地址栏、导航按钮、菜单、扩展,安全提示的小弹窗等等。

在Chrome中其安全模型实现的关键:渲染发生在沙盒进程中。这么做的好处在于当渲染进程render process挂了不会引起整个浏览器停止服务

渲染进程render process包含Blink渲染排版引擎和Chromium compositor(图中绿色的CC简写)

content.png
content.png

作为content来说,其基本构建块是文本、图像、标记(围绕文本)、样式(定义标记的呈现方式)和脚本(可以动态修改上述所有内容)。

当然了,其他类型的内容以特殊的方式呈现比如video, canvas, WebAssembly, WebGL, WebVR, PDF, ...,这里不做讨论。

content结构.png
content结构.png

在古早时期,当时的网页只是通过网络以纯文本形式提供的数千行 HTML、CSS 和 JavaScript。

那个时候没有编译或打包的概念,然而这种简单性是网络早期成功的关键。

早期content.png
早期content.png

综上,content就是网页代码最后运行的结果,浏览器开发者工具可以看到最后是一个经过处理后的HTML的结构。而这个HTML在渲染流水线里是一个输入:)

像素的一生

像素pixels到底是怎么出现的呢?为何一串简简单单普普通通的代码可以获的满屏的色彩,彷佛一念花开,一念世界。

像素形成.png
像素形成.png

写过C/C++代码的同学知道,我们必须使用操作系统提供的底层API去画图,通过操作系统底层去调用驱动程序,令驱动程序驱动硬件将图形库的像素放到屏幕上。

今天大多数平台上都提供了OpenGL的标准化API。在Windows上有一个额外的DirectX转换。这些库提供诸如“纹理”和“着色器”之类的低级图形基元,并允许执行类似“在这些坐标处绘制一个三角形到虚拟像素缓冲区”之类的底层操作。未来计划用Vulkan替代Skia来做底层图形化调用。

因此渲染流水线的整个过程就是将输入的HTML、CSS、JS转化为OpenGL调用,最后在屏幕上呈现像素

渲染简单流水线.png
渲染简单流水线.png

像素的意义

简单来说,像素就是为了可以更加舒服的表达自身的意义,在此认为像素意义在于两种渲染:

  • 初次渲染,将网页内容转化为底层OpenGL调用去显示页面
  • 再次渲染,在JS运行,用户输入,异步请求或者滑动等交互介入后,再次渲染页面起到交互的目的,
像素意义.png
像素意义.png

随着时间推移,每个渲染阶段的结果会为了提高渲染效率而被缓存下来。此外还有JS API会查询一些渲染数据如某个DOM节点的信息

渲染阶段

我们不妨将把渲染管道分成多个阶段,每个阶段都是像素生命周期的一个环节,从图中可以看出原来的content内容会被各个阶段stage处理为中间数据,最后才呈现为画面呈现出来。

像素生命周期.png
像素生命周期.png

parsing

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

parsing.png
parsing.png

DOM

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

DOM树.png
DOM树.png

其是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结构.png
dom结构.png

在生产、学习的过程中,我们不可避免的需要在同一份文档中夹带多份DOM树,树多了就成了森林,对于森林的处理则是采用影子树shadow tree的形式对当前的DOM树进行套娃。

还记得我们在使用入vue中经常会采用的一种特性,v-slot,其本质上就是应用了影子树,

image.png
image.png

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

image.png
image.png

本质上最后是在遍历树后合成视图,也就是两棵树合并为一棵树

style

构建 DOM 树后,下一步是处理 CSS 样式。CSS 选择器选择其属性声明应应用于的 DOM 元素子集。

style.png
style.png

通过style这个属性,我们可以对像素进行各种个性化处理,如旋转跳跃、浮动变色、黯淡闪现等等,当然了这些属性也不能太浪,有可能会出现一些使用上的冲突,因此现在前端工程中定义了一种新的专门的编程语言,可以为CSS增加一些编程的特性,编译后成正常的CSS文件。具有无需考虑浏览器的兼容问题,让CSS更加简洁,适应性更强,可读性更佳,更易于代码的维护等诸多好处。

目前常见的css的预处理器

  • less
  • scss
  • sass
实现原理
style原理.png
style原理.png

CSS 解析器根据每个活动样式表构建样式规则模型。

样式规则以各种方式索引以进行有效查找。

如上图所示属性类在构建时由Python脚本自动生成,以声明方式定义了所有样式属性,如右上侧css_properties.json经过py脚本转化为.cc文件

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

css表现方式.png
css表现方式.png

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

image.png
image.png

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

浏览器中的表现.png
浏览器中的表现.png

layout

在构建了 DOM 并计算了所有style之后,下一步是确定所有元素的视觉几何形状。

对于这个块级元素,我们正在计算一个矩形的坐标,该矩形对应于该元素占据的内容区域的几何区域,如计算x,y,width,height这些数据

layout.png
layout.png

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

块流.png
块流.png

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

左右.png
左右.png

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

文字排列.png
文字排列.png

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

image.png
image.png

表格元素或样式需要更复杂的布局,这些元素或样式指定诸如将内容分成多列、位于一侧且内容在其周围流动的浮动对象、或文本垂直而不是水平排列的东亚语言。

请注意 DOM 结构和 ComputedStyle 值(如“float: left”)如何作为布局算法的输入。

此外渲染流水线的每个阶段都会使用到前面阶段的结果

image.png
image.png

通过遍历DOM树创建渲染树LayoutTree,节点一一对应。布局树中的节点实现布局算法。根据所需的布局行为,LayoutObject有不同的子类。比如LayoutBlockFlow就是块级Flow的文档节点。样式更新阶段也构建布局树。

在样式解析最后结束时需要构建布局树LayoutTree,布局阶段遍历布局树,对布局树每个节点LayoutObject执行布局,计算几何数据、换行符,滚动条等。

DOM节点跟Layout节点不一定是一一对应

image.png
image.png

一般情况下一个DOM节点会有一个LayoutObject,但是有时候LayoutObject是没有DOM节点与之对应的。

比如上图,span标签外部没有section标签嵌套,但是LayoutTree会自动创建LayoutBlock的匿名节点与之对应,再比如样式有display:none的样式,那么也不会创建对应的LayoutTree。

最后,如果是shadowTree的话,其LayoutObject节点可能会在不同的TreeScope里

image.png
image.png
layout引擎的未来

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

layoutng.png
layoutng.png

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

image.png
image.png

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

image.png
image.png

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

image.png
image.png
实例

大家且看这段代码会渲染出什么效果

代码语言:html
复制
<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>

如图所示

渲染结果.png
渲染结果.png

其对应的DOM树如下图所示

image.png
image.png

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

图中的子元素前面两个其实共享了匿名LayoutNGBlockFlow,也就是说有共同的父节点

image.png
image.png

paint

绘制paint阶段创建绘制指令列表paint ops list

绘制指令paint op可以理解为在某些坐标用什么颜色画一个矩形类似的意思,

每个布局对象LayoutObejct可以有多个显示项目,对应于其视觉外观的不同部分,如背景、前景、轮廓等

image.png
image.png

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

image.png
image.png

每个绘制阶段paint phase都需单独遍历堆叠上下文staking context

一个元素甚至可能部分位于另一个元素的前面,部分位于另一个元素的后面。这是因为绘制在多个阶段中运行,每个绘制阶段都对自己的子树进行遍历。

image.png
image.png
例子

且看这段代码渲染出来的效果

代码语言:html
复制
<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>
image.png
image.png

我们不妨分析一下这个指令的解析过程,一个样式和DOM节点渲染出来的结果,包含了四个绘制指令paint ops:

  • document背景色绘制
  • 块级元素的背景色绘制
  • 块级元素的前景色绘制(包含文本的绘制)
image.png
image.png

文本绘制操作包含文本块的绘制,其中包含每个字的字符和偏移量以及字体。如图这些数据都是HarfBuzz计算后得到的raster

中文说的栅格化或者光栅化,本文取PS图层右键的栅格化为译文。熟悉PS的会知道矢量图形栅格化后放大图形会"糊"是不做栅格化处理直接放大矢量图形则不会。原因就是栅格化后只记录了单像素点的rgba值,放大后本来一个点数据要填满N个点,图像就"糊"

image.png
image.png

raster

raster将绘制指令转化为位图,可以把显示列表里的绘制操作执行的过程,成为任务,也称栅格化。比如PS里的合并图层任务,主要区别就是本来矢量的图任务后会变成位图bitmap,后面再缩放就会模糊。

生成的位图bitmap中的每个单元格都包含对单个像素的颜色和透明度进行编码的位。这里用十六进制FFFFFFFF表示一个点的rgba值

image.png
image.png

其还对嵌入在页面中的图像资源进行解码。 绘制操作引用压缩数据(JPEG、PNG 等),然后 raster 调用适当的解码器对其进行解压缩。

image.png
image.png
GPU加速

GPU还可以运行生成位图的命令(“加速栅格化”)。请注意,此时这些像素还没有出现在屏幕上

raster产生的位图数据存储在GPU内存中,通常是OpenGL纹理对象引用的GPU内存。

过去通常是存在内存里再传给GPU,但是现代GPU可以直接运行着色器shader并在GPU上生成像素,这种情况称为“加速栅格化”。但是两个结果都是一致的,最终内存(主存或者GPU内存)里拥有位图bitmap

gpu加速过程.png
gpu加速过程.png

raster通过Skia发出GL调用

GL调用即OpenGL调用,OpenGL意为"开放图形库",可以在不同操作系统、不同编程语言间适配2D,3D矢量图的渲染。

raster通过名为Skia的库发出GL调用。Skia提供了围绕硬件的抽象层,如路径和贝塞尔曲线,子像素抗锯齿以及各种混合叠加模式。

Skia是开源的,由谷歌维护。跟随Chrome一起发布,但位于单独的代码库中。它也被其他产品使用,比如Android。Skia的GPU加速代码路径构建自己的绘制操作缓冲区,在栅格化结束时刷新。实际上发起GL调用的是Skia的后端,后面会说到

image.png
image.png

回想一下,渲染器进程是一个沙箱环境,因此它不能直接进行系统调用。绘制操作被运送到GPU进程进行任务处理。GPU进程可以发出实际的GL调用。

除了独立于渲染器沙箱之外,在GPU进程中隔离图形化操作还可以保护我们免受不稳定或不安全的图形驱动程序的影响。比如GPU进程崩了,浏览器可以重启GPU进程

image.png
image.png

栅格化的绘制操作通过GPU命令缓冲区command buffer传输,渲染进程和GPU进程通过IPC通道发送。命令缓冲区command buffer最初是为序列化的GL图形命令构建的,类似一个proxy。当前的“进程外”栅格化(即GPU)以不同的方式使用它们,更多是绘制操作的包装器,就是命令缓冲区command buffer与底层图形API无关

image.png
image.png

GPU进程中的GL函数指针通过动态查找操作系统底层共享的OpenGL库进行初始化,Windows上用ANGLE做一个转化步骤。

Angel是另一个由Google构建的库;它的工作是将OpenGL转换为DirectX,DirectX是微软在Windows上用于加速图形的API。

调查发现Angle比Windows的OpenGL驱动程序运行更好。

image.png
image.png

change

至此拥有了整个渲染流水线pipeline,从DOM一直到内存中的像素,牢记渲染不是静态的,也不是执行一次就完成了,浏览器会话期间发生的任何事情都会动态更改渲染的过程。并且整个pipeline从头开始运行是非常昂贵的,尽可能希望能减少不必要的工作以提高效率

change.png
change.png

frams

低于60帧每秒的动画和滚动看起来会非常卡,渲染器生成动画帧,每个帧都是内容在特定时间点状态的完整呈现,多个帧连起来就是看到的动画,其实动画只要达到60帧每秒那么看起来就会是连贯的。新的设备甚至要求90或120甚至更高的帧率。

如果在1/60秒内,约16.66ms还不能渲染完一帧画面,那么画面看起来就是断断续续很卡的样子

frams.png
frams.png

流水线各个阶段都依赖上一步的结果

为了提高性能,很简单的想到了尽可能复用上一阶段处理的结果,对于渲染来说既重复使用以前帧的输出

image.png
image.png

repaint

大块区域的绘制和栅格化是非常昂贵的,比如在滚动的时候,视口内所有像素都变化了,这个过程称为重绘repaint

repaint.png
repaint.png

渲染进程主线程的竞争关系

渲染进程的主线程的任何事情都会跟JS竞争(互斥关系),意味着其实JS也会阻塞渲染主线程其他任务的执行

image.png
image.png

解决竞争关系

将页面分解为不同的层便于栅格化raster对不同的层单独处理,在渲染进程主线程构建层后commit到合成线程compositor thread去,合成线程compositor thread会对每一层进行单独绘制

我们可以在浏览器开发工具的Layer看到当前页面的分层,分层的目的是可以对单独的层进行变换transform和栅格化raster

试想一下如果有123三层,其中1,2两层没变化,第3层旋转了,那么只要对第三层每帧进行变换就可以得到每一帧的输出,计算量大大减少

所以分层的目的是为了减少计算加速渲染效率,在渲染进程合成器线程执行则是为了不影响渲染主线程的任务执行

图中的impl*即渲染进程的合成线程,因为历史原因在代码里都是这样表示,后面所有表示合成线程都用impl表示

image.png
image.png

好处

分层的作用在有动画时候可以显著提升性能,如图所示BBB文本一层的变换不会影响其他层

image.png
image.png

动画是层的移动,页面滚动是层的移动和裁剪,放大缩小也是层的缩放

image.png
image.png

实现方式

当滚动事件没有触发JS逻辑时候,即使渲染进程主线程很繁忙,但是浏览器进程发出的页面滚动事件的处理也不会受到影响,因为渲染进程的合成线程compositor thread可以单独处理滚动事件

当然如果滚动触发了JS的逻辑,那么合成线程必须转发事件到主线程去,滚动事件会进入主线程任务队列等待处理

image.png
image.png

正常情况下一个LayoutView会创建一个PaintLayer,对应一个cc(Chromium Compositor) Layer

但是某些样式属性也会导致对应的LayoutObject单独成层,比如transform属性就类似创建新层的“触发器”一样,浏览器遇到这个属性就会单独创建新层,cc(Chromium Compositor) Layer没有父子关系,是一个平级的列表,但是还是保留LayerTree的名称

image.png
image.png

滚动容器创建特殊的多个层,比如元素加了overflow:scroll的滚动属性,那么合成的时候会有5个层,其中4个层都是滚动条scrollbar的层,这些层合并起来称为CompositedLayerMapping

image.png
image.png

合成透明滚动条会禁用子像素抗锯齿,如上图左下角所示。而且判断是否合成滚动条也有判断逻辑,在安卓和ChromeOS上可以合成所有的滚动条

image.png
image.png

合成任务

如下图,合成任务包含构建层树的过程。在布局layout之后,绘制paint任务之前,这个过程也可以称为分层和合成任务,每一层layer都是独立绘制的,一些属性节点单独为层,比如will-change,3D属性transform之类

image.png
image.png

属性树

渲染进程合成线程绘制的时候,合成线程里的合成器可以将各种属性应用于其绘制的图层,如变换矩阵,裁剪,滚动偏移,透明度。这些数据储存在属性树里,可以将这些理解为图层的属性(过去也是这么干的)。后面为了解耦这些属性,让它们可以脱离层单独使用,需要引入prepaint的过程

image.png
image.png

预绘制prepaint阶段遍历并构建属性树

prepaint.png
prepaint.png

CAP

CAPcomposite after paint的缩写,它的目标是将属性和层解耦。即在paint阶段只需要paint的信息,而不需要知道层的任何信息,因为这时层还没有构建

CAP.png
CAP.png

在过去,变换、裁剪、效果滚动等信息等存储在层本身上,但CAP要求层的属性解耦。未来,层layer的合成会在绘制后进行

commit

在绘制paint阶段完成后,即绘制指令准备完成后,会进入渲染进程合成线程commit阶段

commit会拷贝层和属性树生成副本,这里合成线程的commit会阻塞主线程直到commit完成

commit.png
commit.png

tiling

回想一下:光栅是绘制之后的步骤,它将绘制操作转换为位图。

图层可能很大 - 栅格化整个图层的成本会很高,但如果只栅格化部分图层的可见部分成本则会小很多。

这里tiling是平铺的意思,类似装修时候铺地板用大块瓷砖平铺,页面显示的做法类似。

根据viewport所在位置的不同,渲染进程合成器线程会选择靠近视口的图块tiles进行渲染,将最后选择渲染的图块传递给GPU栅格化线程池里的单个栅格化线程执行栅格化,最后得到栅格化好后的tile图块。图块大小根据不同设备的分辨率有不同的大小,比如256*256或512*512

tiling.png
tiling.png

drawlayer

在栅格化所有的图块tiles完成后,渲染进程的合成器线程会生成draw quads命令。

quad类似于在屏幕上特定位置绘制图块tile的指令,draw quads就是绘制图块们的意思。

此时的quad是层树layer tree在拿属性树经过一堆变换后的最终结果,每个quad都引用图块tile在GPU内存里的栅格化输出结果。

多个DrawQuad最后被包装在CompositorFrame里(简单理解就是一排要铺上去的瓷砖 :-),这是渲染进程最后的输出,包含有渲染进程生成的动画帧,会被传递给GPU进程。

drawlayer.png
drawlayer.png

注意执行到这里还只是数据,这里屏幕还没有像素呈现

activation

在准备图块tiles进行栅格化和draw两个阶段渲染进程的合成线程都会参与,但是渲染进程主线程里的layer数据还在不断commit过来。实际上合成线程具有两个树的拷贝副本

  • pending tree: 负责接收新的commit并转给栅格化线程池里的栅格化线程执行,完成后进入激活activation阶段,同步复制处理好后的layer副本到active tree里
  • active tree: 绘制上一次activation同步复制的layer副本(来自上一个commit)

这里pending treeactive tree都是层列表和属性树的结合,不是真的树结构,基于习惯沿用树的叫法

image.png
image.png

display

GPU进程的显示合成器display compositor会将多个进程最后的CompositorFrame进行合并显示,前面说过CompositorFrame是每个进程最后的输出,包裹了DrawQuad列表。

可以看到这里也有浏览器主进程的CompositorFrame,导航栏,收藏夹,前进后退这些Content外的渲染是浏览器主进程控制的。浏览器主进程有自己合成器为浏览器UI生成动画帧,比如标签条和地址栏的动画。

界面可以嵌入其他界面。浏览器嵌入渲染器,渲染器可以嵌入其他渲染器用于跨源iframe(也称为站点隔离,“进程外iframe”或OOPIF)。同源网页,比如iframe和一个标签页可能共用一个渲染进程,而跨源网页则一定是多个渲染进程。

显示合成器display compositor在GPU进程中的Viz线程上运行。Viz取Visuals视觉效果的意思。

显示合成器display compositor同步传入的帧,了解嵌入界面之间的依赖关系,做界面聚合。

image.png
image.png

Viz线程除了做界面聚合还发起图形调用,最后屏幕上显示compositor frame的quad。Viz线程是双缓冲的,分为前置缓冲区和后置缓冲区,这里将数据处理后序列化放到后置缓冲区

undefined.png](https://ask8088-private-1251520898.cn-south.myqcloud.com/developer-images/article/6929359/84q5l2p2tb.png?q-sign-algorithm=sha1&q-ak=AKID2uZ1FGBdx1pNgjE3KK4YliPpzyjLZvug&q-sign-time=1650183666;1650190866&q-key-time=1650183666;1650190866&q-header-list=&q-url-param-list=&q-signature=15ea62ab75afce64c55ad414c39fb1e33acdf1e6)

旧模式是GPU主线程解码器真正发起GL调用,新模式中是交给Skia库。Skia绘制到一个异步显示列表里,会一起传递到GPU主线程。GPU主线程的Skia后端发起真正的GL调用。

分离GL调用通过第三方的Skia或者未来准备使用的Vulkan实现与OpenGL解耦

undefined.png](https://ask8088-private-1251520898.cn-south.myqcloud.com/developer-images/article/6929359/kwlyvfr4hj.png?q-sign-algorithm=sha1&q-ak=AKID2uZ1FGBdx1pNgjE3KK4YliPpzyjLZvug&q-sign-time=1650183679;1650190879&q-key-time=1650183679;1650190879&q-header-list=&q-url-param-list=&q-signature=bdd97d174204ccc8a1020a39a8c4c48efe12de3b)

在大多数平台上,显示合成器display compositor的输出是双缓冲的,即包含前后两个缓冲区。图块绘制到后台缓冲区,Viz发出命令交换前后缓冲区使其可见

也就是说屏幕显示器这一帧的画面,是每HZ从前置缓冲区读取后在屏幕显示的,后置缓冲区在马不停歇的绘制,通过前后缓冲区的交换实现新一帧画面的呈现。在OS X上,使用CoreAnimation做了一些稍微不同的事情

显卡的作用?负责将数据写到后缓冲区,写完后前后缓冲区互换。通常情况下显卡的更新频率和显示器的刷新频率是一致的,如果不一致则会发现视觉上的卡顿。大多数设备屏幕的更新频率是60次/秒,这也就意味着正常情况下要实现流畅的动画效果,渲染引擎需要每秒更新60张图片到显卡的后缓冲区

至此浏览器完成了它的任务,底层驱动通过调用硬件完成绘制。最后,我们的像素出现在屏幕上

undefined.png](https://ask8088-private-1251520898.cn-south.myqcloud.com/developer-images/article/6929359/glnujt7tuk.png?q-sign-algorithm=sha1&q-ak=AKID2uZ1FGBdx1pNgjE3KK4YliPpzyjLZvug&q-sign-time=1650183688;1650190888&q-key-time=1650183688;1650190888&q-header-list=&q-url-param-list=&q-signature=3ab7a05605d0bd57344ee3d6cdc9da6754d3ac6e)

总结

总结.png
总结.png

回顾一下整个渲染流水线的过程,从渲染主线程获取Web内容,构建DOM树,解析样式,更新布局,layer分层后合成,生成属性树,创建绘制指令列表。

再到渲染进程合成线程收到渲染主线程commit过来的带有绘制指令和属性树的layer,将layer分块为图块,使用Skia对图块进行栅格化,拷贝pending treeactive tree,生成draw quads命令,将quad发送给GPU的Viz线程,最后像素显示到屏幕上。

大多数阶段是在渲染器进程里执行的,但是raster和display则在GPU进程中执行。

核心渲染阶段DOM,style,layout,paint是在渲染进程主线程的Blink进行的,但是滚动和缩放等交互事件在渲染主线程繁忙时可以在渲染进程合成线程里执行

渲染进程主线程

  • DOM: 解析HTML生成DOM
  • style: 解析styleSheet生成ComputedStyle
  • layout: 生成layout tree,跟DOM树基本对应,但是display:none的节点不显示,内联元素会创建LayoutBlock匿名节点包裹
  • layer分层后合成: 某些样式属性会单独形成层,如transform会形成单独的层方便进行图形变换,滚动元素会多出scrollbar的4层。合成任务在渲染进程的合成线程中执行,与渲染主线程隔离互不影响
  • prepaint: 为了将属性与层解耦引入prepaint阶段,prepaint阶段需要遍历并构建属性树,属性树即存储如变换矩阵,裁剪,滚动偏移,透明度等数据的地方,方便后面paint阶段拿属性树数据处理
  • paint: 绘制过程是将LayoutObject转化为绘制指令paint op,每个LayoutObject会对应多个绘制指令paint ops,比如背景,前景,轮廓等。样式可以控制绘制的顺序。绘制有自己的顺序,如背景色在前,其次是浮动元素,前景色,轮廓outline

渲染进程合成线程

页面的滚动等交互会进入渲染进程合成线程compositor thread里处理,这也是渲染进程主线程繁忙时交互也不卡的原因

  • commit: 渲染进程合成线程将层从渲染主线程拷贝出两份层和属性树副本
  • tiling: 栅格化整个图层成本大,渲染进程合成线程将layer分块后选择视口相近的图块tiles再进行栅格化成本小很多
  • activate: 合成线程具有两个树的副本,pending tree负责将新commit的layer转到栅格化线程池里的栅格化线程处理好后同步到active tree
  • draw: 栅格化所有变换后的图块之后,生成draw quads命令,包含多个DrawQuadCompositorFrame,这是渲染进程最后的输出,此时屏幕还没有像素出现

GPU进程

  • raster: 栅格化是将绘制指令paint op转化为位图bitmap的过程,转化后每个像素点的rgba都确定。栅格化还处理图片解码,通过调用不同解码器解压缩图片,GPU可以加速栅格化,通过调用Skia对图块进行栅格化
  • Skia: 封装OpenGL调用,提供异步显示列表,最后传递到GPU主线程处理,GPU主线程的Skia后台发起真正的GL调用
  • display: GPU Viz线程里的显示合成器display compositor合并多个进程的CompositorFrame输出,并通过Skia发起图形调用,像素呈现在屏幕上

参考资料

  1. https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit#slide=id.ga884fe665f_64_1691
  2. https://www.youtube.com/watch?v=K2QHdgAKP-s
  3. https://juejin.cn/post/6920773802624286733
  4. https://manfredhu.com/broswer/2021-12-31.broswer-render.html
  5. https://www.chromium.org/developers/design-documents/oop-iframes

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 渲染
    • 何为content
    • 像素的一生
      • 像素的意义
        • 渲染阶段
          • parsing
          • DOM
          • style
          • layout
          • paint
          • raster
        • change
          • frams
            • 流水线各个阶段都依赖上一步的结果
              • repaint
                • 渲染进程主线程的竞争关系
                • 解决竞争关系
                  • 好处
                    • 实现方式
                      • 合成任务
                        • 属性树
                        • CAP
                        • commit
                      • tiling
                        • drawlayer
                          • activation
                            • display
                            • 总结
                              • 渲染进程主线程
                                • 渲染进程合成线程
                                  • GPU进程
                                  • 参考资料
                                  相关产品与服务
                                  云开发 CLI 工具
                                  云开发 CLI 工具(Cloudbase CLI Devtools,CCLID)是云开发官方指定的 CLI 工具,可以帮助开发者快速构建 Serverless 应用。CLI 工具提供能力包括文件储存的管理、云函数的部署、模板项目的创建、HTTP Service、静态网站托管等,您可以专注于编码,无需在平台中切换各类配置。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档