目录
一直以来都想了解浏览器合成层的运作机制,但是相关的中文资料大多比较关注框架和开发技术,这方面的资料实在是太少了,后来在chromium
官方网站的文档里找到了项目组成员malaykeshav在 2019年4月的一份关于浏览器合成流水线的演讲PPT,个人感觉里面讲的非常清楚了,由于没有找到视频,有些部分只能自行理解,本文仅对关键信息做一些笔记,对此感兴趣的读者可以在文章开头的github
仓库或附件中拿到这个PPT自行学习。
摘要
1.合成流水线
合成流水线,就是指浏览器处理合成层的工作流程,其基本步骤如下:
大致的流程就是说Paint
环节会生成一个列表,列表里登记了页面元素的绘制指令,接着这个列表需要经过Raster
光栅化处理,并在合成帧
中处理纹理,最后的Draw
环节才是将这些纹理图展示在浏览器内容区。
2. 预定义UI层
chromium中预定义了一些指定类型的UI层,大致分为:
- Not Drawn - 为了处理透明度或滤镜效果、transform变形或者clip剪裁的非绘制层
- Solid color layer - 固有颜色层
- Painted texture layer - Texture纹理会在这个层执行
paint
渲染和后续的rasterized
光栅化任务 - Transferable resource layer - 共享资源层,可能是GPU里面的Texture纹理也可能未来会发给GPU的位图
- Surface layer - 临时占位层,因为自顶向下遍历layer树时子树都还没处理,需要先占位最后再填充
- Nine patch layer - 用于实现阴影的层
3. paint是什么意思
每个层layer
是由若干个views
组成的,所谓paint
,就是每个views
将自己对应图形的绘制指令添加到层的可展示元素列表Display Item List
里,这个列表会被添加到一个延迟执行的光栅化任务中,并最终生成当前层的texture纹理(可以理解为当前层的绘制结果),考虑到传输性能以及未来增量更新的需求,光栅化的结果会以tiles
瓦片形式保存。在chrome中也可以看到页面瓦片化拆分的结果:
4. 分层的优势和劣势
分层的优势和劣势也在此进行了说明,和之前我们主动思考的答案基本一致(暗爽一下)。
5. 视图属性及其处理方式
views
中支持的属性包含Clip
剪裁,transform
变换,effect
效果(如半透明或滤镜等),mask
遮罩,通常按照后序遍历的方式自底向上进行遍历处理。
clip
剪裁的处理方式是在父节点和子节点之间插入一个剪裁层,用来将其子树的渲染结果剪裁到限定的范围内,然后再向上与父级进行合并;
transform
变换直接作用于父节点,处理到这个节点时其子树都已经处理完毕,直接将整体应用变形即可;
effect
效果一般直接作用于当前处理的节点,有时也会产生交叉依赖的场景;
PPT第40页中在介绍effect
效果处理时描述了两种不同的透明度处理需求,从而引出了一个Render Surface
的概念,它相当于一个临时的层,它的子树需要先绘制在这个层上,然后再向上与父节点进行合并,屏幕就是是根级的Render Surface
。
6. Quads
Layer
遍历处理输出的结果被称为Quads
(从意思上理解好像就是指输出了很多个矩形方块),每个quad
都持有它被绘制到目标缓冲区所需要的资源,根据它持有的资源不同可以分为:
Solid Color
-固定颜色型Texture
- 纹理型Tile
- 瓦片型Surface
- 临时绘图表面型Video
- 视频帧型Render Pass
-Render Surface
类型的占位区,Render Surface
子树处理完后填充到关联的Render Pass
7. Compositor Frame
合成层真正的工作要开始了,主角概念Compositor Frame
(合成帧)登场,它负责将quads
合并绘制在一起,胶片里59-62页非常清楚地展示了合成的过程,最终输出的结果就是根节点的纹理。
chromium
是多进程架构,Browser Process
浏览器进程会对菜单栏等等容器部分的画面生成合成帧来输出,每个网页的Render Process
渲染进程会对页面内容生成合成帧来输出,最终的结果都被共享给GPU Process
GPU进程进行聚合并生成最终完整的合成表面,接着在Display Compositor
环节将最后的位图展示在屏幕上。
8. 关于光栅化以及渲染方式
胶片里并没有描述具体的光栅化的处理过程,但是layer
输出的quads
看起来应该是光栅化以后的结果,推测应该是处理Display Item List
中的绘图指令时也和WebGL类似,经过顶点着色器
和片元着色器
的遍历式处理机制,并在过程中自动完成像素插值。
9.【重要】软件渲染和硬件渲染的区别
声明:本节内容是个人理解,仅用作技术交流,不保证对!
软件渲染和硬件渲染的区别对笔者而言一直非常抽象,只是知道基本概念。后来在【chromium开发者文档】(国内可能无法访问)中《Compositor Thread Architecture》这篇合成器线程架构的文章中找到了一些相关描述,也解开了笔者心中一直以来的疑惑,相关部分摘抄如下:
大概翻译一下,方便英语水平一般的小伙伴理解,GPU处理图片的方式是按照Texture进行贴图的,对此不熟悉的小伙伴可以查看笔者以前发的有关Three.js
相关的博文。
概念比较多没有基础的读者可能理解起来有难度,我尝试用自己的话复述一下:
【软件渲染】的模式下,在paint
时会直接利用Graphics Context
绘图上下文将结果绘制出来,在一个SkBitmap
实例中保存为位图信息;【硬件渲染】的模式下,在paint
时传入一个SkPicture
实例,将需要执行的绘图命令保存在里面先不执行,然后通过共享内存将它传给GPU进程,借助GPU来最终去执行绘图命令,生成多个瓦片化的位图纹理结果(OpenGL
中顶点着色器向片元着色器传递数据时可以自动进行数据插值,完成光栅化的任务)。 纯软件渲染里严格说是没有合成层概念的,因为最终输出的只有一张位图,按照顺序从下往上画,和画到一个新层上再把新层贴到已有结果上其实是一样的。
不管使用哪种途径,paint
动作都是得到位图数据,而最终的draw
这个动作是借助OpenGL和位图数据最终把图形显示在显示器上。