Chromium 软件渲染

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

本章将介绍chromium渲染的最基础部分,同时也是最常见的部分-软件渲染。故名思路,软件渲染就是利用CPU,根据一定的算法来计算生成网页的内容,其没有那么多复杂难懂的概念和架构。如果了解了那么多地关于硬件加速的知识后,你可能会觉得本章的内容非常的简单明了,是的,你没猜错。 在绝大多数的情况下,也就是没有所谓地那些需要硬件加速内容的时候(包括但不限于CSS3 3D transformation, CSS3 3D transition, Canvas 2D, WebGL和Video),Chromium都是用软件渲染的技术来完成页面的绘制工作(除非你强行打开硬件加速绘制),基本上你浏览地大多数门户网站,论坛网站,社交网站等等的网页,都是采用这项技术来完成页面的生成。可以这么说,传统的网页都是软件渲染,用到了硬件加速的新网页通常都是加入了很多眩目视觉效果的网页(当然,这不是绝对的)。 本章首先详细了解软件渲染的基础设施和架构,然后介绍渲染的过程,最后并与硬件加速渲染作一比较,介绍一下各自的优点和局限性。

软件渲染基础和架构

前面介绍了WebKit中的渲染基础,这些设施在这里也同样需要,不过,我不打算在这里重新详细介绍它们。下面详细介绍的是Chromium中支持软件渲染的基础设施及其架构。

先来看看Renderer进程。同硬件加速渲染一样,RenderWidget对象是必不可少的,它不仅负责调度页面渲染和页面更新等操作,而且负责同Browser进程的通信。另一个重要的设施是PlatformCanvas,也就是SkiaCanvas,Render树的绘制操作最后都是调用Canvas的元素操作来实现。

再来看看Browser进程。第一个设施就是RenderWidgetHost对象,一样的必不可少,负责同Renderer进程的通信。第二个是BackingStore,顾名思义,它就是一个后端的存储空间,它的大小通常就是viewport也就是网页可视区域的大小,该空间存储的就是页面的绘制结果。

最后来看看这两个进程是如何传递信息和绘制内容地。传递消息还是之前介绍过的消息机制,而绘制的结果则是通过TransportDIB类来完成,该类在Linux系统下其实是一个共享内存的实现。对Renderer进程来说,Skia Canvas把内容绘制到bitmap中,该bitmap的后端即是共享内存。对Browser进程来说,当绘制完成后,它会把共享内存的内容复制到自己的backingstore中。

下图显示的是软件渲染的架构图,其主要来源于chromium的官方网站,但这里做了一些扩充。 enter image description here 其组成部分上面已经介绍过,那么一个大致的过程是这样的:RenderWidget接收到更新请求时,创建一个共享内存区域,然后创建skia canvas,之后RenderWidget会把实际绘制的工作派发到Render树中来完成,具体上来讲,也就是WebKit负责遍历Render树,每个RenderObject节点根据需要来绘制自己和子女的内容到目标存储,也就是SkiaCanvas所对应的共享内存的Bitmap中。之后,RenderWidgetHost把bitmap复制到backingstore的相应区域中,并调用paint来把自己绘制到窗口中。下面详细介绍这个过程。

渲染具体过程

依照惯例,我们先来看一个简单的HTML例子,里面包含简单的元素和一小段JavaScript代码,它会每隔50ms请求更新id为”content”的元素的内容。 enter image description here 后面我们会讲它在哪些时候请求绘制网页内容,这里先了解一下很多情况下会发起重新绘制某些区域的请求,大概可以分为两类: 1) 前端请求:该类包括从browser进程发起的请求,可能是browser自身的,也有可能是X(或者其他窗口系统)。 2) 后端请求:由页面自身发起更新部分区域的请求,例如HTML元素或者样式的改变,动画等等。上面的例子中, JS代码每隔50ms更新元素,所以它会触发更新部分区域。 下面来解释一下当有绘制或者更新某个区域请求时,Chromium和WebKit如何来处理。过程如下图所示,下面阐述一下大致的步骤。

1) Renderer进程的message loop调用处理Invalidation的回调函数,该函数主要调用RenderWidget::DoDeferredUpdate来完成绘制请求。

