《WebKit 技术内幕》之八(3):硬件加速机制

3 其他硬件加速模块

3.1 2D图形的硬件加速机制

其实网页中有很多绘图操作是针对2D图形的,这些操作包括通常的网页绘制,例如绘制边框、文字、图片、填充等,它们都是典型的2D绘图操作。在HTML5中,规范又引入了2D绘图的画布功能,它的作用是提供2D绘图的JavaScript接口,所以JavaScript代码可以很容易地调用该接口来绘制任意的2D图形。2D绘图本身是使用2D的图形上下文,而且一般使用软件方式来绘制它们,也就是光栅化(Rasterize)的方法。但是,其实这些2D绘图操作也可以使用GPU也就是3D绘图来完成,这里把使用GPU来绘制2D图形的方法称为2D图形的硬件加速机制。

如上面所述,目前2D图形的硬件加速有两种应用场景,第一种就是网页基本元素的绘制,针对的层次类型其实在前面描述过,也就是ContentLayer,读者应该记得它的后端是一个2D的画布对象;第二种就是HTML5的新元素canvas,用来绘制2D图形。

3.1.1 2D图形上下文

第7章中介绍了WebKit中的2D图形上下文,该上下文在WebKit的Chromium移植中需要使用Skia图形库来完成2D图形操作,图8-26描述了WebKit的Chromium移植中2D图形上下文的实现类。

图WebKit的Chromium移植使用Skia来绘制2D图形

在上图中,对于WebKit需要使用GraphicsContext的地方,Chromium会创建一个Skia图形库中提供的SkCanvas对象来处理WebKit的2D图形操作请求。至于这个SkCanvas对象是使用软件绘图还是GPU绘图,取决于对SkCanvas对象的设置。SkCanvas类表示的是一个画布,2D的图形操作都是在这个画布上处理,绘制结果也是保存在SkCanvas对象中。如果调用者需要使用软件方式来绘图,如图中左半部分所示,那么调用者需要创建一个基本的SkDevice对象,该对象使用光栅扫描的方法来一一计算绘制的像素结果,并把结果存入SkBitmap对象中。SkBitmap对象使用一块CPU内存,该内存中保存的是一个个像素值,典型的例如RGBA格式。

如果调用者需要使用GPU硬件来进行绘图,那么在创建SkCanvas对象的时候,通过传入SkSurface_Gpu对象即可。当然创建SkSurface_Gpu对象需要很多其他的对象,最重要的是SkGpuDevice对象,它是SkDevice的一个基类,同原先软件方式不同的是,它是将2D图形操作转变成对GL的操作,使用GrContext的3D图形上下文来绘制,并将结果存储在GrRenderTarget,该存储目标是GPU的内存缓冲区。

从上面的讨论可以看出,WebKit调用GraphicsContext对象的时候,WebKit根本不知道下层实际使用的是软件还是GPU来绘制2D图形,这一切都是由Skia图形库来完成的,当需要启动硬件加速的时候,Chromium只需要为SkCanvas对象设置相应的对象即可。

3.1.2 Canvas 2D

"canvas"是HTML5中新加入的元素,在最开始的时候它只是一个2D画布对象,网页开发者可以使用规范定义的JavaScript接口在画布上绘制任意的2D图形,这样的技术我们称之为Canvas 2D。不过,Khronous组织提出可以在该元素上使用JavaScript接口绘制3D图形,这样的技术我们称之为WebGL或者Canvas3D,这个稍后会做介绍。一个"canvas"元素的对象只能绘制2D图形和3D图形中的一种,不能够同时绘制这两者。

"canvas"元素的"getContext"方法包含一个参数,该参数用来指定创建上下文对象的类型。对于2D的图形操作,通过传递参数值"2d",浏览器会返回一个2D图形上下文,称为CanvasRenderingContext2D,它提供了用于绘制2D图形的各种应用程序编程接口,包括基本图形绘制(如线、矩形、圆弧)、文字绘制、图形变换、图片绘制及合成等。

前面说到,CanvasRenderingContext2D是2D图形绘制的上下文对象,其提供了用于绘制2d图形的API,W3C工作组起草了标准的草案。该对象由JavaScript代码调用"getContext()"函数创建,Web开发者便可以调用它的编程接口在画布上绘制2D图形了。这些编程接口主要的作用就是在画布上绘制点、线、矩形、弧形、图片等,除此之外,还提供了这些绘制的样式和合成效果等。示例代码8-3给出了使用Canvas2D技术的基本方法。

