一、内容主要来源于:chromium开发者,Steve Kobes的演讲PPT,也可参考中文(胡文峰)
渲染
一、网页的渲染可以表示为content经过rendering最后呈现的过程,即Code -> 可交互的界面
content是什么
一、可以看到content就是WebContents对象,c++代码的一个类。其代表的区域其实是标签页打开的部分(下图红色部分)。而浏览器进程还包含有地址栏、导航按钮、菜单、扩展,安全提示的小弹窗等。
1、渲染进程render process是一个沙盒,包含Blink渲染排版引擎和Chromium compositor(上图中绿色的CC简写),这么分是因为渲染进程里有主线程和合成线程。
【示例】![]() |
---|
二、content还表示网页内容代码,有HTML,CSS,JS,图片等,还有video,canvas,WebAssembly,WebGL等都可能在content区域显示或者运行
【示例】![]() |
---|
三、综上,content就是网页代码最后运行的结果,浏览器开发者工具可以看到最后是一个经过处理后的HTML的结构。而这个HTML在渲染流水线里是一个输入
【示例】![]() |
---|
像素是如何呈现的
一、写过C/C++代码的同学知道,我们必须使用操作系统提供的底层API去画图,操作系统底层又去调用驱动程序,驱动程序驱动硬件。
今天大多数平台上都提供了“OpenGL”的标准化API。在Windows上有一个额外的DirectX转换。这些库提供诸如“纹理”和“着色器”之类的低级图形基元,并允许执行类似“在这些坐标处绘制一个三角形到虚拟像素缓冲区”之类的底层操作。未来计划用Vulkan替代Skia来做底层图形化调用。
【示例】![]() |
---|
所以渲染流水线的整个过程就是将输入的HTML、CSS、JS转化为OpenGL调用,最后在屏幕上呈现像素
浏览器渲染的目标
一、渲染目标
- 初次渲染,将网页内容转化为底层OpenGL调用去显示页面
- 更新渲染,在JS运行,用户输入,异步请求或者滑动等交互介入后,再次渲染页面起到交互的目的,而且这里再次渲染需要高效执行,你会想到缓存对吗?是的每个阶段的结果为了提高渲染效率而被缓存下来。还有JS API会查询一些渲染数据如某个DOM节点的信息
拆分渲染流水线
一、把渲染管道分成多个阶段的话,可以看出来原来的content内容会被各个阶段stage处理为中间数据,最后才呈现为画面呈现出来。
渲染流水线(简版)
一、整个pipeline:DOM -> style -> layout -> paint -> raster -> gpu,从DOM一直到内存中的像素
二、渲染不是静态的,也不是执行一次就完成了,浏览器会话期间发生的任何事情都会动态更改渲染的过程。并且整个pipeline从头开始运行是非常昂贵的,尽可能希望能减少不必要的工作以提高效率
渲染流水线(全版)
一、渲染流程:构建DOM树(DOM) -> 样式计算(style) -> 布局(layout) -> 分层 -> 绘制(paint) -> 分块 -> 光栅化(raster) -> 合成
一、整个渲染流水线的过程
- 从渲染主线程获取Web内容,构建DOM树,解析样式,更新布局,layer分层后合成合成层,生成属性树,创建绘制指令列表。
- 再到渲染进程合成线程收到渲染主线程commit过来的带有绘制指令和属性树的layer,将layer分块为图块,使用Skia对图块进行栅格化,拷贝pending tree到active tree,生成draw quads命令,将quad发送给GPU进程的Viz线程,最后像素显示到屏幕上。
二、大多数阶段是在渲染进程里执行的,但是raster和display则在GPU进程中执行。
- DOM: 解析HTML生成DOM树
- style: 解析styleSheet生成每个结点的ComputedStyle
- layout: 生成layout tree,跟DOM树基本对应
- 但是display:none的节点不显示直接裁剪,不会出现在LayoutTree
- 伪类,伪元素是在LayoutTree生成的,不是在DOM上
- LayoutEngine在重写,2021年中基本完成,更合理的输入输出分离架构以及更好的性能
- 为了高效处理不同布局类型,一个布局结点下只能是块级元素或者内联元素两种类型的其中之一,某些匿名节点会被创建
- layer分层后合成(compositing assignments): 某些样式属性会单独形成层,如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命令,包含多个DrawQuad的CompositorFrame,这是渲染进程最后的输出,此时屏幕还没有像素出现
GPU进程
raster: 栅格化是将绘制指令paint op转化为位图bitmap的过程,转化后每个像素点的rgba都确定。栅格化还处理图片解码,通过调用不同解码器解压缩图片,GPU可以加速栅格化,通过调用Skia对图块进行栅格化
- Skia: 封装OpenGL调用,提供异步显示列表,最后传递到GPU主线程处理,GPU主线程的Skia后台发起真正的GL调用
- display: GPU Viz线程里的显示合成器display compositor合并多个进程的CompositorFrame输出,并通过Skia发起图形调用,像素呈现在屏幕上