Chromium 的GPU硬件加速

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

这里所说的GPU硬件加速是指应用GPU的图形性能对chromium中的一些图形操作交给GPU来完成,因为GPU是专门为处理图形而设计,所以它在速度和能耗上更有效率。但是,使用GPU加速有些额外开销,并且某些图形操作CPU完成的会更快,因而不是所有的操作都合适交给GPU来做。

Chromium中,GPU加速可以不仅应用于3D,而且也可以应用于2D。这里,GPU加速通常包括以下几个部分:Canvas2D,布局合成(Layout Compositing), CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。目前chromium对这些的支持已经越来越充分,只是某些方面可能还存在些正确性和性能问题。

Chromium除了对以上采用GPU硬件加速之外,在新的views框架中,引入了对浏览器主界面采用硬件加速的机制。 它可以把浏览器主界面上的各种工具栏等和网页通过GPU合成(compositing)起来。因为之前网页布局也用到合成器,目前两个合成器各自为战,所以chromium会实现一个新的合成器叫chrome compositor,关于它的细节,我会在专门的章节来做介绍。

本章主要介绍GPU进程相关的内容,但是不涉及各个部分的加速实现。具体的各个加速实现及WebKit对GPU硬件加速的支持,后面会有分别的章节来介绍。

启用GPU加速

如果想使用Chromium的GPU硬件加速功能,首先需要你的GPU驱动程序不在chromium的黑名单中。因为很多的GPU驱动程序存在很多的错误,这些错误可能会比较严重的影响了chromium的稳定,例如导致程序崩溃,所以这些GPU被列在黑名单中。一个检测的简单办法是在地址栏里输入“about:gpu”查看相关GPU的信息,检查相关加速是否已经打开。

如果你的GPU驱动不幸在黑名单中,首先的办法是升级你的驱动,然后重新启动chromium。如果还是解决不了问题,可以通过在chromium的启动参数中加入“--ingore-gpu-blacklist”来关闭黑名单功能,这样你就开启了你的GPU加速功能。但是,如前所说,这种方法可能会造成chromium的不稳定,因而只是权宜之计。

进程模型

由于chromium的安全模型和稳定性考虑,GPU加速也采用多进程模型来实现。所以的GPU加速相关的操作均有一个独立的进程来完成,这就是GPU进程。详见下图所示GPU进程和Browser,Renderer进程。

enter image description here

GPU进程由Browser进程来创建和销毁,这类似于对plugin进程的管理。它们之间的通信是通过chromium的IPC消息机制实现的。Chromium只会创建一个GPU进程,该进程被所有的Renderer进程和Pepper plugin进程所共享,但是GPU进程会为不同的进程创建不同的command buffer的示例。对于一个Renderer进程,GPU进程也可以为其创建多个示例,这取决于Renderer进程的需求。

下面主要介绍以下GPU进程和客户进程(以Renderer进程示例)之间是如何工作的。下图描述了以WebGL为示例的主要模块(类)及它们之间的联系。

enter image description here

首先来看一看Renderer进程的模块类:

         WebGraphicsContext3DCommandBufferImpl:继承自WebKit::WebGraphicsContext3D类, 具体的实现类,主要是转接自WebKit的调用到chromium的具体实现。主要包括一个RenderGLContext对象。
         RendererGLContext:  Renderer进程对GLContext的一个封装,包括所有用于跟GPU进程交互的类,有一个GLES2Implementation对象,一个CommandBufferProxy对象和一个GPUChannelHost对象。
         GLES2Implementation:该类模拟GLES2,但是不直接调用GLES2的实现,而是将这些调用转换成特定格式的命令存入CommandBuffer中。
         CommandBufferHelper: 该类是一个辅助类,包括一个CommandBuffer代理类和一个共享内存
         CommandBufferProxy:CommandBuffer的一个代理类,实现CommandBuffer的接口,用于和CommandBufferStub之间的通信
         GPUChannelHost:用于IPC消息的通信类
接着再挨个了解一下GPU进程中的模块类:
         GPUChannel:用于GPU进程中的IPC消息的通信类
         GPUCommandBufferStub:CommandBuffer的桩,接受来自于CommandBufferProxy的消息,将请求交给CommandBufferService处理
         CommandBufferService:具体的CommandBuffer的实现类,但是其实它并不具体解析和执行这些命令,而是当有新的命令时,触发给注册的回调函数来处理
         GPUScheduler:负责调度执行commandbuffer的命令,它会检查该commandbuffer是否应该被执行,并适时将命令交给CommandParser的来处理
         CommandParser:仅检查CommandBuffer中的命令的头部,其余交给具体的decoder来做
         GLES2DecoderImpl:解析每条具体的命令并执行调用GL相应的函数
         GLImplementation Wrapper: 一组GL相关的函数指针,通过用户的设定来读取相应gl库的函数地址来设置自己
         GLLibraries: 具体的函数库,可以是opengl,opengles,mesagl,mock等