Canvas 2D可以使用软件方法来绘图,也可以使用GPU来加速绘图,根据前面介绍的2D图形上下文和Chromium中使用Skia图形库来绘图的方法,网页的Canvas 2D技术同样需要借助Skia这一技术。图8-27描述了Canvas 2D使用GPU来绘图所涉及的一些主要类。对于软件绘图来说,Chromium的工作过程实际上更简单一些,图中ImageBuffer只是使用SkCanvas、SkDevice和SkBitmap等类,这一过程其实不如硬件加速绘图复杂,所以这里不再赘述。

示例代码(使用Canvas2D技术的网页代码)

图(使用GPU硬件绘图的Canvas2D技术)

HTML5的Canvas2D机制使用了一个GraphicsContext对象,也就是2D图形上下文,同时还包括一个ImageBuffer对象来表示canvas绘制的结果,这里软件绘图和GPU硬件加速绘图没有什么大的不同。回到GPU硬件绘图上来说,如果使用硬件加速机制的话,Chromium会创建一个SkDeferredCanvas对象,该对象的特别之处在于它采用延迟机制来绘制2D图形,随后介绍。该对象当然需要SkGpuDevice来将2D绘图操作转换为使用3D图形上下文来绘制,这一过程跟上一小节介绍的非常类似。笔者需要强调的是图中的Canvas2DLayerBridge类,它是一个桥接类,因为实际上2D图形是使用3D图形接口绘制的,所以Chromium需要3D图形上下文和一些准备工作,这些都是在该类中完成。

WebGraphicsContext3DCommandBufferImpl类之后的部分跟图8-12介绍的过程完全一样,在这种情况下,上层是2D绘图还是3D绘图已经完全没有差别了。

下面用三个阶段来描述Chromium是如何使用硬件加速绘图来支持HTML5的Canvas2D功能的,以示例代码8-3作为例子加以说明。首先看第一阶段。