2) RenderWidget::DoDeferredUpdate首先调用layout来触发检查是否有需要重新计算的布局和更新请求。

3) RenderWidget调用TransportDIB来创建共享内存,内存大小为绘制区域的高×宽×4,同时调用Skia来创建一个canvas,它的绘制目标是一个使用共享内存存储的bitmap。

4) 当渲染该页面的全部或者部分时,ScrollView请求按照从前到后顺序遍历并绘制所有的RenderLayer的内容到目标的bitmap中,每个RenderLayer的绘制通过以下步骤来完成:首先计算重绘的区域是否和自己有重叠,如果有,则要求该layer中的所有RenderObject对象绘制自己。这在上图中省略了该部分的具体内容,详情参考代码。

5) 绘制完成后,发送UpdateRect的消息给browser进程,Renderer进程同时返回完成绘制。Browser进程接受到消息后首先由BackingStoreManager来获取或者创建BackingStoreX(在linux平台上),BackingStoreX大小是Viewport(可视区域),包含相对于整个网页的坐标信息,它根据UpdateRect的更新区域的位置信息将共享内存的内容绘制到自己的对应存储区域中。

6) 最后Browser进程发送UpdateRect的ACK消息给renderer进程,完成整个过程。

enter image description here

那么上述的HTML例子中大概有哪些绘制请求呢?

1) 当初始打开该HTML页面时,首先会由browser进程发起要求更新整个可视区域的重绘请求,后面的绘制过程如上所述。

2) 后面,每隔50ms,chromium执行JS代码,JS代码修改了HTML元素,该动作触发重新计算布局,因而发起更新某块小的区域的请求。

同硬件加速渲染的对比

我们可以看到,软件渲染有一个重要的影响性能的地方来自于共享内存后的复制到backingstore的操作,那么为什么需要将共享内存的内容复制到backingstore中,而不是直接使用呢?我认为这里面有两点重要的原因:

第一, 共享内存是很宝贵的资源。通常,操作系统对共享内存的大小和总量有一定的限制,因而可以使用的量比较小(相对于虚拟地址空间)。chromium浏览器可能包含多个Renderer进程,这些进程如果都申请这么多的共享内存,这将是一个极大的浪费,鉴于此,在使用完后,因尽快地释放共享内存。

第二, 重新绘制网页内容的需要。当X有请求更新浏览器窗口的某个区域时,在此情况下,网页内容可能没有任何的改变,因而如果在浏览器端有一个backingstore保存网页的内容,那么不需要WebKit(Renderer进程)重新渲染,这将会是一个很费时的工作,这在某种程度上可以提高性能。

软件渲染同硬件加速渲染另外一个很不同的地方就是对更新区域的处理。回忆一下之前介绍的硬件渲染部分,当网页中有一个更新某个区域的请求时(例如动画),硬件渲染可能只需要对其中的一层重新绘制,然后重新合成即可。但是,在软件渲染过程中,因为它没有为每一层提供后端存储,因而它需要将和这个区域有重叠部分的所有层次重新绘制一遍,因而某些情况下开销比较大。

上面说了之后,可能会觉得软件渲染不那么有用,其实不然,大多数情况下,软件渲染是有其优势所在的,

第一, 很多场景适合用软件渲染,而且速度更快。一般的网页,用软件渲染速度更快,用了硬件加速这个“牛刀”可能会造成较大地额外负载,一般网页的文字和图片对现代CPU来说不是什么大问题,处理起来那是相当地快。

第二, 相比较硬件加速渲染,它不需要GPU内存和其他资源。硬件加速渲染需要很多的GPU内存资源,因为它会每个层分配内存对象,很多时候GPU资源很紧张。

源文件目录

content/renderer/
         Renderer进程端与Browser进程通信的设施
content/browser/
         Browser进程端与Renderer进程通信的设施
ui/gfx/surface
         包含TransportDIB类,用来申请和管理共享内存

参考文献

  1. GPU加速合成: http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome
  2. 渲染过程图: http://www.chromium.org/developers/design-documents/rendering-architecture-diagrams