硬件加速之 RenderLayer 树到合成树

优质
小牛编辑
129浏览
2023-12-01

在前面的章节中,笔者介绍了WebKit渲染引擎是如何有HTML网页构建DOM树、RenderObject树到RenderLayer树,以及它们之间的对应关系。这一渲染基础被软件渲染和硬件渲染共享,也就是说不管哪种类型的渲染机制,WebKit渲染引擎都会构建它们。不过对于软件渲染而言,到RenderLayer树就结束了,后面不会建立其它额外的树来对应于RenderLayer树。但是,对于硬件渲染来说,这是漫长阶段的一个部分,在RenderLayer树之后,WebKit渲染引擎为硬件渲染提供了更多的内部结构来支持这个机制。本文将在介绍WebKit中的内部结构之上,同时介绍Chromium又构建的新内部结构,用于该项目中引入的众多纷繁复杂的技术,例如线程化合成器,线程化绘图等等。

WebKit基础设施

一个RenderLayer对象如果需要后端存储,它会创建一个RenderLayerBacking对象,该对象负责Renderlayer对象所需要的各种存储。正如前面所述,理想情况下,每个RenderLayer都可以创建自己的后端存储,事实上不是所有RenderLayer都有自己的RenderLayerBacking对象。如果一个RenderLayer对象被像样的创建后端存储,那么将该RenderLayer称为合成层(Compositing Layer)。 哪些RenderLayer可以是合成层呢?如果一个RenderLayer对象具有以下的特征之一,那么它就是合成层: 1. RenderLayer具有CSS 3D属性或者CSS透视效果。 2. RenderLayer包含的RenderObject节点表示的是使用硬件加速的视频解码技术的HTML5 ”video”元素。 3. RenderLayer包含的RenderObject节点表示的是使用硬件加速的Canvas2D元素或者WebGL技术。 4. RenderLayer使用了CSS透明效果的动画或者CSS变换的动画。 5. RenderLayer使用了硬件加速的CSSfilters技术。 6. RenderLayer使用了剪裁(clip)或者反射(reflection)属性,并且它的后代中包括了一个合成层。 7. RenderLayer有一个Z坐标比自己小的兄弟节点,该节点是一个合成层。 至于为什么这么做,不外乎两个原因,第一当然是合并一些RenderLayer层,这样减少内存的使用量,其二是在合并之后尽量减少合并带来的重绘性能和处理上的困难,其三同时对于那些使用单独层能够显著提升性能的RenderLayer对象,继续使用这些好处,例如使用WebGL技术的canvas元素。 每个合成层都有一个RenderLayerBacking,RenderLayerBacking负责管理RenderLayer所需要的所有后端存储,因为后端存储可能需要多个存储空间。在WebKit中,存储空间使用类GraphicsLayer来表示。 下图就是WebKit构建的从RenderLayer树到RenderLayBacking树,再到GraphicsLayer树,并给出这些硬件加速基础设施的对应关系。在RenderLayer树中的第四个节点没有创建RenderLayerBacking对象,因为不符合上面的创建条件,而对于每个RenderLayerBacking对象,它也至少需要一个GraphicsLayer对象,当然也可能需要多个,图中的RenderLayerBacking分别需要2个,1个或者4个GraphicsLayer对象,这些对象分别表示什么呢?

enter image description here

为什么一个RenderLayerBacking对象需要这么多层呢?原因有很多,例如是需要将滚动条独立开来称为一个层,需要前景层、背景层等,需要两个容器层来表示RenderLayer对应的Z坐标为正数的子女和Z坐标负数的子女,将可能需要滚动的内容建立新层,还有剪裁层和反射层,那么这些层次是如何组织并且它们的绘制顺序是如何呢?上图中的GraphicsLayer树状结构按照一定顺序,具体可以参考于Chromium工程师的文档”Compositing in Blink / WebCore: FromWebCore::RenderLayer to cc:Layer”,图中每个层就是一个GraphicsLayer对象,对于某个RenderLayerBacking对象来说,其中主层是肯定存在的,其它层则不一定存在,因为不是每个RenderLayer对象都需要用到它们。 这其中还有众多的其它层,这里不一一介绍了。