通过上面每个模块类的具体介绍,相信你已经大概知道它们的工作过程。那么,它们是如何同步的呢?因为Renderer进程需要等待GPU进程做好某些操作之后才继续执行。答案是,GPU进程处理一些命令后,会向Renderer进程报告当前自己当前的状态,Renderer进程通过检查状态信息和自己的期望结果来确定是否满足自己的条件。

GPU进程最终绘制的结果不再像软件渲染那样通过共享内存传递给Browser进程,而是直接将页面的内容绘制在浏览器的标签窗口内。

Command Buffer

Command Buffer主要用于GPU进程(且称为GPU 服务端)和GPU的调用者进程(且称GPU客户端, 如Renderer进程, Pepperplugin进程)传递GPU操作命令。从接口上来,它只提供一些基本的接口来对buffer进行管理,它没有对buffer的具体方式和命令的格式进行任何限制。

现有实现是基于共享内存的方式来完成,因而命令是基于gles编码成特定的格式存储在共享内存中。共享内存方式采用了ring buffer的方式来管理,这表示内存可以循环使用,旧的命令会被新的命令所覆盖。基于gles的编码格式,因为WebGL是基于gles的JavaScript绑定,所以简单直观。

命令基本格式

一条命令可以分成两个部分:命令头和命令体。命令头包含两个部分,一个是命令的长度,一个是命令的ID。命令体包含该命令所需要的其他信息,例如命令的立即操作数。命令是可以固定长度的,也可以是变长的,一起取决于该命令。具体的结构如下图所示。

enter image description here

基于GLES2的命令

上面说到,本身Command Buffer是没有定义具体的命令,所以GLES2可以根据需要自己来定义。命令大概可以分成两类,第一类是公共的命令,主要用来操作桶(bucket),跳转,调用和返回。第二类是跟GLES2的函数相关的命令,主要用来操作GLES2的函数。

IPC机制

前面提到,命令本身是保存在共享内存中的,而且每条命令的长度不能超过(1<<21 -1),而且共享内存的大小也是固定的,如果命令太长,可存储的命令很少。那么问题就出来了,如何解决需要传输较大数据的命令呢?对于这样类型的数据,可以对它们利用独立的共享内存来实现,例如TexImage2D。但是,当共享内存大小超过系统的限制时,这种方式就行不通。chromium提供了一种新的机制来解决这个问题。

这个机制就是桶(bucket)机制。其解决问题的原理是,通过共享内存机制来分块传输,而后把分块的数据保存在本地的桶内,从而避免了申请大块的共享内存。前面提到的公共的命令就是用来处理桶相关的数据。当数据传输完成之后,对该数据进行操作的命令就可以执行了。

桶机制也可用来传输string类型的变长数据。接受端首先获取桶内string的长度,然后通过共享内存方式来分块传输,最后合成在自己的桶内。

GPU加速的后端

GPU加速虽然是基于opengles的接口来设计commandbuffer的,但是这不意味着系统的gl的后端库必须是egl/opengles库,这是因为chromium提供了一层转接。

这层转接如何实现的呢?原理其实并不复杂。Chromium首先定义了一组gl相关的函数指针,然后chromium根据设置或者命令行参数来加载相应的库,并从这些库中读取相应的函数地址,并把这些地址赋值给这些函数指针。这样一来,GPU加速部分的代码调用的是这一组函数指针,从而避免直接使用opengles的库。详细代码参考源文件目录” ui/gfx/gl”。

源代码目录

gpu/
         该目录包含GPU相关的文件,主要包括commandbuffer的实现,工具,GPU涉及的IPC等
gpu/command_buffer
         该目录存放CommandBuffer相关的类,主要包括client端,service端和公共的基础类
gpu/command_buffer/client
         该目录存放GPU客户端所使用到的与CommandBuffer相关的类
gpu/command_buffer/service
         该目录存放GPU进程端所使用到的与CommandBuffer相关和GLES2具体实现相关的类
content/gpu
         该目录存放GPU进程所涉及使用的基础类,包括进程入口函数,IPC,进程和线程管理等相关类
content/common/gpu/
         该目录存放GPU进程使用的与GPU相关的类
content/Renderer/gpu/
         该目录存放Renderer进程使用GPU加速所涉及的类,主要包括IPCchannel, CommandBuffer代理等类
content/browser/gpu/
         该目录存放Browser进程和GPU、Renderer进程交互相关的类,如GPUProcessHost。还包括GPU黑名单的实现
ui/gfx/gl/
         该目录存放与具体的opengl,opengles库相关类,包括根据命令行参数读取所需要的gl的库类型,库的函数地址的读取和管理等等

参考文献

  1. http://www.chromium.org/developers/design-documents/gpu-command-buffer
  2. http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome
  3. http://www.chromium.org/developers/design-documents/rendering-architecture-diagrams