stay hungry stay foolish

webkit学习笔记-(7)渲染基础

RenderObject 树

对于可视节点(head、meta 等为非可视节点),Webkit 会为它们建立相应的 RenderObject 对象,这个对象保存了为绘制 DOM 节点所需要的各种信息。这些 RenderObject 对象同 DOM 节点一样,也会构成一棵树,称为 RenderObject 树。

每个 DOM 对象都会递归调用 attach 函数,该函数检查 Element 对象是否需要创建 RenderObject,如果需要,则调用 NodeRenderingContext 类来创建相应的 RenderObject 节点。

RenderObject 基类和它的主要子类:

Webkit 在某些情况下需要建立匿名的 RenderObject,因为 RenderBlock 的子女必须都是内嵌的元素或者都是非内嵌的元素。

<!DOCTYPE html>
<html>
<head>
	<style type="text/css">
		video,div,canvas{
			-webkit-transform: rotateY(30deg) rotateX(-45deg);
		}
	</style>
</head>
<body>
	<video src="avideo.mp4"></video>
	<div>
		<canvas id="a2d"></canvas>
		<canvas id="a3d"></canvas>
	</div>
	<script type="text/javascript">
		var size = 300;
		var a2dCtx = document.getElementById('a2d').getContext('2d');
		a2dCtx.canvas.width = size;
		a2dCtx.canvas.height = size;
		a2dCtx.fillRect(0,0,200,200);

		var a3dCtx = document.getElementById('a2d').getContext('2d');
		a3dCtx.canvas.width = size;
		a3dCtx.canvas.height = size;
		a3dCtx.clearColor(0,0,192.0/255.0,192.0/255.0,80.0/255.0);
		a3dCtx.clear(a3dCtx.COLOR_BUFFER_BIT);
	</script>
</body>
</html>

DOM 树节点和 RenderObject 树的对应关系:

网页层次和 RenderLayer 树

Webkit 会为网页的层次创建相应的 RenderLayer 对象,当某些类型的 RenderObject 的节点或者具有某些 CSS 样式的 RenderObject 节点出现的时候,Webkit 会这些节点创建 RenderLayer 对象。一般来说,某个 RenderObject 节点的后代都属于该节点,除非 Webkit 根据规则为某个后代节点创建了一个新的 RenderLayer 对象。

RenderLayer 树是基于 RenderObject 树建立起来的一棵新树。RenderObject 节点和 RenderLayer 节点不是一一对应关系,而是一对多的关系。下面这些情况会建立新的 RenderLayer 节点:

  • DOM 树的 Document 节点对应的 RenderView 节点。
  • DOM 树中的 Document 的子女节点,也即 HTM 对应的 RenderOject 节点。
  • 显示的指定 CSS 位置的 RenderObject 节点(position:absolute等)。
  • 有透明效果的 RenderObject 节点。
  • 节点有溢出(Overflow)、alpha 或者反射等效果的 RenderObject 节点。
  • 使用 Canvas 2D 和 3D 技术的 RenderObject 节点。
  • Video 节点对应的 RenderObject 节点。

除了 RenderLyaer 树的根节点,一个 RenderLayer 节点的父节点就是该 RenderLayer 节点对应的 RenderObject 节点的祖先链中最近的祖先。

每个 RenderLyaer 节点包含的 RenderObject 节点其实是一棵 RenderObject 子树。理想情况下,每个 RenderLayer 对象都会有一个后端类,该后端类用来存储该 RenderLayer 对象绘制的结果。

RenderObject 树和 RenderLayer 树的对应关系:

“layer at(x,x)”表示的是不同 RenderLayer 节点,下面所有的 RenderObject 子类对象均属于该 RenderObject 对象。“canvas”元素并没有在第二个 layer 中,虽然该元素仍然是 RenderBody 节点的子女。第二 layer 包含一个匿名的 RenderBlock 节点。第三个 RenderLyaer 对象是在 Webkit 执行 JavasSript 代码时才被创建的,因为 Webkit 需要检查出 JavaScript 代码是否为“canvas”确实创建了绘图上下文,而不是在遇到“canvas”元素时就创建新的 RenderLayer 对象。