Chromium基础设施

GraphicsLayer是对一个渲染后端存储的抽象,同众多其它WebKit所定义的抽象类一样,在WebKit移植中,它还需要具体的实现类来支持该类提供的功能。为了完成这一功能,Chromium提供了更为复杂的设施类,这一节主要介绍从GraphicsLayer类到合成器这一过程中所设计的众多内部结构。 下图中描述了从WebCore中同移植无关的GraphicsLayer到之后Chromium移植及Chromium浏览器所设计的Chromium合成器的LayerImpl类这一过程,读者可以看到,中间有好几层,原因在于抽象和合成机制的复杂性,以及性能等众多方面的考虑,从上到下主要类依次是: GraphicsLayerChromium:GraphicsLayer的子类,实现了GraphicsLayer需要的一个功能,并且加入了Chromium的所需信息。 WebLayer:WebKit的Chromium移植的抽象接口类,它被GraphicsLayerChromium等调用,主要目的是将Chromium的实际后端存储类抽象出来,以便WebCore使用它们。 WebLayerImpl:WebLayer类的实现类,具体作用是将合成器的层能力暴露出来,跟Layer类一一对应。 Layer:合成器的层表示类,是Chromium合成器的接口类,用于表示合成器的合成层,它会形成一颗合成树。 LayerImpl:同Layer对象一一对应,是实际的实现类,包含有后端存储,可能更Layer树在不同的线程,具体在后面介绍。 由上面介绍可以看出,这个过程基本上就是各种类的映射,从GraphicsLayer类到LayerImpl类,目的是将WebKit的合成层映射到合成器中的合成层,合成器最终合成这些层。合成过程在合成器小节中介绍。

enter image description here

Chromium合成器

合成器的作用就是合成多个合成层,所以它的输入是多个待合成的合成层,每个层都有一些属性(例如3D变形等)。它的输出就是一个后端存储,例如是一个GPU的纹理缓冲区。 Chromium合成器是一个独立并且复杂的模块,顾名思义,它的作用是合成网页划分后的合成层,但是,这里的合成器同网页没有必然的绑定关系,它既可以合成网页,也可合成用户界面,或者多个标签页。其实,按照笔者的理解,如果你的项目中需要合成器,可以尝试移植该合成器为自己所用,当然,该合成器有一些依赖关系需要解除,难度也很大,这些都是题外话。 在架构设计上,合成器采用将表示和实现分离开的原则,也就是前面介绍合成器Layer层(同GraphicsLayer类一一对应)同具体合成器所要合成的操作分离的原则,下图描述了这一思想。WebKit中对合成层的各种设置,最后都使用Layer树来表示,里面包含3D变形、剪裁等属性,但是将这些属性应用到后端存储并合成这一过程并不是在Layer树中进行,而是将这些功能委托LayerImpl树来完成,两者之间通过代理来同步。Layer树所有的信息都会复制到LayerImpl树中,代理的作用是协调和同步两者之间的这些操作。

enter image description here

上图中描述的Layer树工作在主线程,但是LayerImpl树在实现部分的线程,这里可以它理解为就是工作在主线程也可是是单独的一个线程,两者在Chromium中目前都被使用。实现部分作为单独一个线程是在Renderer进程中用来合成网页的时候,通常也称为合成器(compositor)线程,后者也称为线程化合成(threaded compositing)。在Chrome的Android版本,合成实现部分则是在主线程,目的是同浏览器界面的合成,称为线程内合成(in-thread compositing)。 在目前的Chromium合成器中,具体的工作非常复杂,例如还有线程化绘图(impl side painting),远远超出了文章"Chromium硬件加速合成"章节中介绍的合成器过程,希望以后可以在专门的章节中来介绍它们。

参考资料

  1. https://docs.google.com/presentation/d/1dDE5u76ZBIKmsqkWi2apx3BqV8HOcNf4xxBdyNywZR8/edit?pli=1#slide=id.g9ade3ed5_017

By yongsheng@chromium.org