第一阶段这里称为初始化阶段,也就是示例代码(使用Canvas2D技术的网页代码)中调用"fillStyle"的阶段,因为该函数会触发建顺序图(Canvas2D初始化阶段的对象创中这些对象的创建。基本上,这一阶段的代码需要WebKit和Chromium创建图8-28所涉及类的对象,读者可以理解一下它们的顺序。其中GraphicsContext类主要是被CanvasRendering-Context2D类所使用,而GraphicsContext3D类是被Canvas2DLayerBridge类使用。这里面还需要强调的一点就是合成器中的CC::Layer(还包括WebLayer,限于图片太大,没有画出),它是由Canvas2DLayerBridge类创建,这听起来有点奇怪,因为CC::Layer类和GraphicsLayer类是一一对应的。不过没关系,因为在这里,"canvas"元素对应的RenderLayer对象还没有被创建,它在第二阶段才会创建,这是为什么呢?回想之前的介绍,在DOM创建过程中,当WebKit构建canvas元素的对象时,并没有为它创建RenderLayer对象,因为这是延迟执行的。如果"canvas"元素没有创建2D或者3D图形上下文,它是不需要RenderLayer对象的,当然也就没有RenderLayerBacking对象,更没有GraphicsLayer对象。同时,这段JavaScript代码在DOM构建过程中会被调用,这就造成了上面所述的这种情况。

建顺序图(Canvas2D初始化阶段的对象创

第二阶段是WebKit构建RenderLayer等对象。在DOM树构建完之后,WebKit会检查有无变化的CSS样式,在这里JavaScript代码改变了canvas元素的属性,所以WebKit会更新RenderObject树和RenderLayer树。图(为Canvas元素创建RenderLayer等并设置GraphicsLayer)描述了这一阶段。

图(为Canvas元素创建RenderLayer等并设置GraphicsLayer)

在这里,笔者不想重复RenderLayer等对象的创建,而想重点强调GraphicsLayer对象如何设置自己的WebLayer成员变量。方法是这样的:WebKit中的RenderLayerBacking对象检查是否是canvas元素,如果是,RenderLayerBacking对象从HTMLCanvasElement对象中获得CanvasRenderingContext2D对象,这一上下文对象的platformLayer函数能够得到之前创建的WebLayer对象,这样WebKit就建立了RenderLayer和GraphicsLayer的映射关系。为了简洁起见,图中省略了一些步骤。

第三个阶段就是绘图部分。图8-30详细描述了这一思想和主要过程。Chromium采用缓存模式来处理JavaScript代码的2D图形操作,也就是说,当JavaScript通过标准的接口调用2D图形的时候,Chromium使用SkDeferredCanvas对象保存2D图形操作,当Chromium需要绘制一个新帧的时候,Skia图形库才会一次性提交并绘制这些缓存的操作。

图(Chromium中的Canvas2D绘图过程)

先看图中的上半部分,当JavaScript调用2D绘图接口时需要使用contextAcquired()函数获取2D图形上下文,Chromium据此判断后面修改画布的内容,所以Chromium会使用Canvas2DLayerManager类来设置一个TaskObserver对象到主消息循环,这样做的好处是等到JavaScript代码调用2D绘图接口之后,才会触发真正的绘图动作。而JavaScript代码调用的这些操作都是依靠SkDeferredCanvas来保存的。

图中的下半部分表示当前面JavaScript调用2D绘图接口完毕后,WebKit调用TaskObserver类的didProcessTask方法。Canvas2DLayerManager类调用CanvasLayerBridge类来判断是否需要刷新那些操作。Canvas2DLayerBridge类检查并重置前面设置的标记,如果时机合适的话,该类调用SkDeferredCanvas类的flush函数提交前面保存的所有绘图操作,这样就完成了Canvas2D的绘制工作。

当合成器调用updateLayers函数的时候,该函数会触发每个合成层绘制自己。因为Canvas2D机制是由JavaScript代码来绘制2D图形,所以这个时候canvas所在的合成层实际上已经绘制完成(或者说绘制操作已经缓存起来了)。图8-31描述了合成器要求绘制Canvas2D的合成层的过程,读者可以看到,这时候WebKit实际上不需要绘制该层,只需要改变一下3D图形上下文的状态。

图(Chromium中合成器调用绘制Canvas2D的合成层)

从中读者可以发现,这一机制虽然延迟了一些操作(实际上没有什么影响),但是Chromium采用的这一延迟思想非常有用,也就是合并很多2D绘图操作(5),这样能够有效提高绘制的性能。

3.2 WebGL

3.2.1 3D图形上下文

前面提到过3D图形上下文,WebCore表示该上下文的抽象类是GraphicsContext3D。WebKit的Chromium移植定义了WebGraphicsContext3D接口,该接口类是GraphicsContext3D的实现类,基本上实现类的所有接口都可以映射到OpenGL ES 2.0 规范所定义的编程接口。

图(3D图形上下文和Chromium的实现类)中包含了三个类,最下面的类就是WebGraphicsContext3DCommandBufferImpl,该类是WebGraphicsContext3D类对应的使用命令缓冲区的实现子类。前面提到的合成过程和Canvas2D,包括本节介绍的WebGL都会使用该类来实现3D图形操作。

图(3D图形上下文和Chromium的实现类)

3.2.2 WebGL的实现

WebGL是Khronous组织提出的一套基于3D图形定义的JavaScript接口,它基于canvas元素,跟Canvas2D不同的是,Web开发者可以使用3D图形接口来绘制各种3D图形。根据WebGL规范中的描述,这些接口可以分成下面几个主要的部分。

  • 上下文及内容展示 :在使用WebGL的编程接口之前,开发者需要获取WebGL-RenderingContext和DrawingBuffer接口(Chromium需要它)。对JavaScript代码来说,GL的操作都是由WebGLRenderingContext对象来负责完成。但是,DrawingBuffer接口对用户来说是透明的,它用来存储渲染的内容并被合成器所合成,包括帧缓冲器对象(绘制的结果存储)和纹理对象(纹理被合成器所使用)。
  • WebGL的资源及其生命周期 :纹理对象、缓冲区(VBOs)、帧缓冲区、渲染缓冲区、着色器等(这些也都是OpenGL的资源)。它们有对应的JavaScript对象即与WebGLObject对应,这些对象的生命周期是一致的。
  • 安全 :WebGL规范为保证安全性,第一,所有的WebGL资源必须包含初始化的数据;第二,来源安全性,为防止信息泄露,当"canvas"元素的属性"origin-clean"为false时,readPixels将会抛出安全方面的异常;第三,要求所有的着色语言必须符合OpenGL ES Shading Language 1.0;第四,为防止DOS攻击,规范建议采取适当的措施对花费时间过长的渲染操作过程进行监控和限制。
  • WebGL接口 :主要包括各种资源类的接口和上下文类的接口,这些接口用于绘制3D的操作,它们基本上来源于OpenGL ES 2.0定义的接口。
  • WebGL与OpenGLES 2.0的区别 :这里不再一一介绍,读者可以自行翻阅和了解相关细节。读者假如想要了解自己的浏览器对WebGL支持的详细情况,请访问http://webglreport.sourceforge.net/获取详细参数。

针对规范定义的内容,WebKit和Chromium定义了相应的类来描述它们,图8-33给出了主要类。WebGLRenderingContext类同CanvasRenderingContext2D类的作用类似,都是规范定义的接口。不同的是,WebGL的接口是3D图形操作。WebGLRenderingContext类同样需要一个GraphicsContext3D类和它的实现类,除此之外,还需要一个DrawingBuffer类,它类似于Canvas2D中的ImageBuffer,它的作用是保存WebGL渲染目标结果,WebKit将渲染结果用来合成。

图(WebKit和Chromium中支持WebGL的主要类)

下面同样以例子来说明WebGL的工作过程,示例代码8-1中就是一个使用WebGL技术的网页,以此例为基础来描述这一个过程。跟Canvas2D的过程分析一样,这里同样也把Chromium中WebGL的工作过程分成三个阶段。

第一阶段是对象的初始化阶段,当JavaScript引擎调用示例代码中的getContext函数的时候,WebKit就会执行如图8-34所示的对象创建顺序,这一过程跟Canvas2D的对象创建过程非常类似。不同的是,这一阶段不会创建CC::Layer对象。

图(WebGL初始化阶段的对象创建顺序)

第二阶段是构建RenderLayer、WebLayer、CC::Layer等对象,同样是在DOM树构建之后检查CSS样式变化时才会被触发。当RenderLayer等对象被创建之后,WebKit设置GraphicsLayer对象所对应的WebLayer对象。同Canvas2D不一样的是,此时DrawingBuffer对象才会开始请求创建WebLayer(WebLayerImpl)和TextureLayer对象,之后WebKit同样将WebLayer对象设置到GraphicsLayer中。图8-35描述了这一过程,为了简洁起见,图中省略了一些步骤。

图(WebGL创建RenderLayer和TextureLayer等对象)

第三阶段是3D绘图部分,图8-36是一个简单的WebGL使用clearColor接口来设置颜色的JavaScript代码被WebKit执行的过程。同Canvas2D机制不一样的是,每个GL的调用都是直接通过WebGraphicsContext3DCommandBufferImpl类将GL命令传给GPU进程,这一过程没有使用缓存机制,而是直接将命令传递给GPU进程。

图(WebGL绘制3D图形)

同样的,当合成器调用updateLayers函数的时候,该函数会触发WebGL所使用的合成层绘制合成层的目标结果到合成层的存储结果。WebGL所在层的内容在合成器请求更新该层之前已经由WebKit完成。图8-37描述了合成器要求绘制WebGL的合成层时候的过程,DrawingBuffer所要做的就是刷新3D图形上下文中的结果数据,并返回结果。

图(Chromium中合成器调用绘制WebGL的合成层)

3.3 CSS 3D变形

CSS 3D变形和动画是HTML5引入的新特性,作用是能够对任意DOM子树作3D变形。这一特性非常有用,它同WebGL提供的能力是不一样的。WebGL是在一个"canvas"元素内部绘制3D图形,CSS 3D变形功能可以对任何元素进行3D变形,它是一个可以被元素子女继承的属性,也就是一个元素和它的子女都会作相应的3D变形。

WebKit对应用该变形技术的DOM子树使用单独的合成层和硬件加速机制。当使用JavaScript代码改变该元素的3D变形样式后,Chromium能够减少网页每一帧渲染所需要的时间,典型的例子就是前面提到的网页,来自于www.famo.us

以示例代码6-1所展示的CSS 3D变形为例,说明3D变形是如何被WebKit和Chromium处理的。对于WebKit需要创建RenderLayer等对象,之前已经介绍过,这里不再赘述。图(设置合成器中的Layer对象的3D变形值)介绍了WebKit和Chromium设置3D变形值的过程。

图(设置合成器中的Layer对象的3D变形值)

当网页中JavaScript代码修改元素的变换属性值的时候,通过上面的过程,最后样式是设置在该合成层(前景层)上,当WebKit将元素绘制完成之后,在合成过程中,WebKit通过3D变形作用到该合成层上,即可完成特定的效果。WebKit只是在第一次需要绘制"div"元素的内容,之后仅仅设置变换属性值,然后重新合成即可。当合成器调度绘制该合成层的时候,WebKit根本不会发生想象中的重新布局和重绘动作。虽然用户看起来网页内容在变动,但是这只是合成的动作,这些动画等都是WebKit和Chromium设计的机制和硬件加速带来的效果。

3.4 其他

网页中还有很多其他的模块可以使用GPU硬件机制来加速,例如支持视频解码和播放、2D图形绘制等,WebKit支持它们的主要思想依旧是对这些内容进行分层,使用GPU的强大绘图能力来支持这些模块,关于视频方面的介绍,笔者将在第11章"多媒体"作进一步的阐述。读者还可以思考一下有没有其他地方可以使用加速机制。

另外,还有很多新的思路对硬件加速机制进行改进,典型的做法是使用多线程机制,因为现代处理器都包含多核,使用线程化的方法来支持网页的渲染是一个很好的思路。当然线程化的代价就是WebKit需要同步或者内容的拷贝,例如前面介绍的线程化合成器、Layer树和LayerImpl树。Chromium为了减少同步和等待的开销,创建LayerImpl树并拷贝Layer树的内容,但是这一做法带来的好处也很明显。所谓有利也有弊正是如此,关键看应用场景的实际效果如何。

3.5 实践:Chromium的支持

Chromium使用GPU加速机制来加速网页渲染被广泛地应用在各种网页中。Chromium浏览器对网页渲染的理解的确很不一样,在网页功能越来越强的同时,用户也能够取得较好的性能和体验效果。

图(Chromium能够使用GPU加速的功能)描述了Chromium目前能使用GPU硬件加速的各项网页功能,这是在Windows系统上显示的结果,在其他平台上,结果可能不一样,例如在Android上面可能有更多跟加速相关的功能。图中的一些功能之前已经介绍过,例如Canvas、Compositing、3D CSS和CSS Animation、WebGL等。不过,还有些其他的功能笔者并没有介绍,例如Flash、Video、WebGL multisampling(WebGL中的反锯齿技术)等。除了Video之外,读者对其他功能有兴趣的话,可以自行参考相关材料。

图(Chromium能够使用GPU加速的功能)

关于使用硬件加速和合成器的实践,读者可以回顾第2章中图2-5所描述的网页层次结构。相信读者现在对那些合成层,以及将合成层分成瓦片的理解更深了。

相关推荐
charlee442 天前
CMake构建学习笔记19-OpenSSL库的构建
ssl·cmake·c/c++·构建
丶Darling.2 天前
进程间通信博客总结目录
linux·网络编程·进程同步·系统编程·c/c++·阻塞和非阻塞
witton2 天前
将VSCode配置成Goland的视觉效果
ide·vscode·编辑器·go·字体·c/c++·goland
丶Darling.2 天前
linux高性能服务器编程读书笔记目录&&建议
linux·服务器·网络编程·目录·系统编程·c/c++·博客总结
Alan_Wdd4 天前
解决谷歌人机验证 (reCAPTCHA) 无法加载问题
前端·chrome·浏览器·插件·人机验证·recaptcha
丶Darling.6 天前
高并发服务器实现总结目录
运维·服务器·网络·网络编程·c/c++·阻塞和非阻塞
小丑西瓜6669 天前
网络基础概念
linux·网络·tcp/ip·c/c++
波儿菜13 天前
WebKit Authentication Challenge 核心流程
webkit
x-cmd18 天前
[241202] Firefox 切换到 .tar.xz 用于 Linux 打包 | Red Hat 即将成为 WSL 官方发行版
linux·windows·firefox·浏览器·wsl·混合云·压缩方式