渲染方式

在 Wibkit 中,绘图操作被定义了一个抽象层,称为绘图上下文。绘图上下文可分为两种类型,第一种是用来绘制 2D 图形的上下文,称之为 2D 绘图上下文(GraphicsContext);第二种是绘制 3D 图形的上下文,称之为 3D 绘图上下文(GraphicsContext3D)。这两种上下文都是抽象类,也就是说它们只提供接口,因为 Webkit 需要支持不同的移植。而这两个抽象基类的具体绘制则由不同的移植提供不同的实现。

PlatfromCraphicsContext 类和 PlatfromCraphicsContext3D 类是两个表示上下文的类,Webkit 通过 C 语言的 typeof 来将每个不同移植的类重命名成 PlatfromCraphicsContext 和 PlatfromCraphicsContext3D。

每个 RenderLyaer 对象可以被想象成图像中的一个层,各个层一同构成了一个图像。在渲染的过程中,浏览器也可以作同样的理解。每个层对应网页中的一个或者一些可视元素,这些元素的都绘制内容到该层上。如果绘图操作使用 CPU 来完成,那么称之为软件绘图。如果绘图操作由 GPU 来完成,则称之为 GPU 硬件加速绘图。理想情况下,每个层都有个绘制的存储区域,这个存储区域用来保存绘图的结果。最后,需要将这些层的内容合并到同一个图像之中,称之为合成(Composit),使用了合成技术的渲染称之为合成化渲染。

渲染方式一般分为三种:

  • 软件渲染:使用 CPU 来绘制每一层
  • 硬件渲染:使用 GPU 来绘制每一层
  • 合成化渲染:使用 CPU 绘制一些层,使用 GPU 绘制另一些层

在软件渲染的机制中,通常渲染的结果就是一个位图(Bitmap),绘制每一层的时候都使用该位图,区别在于绘制的位置可能不一样,当然每一层都是按照顺序从后到前的顺序,所以软件渲染没有合成阶段。

硬件渲染和合成化渲染都包含了合成的工作,合成的工作都是由 GPU 来做,称之为硬件加速合成(Accelerated Compositing)。对于使用 CPU 来绘制的层,该层首先保存在 CPU 中,之后被传输到 GPU 内存中,用于之后的合成。

软件渲染过程

对于每个 RenderObject 对象,需要三个阶段来绘制自己,第一阶段是绘制该层的背景和边框,第二阶段是绘制浮动内容,第三阶段是绘制前景(子元素)、轮廓(outline)。子元素的绘制同样遵循该机制。

Webkit 第一次绘制网页的时候,Webkit 绘制的区域等同于可是区域的大小。在这之后,Webkit 只是首先计算需要更新的区域,然后绘制和这些区域有交集的 RenderObject 节点。这也就是说,如果更新区域跟某个 RenderLayer 节点有交集,Webkit 会继续查找 RenderLyaer 树中包含的 RenderObject 子树中的特定一个或一些节点,而不是绘制整个 RenderLayer 对应的 RenderObject 子树。如果该层和更新区域重叠,则需要绘制该层所有的 RenderObject。

在 Render 进程中,Skia Canvas 把内容绘制到位图中,该位图的后端即是共享的内存 CPU。当 Borwser 进程收到 Render 进程关于绘制完成的通知消息,Browser 进程会把共享内存复制到 BackingStrore 对象中,然后释放共享内存。

一个多进程的软件渲染过程大致是这样的:RenderWidget类接受到更新请求时,Chrominum 创建一个共享内存区域。然后 Chrominum 创建 Skia 的 SkCanvas 对象,并且 RenderWidget 会把实际绘制任务派发给 RenderObject 树。Webkit 负责遍历 RenderObject 树,每个 RenderObject 节点根据需要来绘制自己和子女到目标存储空间,也就是 SkCanvas 对象对应的共享内存的位图中。最后,RenderWidgetHost 类把位图复制到 BackingStore 对象的相应区域中,并调用“paint”来把结果绘制到窗口中。