Life of a Pixel 演讲的学习笔记。浏览器的渲染过程极为的复杂,演讲的语速也特别的快,所以如有错误请及时指出。
前端的所有代码统称为Content,比如html,js,css,图片,视频等。
在Chromium代码架构上Content命名空间包含了,黄色框内的所有内容。在Chromium代码中由WebContent类表示,WebContent类中封装了渲染进程。
Chromium是Google的开源项目,Chrome浏览器就是基于Chromium代码实现,Edge,Opera也是基于Chromium代码。
Blink是浏览器排版引擎,属于Chromium中的一部分。Blink属于Content中渲染过程代码的子集。
总结一下:在Chromium中WebContent类负责Content的渲染,渲染过程交由Blink实现
parsing(解析HTML)-> style (生成样式规则模型) -> layout(生成布局对象)-> compositing update (输入合成)-> paint(绘制)-> commit(提交)-> tiling(切割)-> raster(栅格化)-> draw quads -> display(显示到屏幕上)
浏览器并不能只靠自己将网页渲染到屏幕上,浏览器的渲染需要使用底层操作系统提供的图形库,比如OpenGL API。但是OpenGL并不认识Html,Css这些内容,所以需要通过浏览器的Web Content需要将Html,Css转换成OpenGL可以识别的内容,然后通过OpenGL渲染到屏幕上。
在渲染到屏幕之后,需要监听并响应,JavaScript,用户输入,异步加载,动画,滚动,缩放,然后进行渲染更新。对于一些更新渲染,是不需要从头走完全部的渲染流程的。
从网络请求开始,最先获取的是html文件,所以浏览器渲染的起点,是HTML解析器。同时HTML中引入了CSS,JS,图片等资源,浏览器也会加载它们。
HTML文件的解析过程:
HTML文件 -stream-> HTMLDocumentParser(HTML文档解析器)-> HTMLTreeBuilder(HTML树构建器)-> DOM
DOM是基于HTML的倒着的树形结构,DOM有两个作用:
CSS样式表的解析过程:
CSS样式表 -> CSSParser(CSS解析器)-> 样式规则模型(Style Rule Model)
CSS样式是如何作用与DOM的?
根据已经解析的样式规则(Style Rule),和浏览器的默认样式,计算出每一个DOM的样式。样式和属性值存储在一个巨大的ComputedStyle对象中。ComputedStyle对象是属性和属性值的映射。ComputedStyle对象中会挂载元素,应对应不同元素的不同样式。
在构建完DOM并完成样式计算后,需要确定所有元素的几何形状(几何形状所占的区域以及坐标),这些布局信息称为LayoutObject对象,
布局对象(LayoutObject)保存布局树中,并与DOM关联。不同的节点,对应不同的布局类。但是不同布局类都继承自LayoutObject这个父类。
LayoutObject对象和DOM元素并不是一一对应的。比如当DOM元素的样式是display: none
时,是没有LayoutObject对象的。
输入合成阶段,我们在下面讲🍵
根据布局对象(LayoutObject),绘制操作会将绘制操作记录在一个待显示项目列表(display items list)中。
什么是绘制操作?比如,根据布局对象(LayoutObject)在指定区域绘制一个红色的矩形。这就是绘制操作。
每一个布局对象(LayoutObject)对应多个待显示项目,因为可能涉及到绘制不同的部分。目前只是记录绘制操作,还没有执行绘制操作。绘制的顺序,受控于z-index
属性。
提交,分割阶段,我们在下面讲🍵
待显示项目列表中绘制的实际操作,是由栅格化进程执行的。
栅格化会将待显示项,转换为颜色值的位图(将图像的信息,以像素为单位记录,记录每一个像素点的颜色信息rgba值)。位图信息保存在内存中(通常是显存中)。gpu也可以将待显示项目列表栅格化,我们称为gpu加速。此时位图信息还保存在显存中,没有输出到屏幕上。
对于图像信息,栅格化会对图像进行解码,获取位图信息。
栅格化的过程是通过SKIA库调用OpenGL API完成的。
SKIA是一个开源的图形引擎,SKIA可以实现栅格化,PDF输出,GPU加速。
draw quads阶段,我们在下面讲🍵
SKIA产生OpenGL调用,使用命令缓存区的形式,传输到GPU进程,GPU接受到命令缓存,产生GL调用。像素被渲染到屏幕上了。
commit(提交)-> tiling(切割)-> raster(栅格化)-> draw quads -> display(显示到屏幕上)
渲染不是静态的,JavaScript,用户输入,异步加载,动画,滚动,缩放,都会改变渲染。从头执行渲染流程代价会很昂贵。如果每一秒低于60fps,就会变得卡顿。
为了避免从头执行整个渲染流程,常用的优化方法就是标记出,发生了改变的部分,复用没有改变的部分。比如一个节点需要重新计算样式,在下一帧的时候,只重新计算该节点的样式。
Chrome另外的优化手段是分层
动画,滚动,缩放,操作都会创建图层。单独的合成线程,会对滚动输入操作,进行单独处理。
while(true) { } 复制代码
当我们使用js阻塞主线程时,由于单独的合成线程的存在,合成线程会对用户的滚动操作进行处理,虽然主线程被阻塞但是页面依然可以滚动
图层的存储结构也是树的形式,图层树间接的基于布局树。
compositing update(输入合成)阶段发生在Layout(布局)阶段之后,Paint(绘制)阶段之前。对页面进行分层,每个图层被分别绘制。绘制阶段存在的待显示项目列表(display items list),其实不同的图层拥有不同的待显示项目列表(display items list)
绘制完成后,通过同步主线程的状态,更新合成线程图层的状态,最后同步回主线程
Raster会将待显示项目列表(display items list)转换为位图,但是有时候图层会很大,Raster又是一个开销很大的步骤,合成线程会将图层分割为图块,图块是Raster的基本单位。距离视口越近,会优先创建图块优先被栅格化。
图块会在单独Raster线程中栅格化
在绘制完所有图块之后,合成器线程将生成draw quads
,draw quads
是绘制图块的指令,draw quads
被包装在合成器框架对象中,提交给浏览器进程。
浏览器进程,运行显示合成器的组件,合成器组件调用OpenGL绘制draw quads
,最后像素在用户的屏幕上可见。