Chrominum CC 合成器文档学习记录
Chrominum CC 合成器文档学习记录
前言
笔者现在需要准备查看一下CC这个目录,为此,就需要一些时间来准备阅读文档,从而更好的理解我们的代码
Chrominum的源码非常庞大,一个办法是下载Chrominum的源码学习,但是笔者要告诉你大约70多个G,编译后也要再增加小100G,所以另一个办法就是查看在线文档,比较轻量。
链接:cc/
所以,Chrome的合成器在做什么,为什么需要他?
文档的第一行话就是:This directory contains a compositor, used in both the renderer and the browser. In the renderer, Blink is the client. In the browser, both ui and Android browser compositor are the clients.
翻译一下:这个文件夹下包含了一个合成器的代码,不光用在渲染器,还用在浏览器(笔者提示:这里说的是浏览器本身,比如说您浏览器上的地址栏,导航栏,网页标签那些的)。对于渲染器,Blink是客户端(笔者再注:这里的客户端后面我会再解释),对于浏览器(本身),UI 和 Android 浏览器合成器都是客户端。
回答这个问题之前,我们需要回顾一下,Chrominum内核为几乎绝大多数浏览器都提供了不少功能性的支持。最重要的一个功能就是让我们可以surf the Internet for free,而且需要存在一定的安全保障和流畅度的基本需求。
所以,为什么分出来多个客户端是可以理解的:
第一个方面就是稳定性 (Stability),我们不希望一个很烂的前端程序员直接写出来很烂的JavaScript把我们的浏览器干崩了,大家都知道,网页如果出现了问题,只会显示一个:“喔,崩溃了!”。你的浏览器“外壳”和其他所有标签页安然无恙,因为浏览器进程没有崩溃,我们只会骂两句什么玩意后继续浏览器其他的网页。
再就是响应速度,比如说一个网页陷入了某种奇妙的无限循环,CPU 占用 100%,页面卡死。由于我们的生成渲染指令Commit的Clients是隔离开的,不影响我们点击地址栏(属于浏览器进程B)、切换到其他标签页,或者关闭那个卡死的标签页。UI 始终是流畅的。
而,现代网页是非常复杂的,为此,一个有效的缓解复杂性的办法就是pipeline化,我们的浏览器渲染界面实际上分出来了好几个可以并行化的steps。解析HTML + CSS + JS,生成Layout提交到逐步分解为GPU可以理解的渲染指令。中间有一步很重要,那就是图层化。
我们知道一个复杂的图像是由背景、人物、文字等多个图层叠加而成的。现代浏览器用同样的逻辑处理我们的网页,对于浏览器而言,网页不是一个单一的平面图像,它被分解为多个图层。例如:一个视频播放器可能在一个图层,背景在另一个图层,一个CSS动画元素在它自己的图层,页面上的文字又在另一个图层。
合成器的工作,就是将这些独立的图层收集起来,按照正确的顺序,位置和透明度,把它们组合(“合成”)成一帧完整的图像,最后显示在你的屏幕上。
为什么要怎么做?答案还是回到流畅度上,我们现代的设备都是有GPU加速渲染的,这没有任何疑问。合成器就是准备提交给GPU告诉他你要做什么动作。为了最小重绘,我们就锁定到底是哪一个Layer需要重绘,在上面计算藏矩形后提交相应的重绘指令就好了。比如说,你对Chrome CC浏览文档鼠标向下滑动,但是很显然,顶部的地址栏,标签页,还有其他的GuidePanel是不用动的,他们就被分解到不同的层,只需要改动内容Content层,让他向上移动200个像素,把这些内容重新合成出来交给GPU绘制。现代GPU如此特化这一流程的基本组合,以至于我们完全不需要 CPU 重新计算和绘制整个页面的所有像素。这就是为什么现代浏览器的滚动、缩放和CSS动画如此流畅的原因。
回到我们的浏览器架构上,为了独立性(直指稳定性),浏览器本身作为主进程,背后托管了一系列派生的子进程也就是渲染进程,分别负责不同的内容。
对于主进程,负责管理整个应用程序的“外壳”,比如地址栏、标签页、前进/后退按钮、收藏夹栏等。它还负责网络请求、安全和管理所有其他进程。对于渲染器进程,需要注意的是,每个标签页通常都有一个自己独立的渲染器进程。它负责“内陷”的工作:解析 HTML、运行 JavaScript、计算 CSS 布局,以及真正把网页内容画出来。
所以,我们回到开始文档的第一句话,他的意思很简单了,这个CC就是上面所说两类进程的后台大佬,对于网页渲染器进程而言,Blink是客户端,他是我们Chromium 的渲染引擎(是渲染器进程中的核心组件)。可以快速的讲,Blink 解析完网页后,它会把网页内容分解成很多图层(比如文字图层、图片图层、视频图层)。然后,Blink 作为“客户端”,把这些“网页内容图层”打包,发送给“合成器服务”,并说:“请把这些图层帮我合成到屏幕上。”
对于浏览器本身,也就是桌面端的内容上,我们的地址栏、标签页、按钮等也是用图层绘制的。而且没有例外的是,也会发送到这里嗷嗷待哺准备进行进一步的分解为GPU可以识别的指令提交上去。
需要注意的是,我们下面准备讨论的就是Layer树的合成,这也是合成器的公共 API。他暴露了LayerTreeHost 和 Layer 及其派生类型。嵌入器会创建一个 LayerTreeHost(单线程、多线程或同步),然后将一个 Layer 树附加到它。
笔者开始的时候理解的很费劲,但实际上是这个意思,我们想一下,现在Blink已经处理好他们的内容,我们的UI完成了Paint准备提交模块到下一个接力者,离我们的内容上GPU更进一步。我们的CC需要提供一些操作模块,让这些对接人操作我们的模块成准备进一步提交的Layer树。
谁和 GPU 真正打交道是在合成器端的实现(通常是派生出的 LayerTreeHostImpl / LayerTreeImpl 与 LayerImpls)。这些结构运行在独立的合成器线程(或 GPU 进程)上,这样主线程可以继续处理交互与更新而不被阻塞。主线程只需要异步地更新它自己的 LayerTree,合成器线程会在后台把这些更新转换成适合 GPU 的表示并上传。换句话说,UI/主线程负责“准备”和“提交”层级结构,合成器线程和 GPU 负责“变成像素并显示”。
pipeline化似乎是Chrome很喜欢的技巧,我们这里也多加了一个缓冲层——在多线程模式下,提交并不是直接替换正在绘制的那棵树,而是先把主线程的更新转换到一个“待处理树(pending tree)”——这是一个供光栅化使用的暂存版本。待处理树的存在是为了保证主线程所有的更新能以原子、一致的方式呈现给用户,同时上一帧仍可以继续滚动或动画。光栅化等操作足够完成后,待处理树会被“激活”到活动树(active tree),激活动作类似提交:它把待处理树的数据推到活动树上,活动树才是真正用于绘制的树。单线程合成器的路径则更简单,直接向活动树提交,然后等待内容就绪再绘制。
绘制的实际工作由活动树上的 LayerImpl 来完成,合成器内部通过 LayerImpl::AppendQuads 把每个 layer 的绘制内容转换成一组 DrawQuads 和 RenderPasses,批量打包成 CompositorFrame。这些 CompositorFrame 通过 CompositorFrameSink 发出,进入显示管线。来自不同合成器或不同来源的帧会被送到 SurfaceManager(通常在 GPU 进程里),当显示端请求刷新时,SurfaceAggregator 会把这些 CompositorFrame 组合成最终的合成结果,最后交给诸如 viz::DirectRenderer 的渲染器去把合成后的内容真正画到屏幕上。
整个流程由调度器(Scheduler 与其状态机 SchedulerStateMachine)来控制时序:决定什么时候提交、什么时候激活、什么时候绘制,保证帧的时间点和资源协调。这样设计的好处很明显——UI/渲染职责明确,合成器与 GPU 在独立线程或进程上运行以避免卡顿,待处理树→激活→活动树的流程确保主线程的更新能一次性、原子地呈现,同时不打断正在进行的滚动或动画,最终通过 SurfaceManager/SurfaceAggregator 把多个来源的内容合并出一帧交由渲染器绘制,完成从内容到屏幕的闭环。
