Chromium支持硬件加速渲染网页,即使用GPU渲染网页。在多进程架构下,Browser、Render和Plugin进程的GPU命令不是在本进程中执行的,而是转发给GPU进程执行。这是因为GPU命令是硬件相关操作,不同平台的实现不一样,从而导致不稳定,而将不稳定操作放在独立进程中执行可以保护主进程的稳定性。本文对Chromium硬件加速渲染机制的基础知识进行简要介绍和制定学习计划。
老罗的新浪微博:,欢迎关注!
GPU进程将所有的GPU命令都放在一个GPU线程中执行。如果说GPU线程是一个Server端,那么它的Client端就是Render进程、Browser进程和Plugin进程。GPU线程需要区分每一个Client端所转发过来的GPU命令,避免它们之间产生相互影响。为此,GPU线程需要为每一个Client端创建一个OpenGL上下文,如图1所示:
图1 GPU线程中的OpenGL上下文
在GPU线程中,每一个OpenGL上下文都用一个GLContextEGL对象描述。在图1中,我们列出了GPU线程的三种Client端,分别是WebGL、Render和Browser。其中,WebGL是html5支持的3D渲染接口,Render是用来渲染网页的,而Browser是用来渲染整个浏览器窗口UI的。 WebGL和Render这两种GPU线程Client端运行在Render进程中,而Browser这种GPU线程Client端运行在Browser进程中,它们均是通过GPU Channel与GPU线程进行通信,也就是它们在OpenGL线程都有一个对应的OpenGL上下文。关于GPU线程以及GPU Channel,可以参考前面Chromium的GPU进程启动过程分析一文。为了方便描述,我们将上述的三种GPU线程Client端称为WebGL端、Render端和Browser端。
每一个OpenGL上下文都需要设置一个绘图表面。其中,WebGL端和Render端的OpenGL上下文设置的是同一个绘图表面,并且该绘图表面是一个离屏绘图表面,该离屏绘图表面是GPU线程的默认离屏绘图表面。有某些平台上,OpenGL上下文不需要设置一个真的绘图表面,这时候GPU线程的默认绘图表面通过Surfaceless对象来描述。在需要给OpenGL上下文设置一个真的绘图表面的平台中,GPU线程的默认离屏绘图表面使用Pbuffer对象来描述。
与WebGL端和Render端相比,Browser端的OpenGL上下文设置的绘图表面是一个屏幕绘图表面,也就是一个可以显示在屏幕上的用户可见的绘图表面,这意味着这个绘图表面是与平台相关的,例如在Android平台上,这个绘图表面通过SurfaceView来描述。
Chromium之所以将WebGL端和Render端的OpenGL上下文的绘图表面设置为离屏绘图表面,而将Browser端的OpenGL上下文的绘图表面设置为屏幕绘图表面,是因为前两者渲染出来的UI最终是合成在后者的绘图表面上显示在屏幕中的。这一点我们后面再分析。
每一个GPU命令都必须在一个OpenGL上下文中执行。这意味着,从WebGL端、Render端和Browser端发送过来的GPU命令要分别在它们对应的OpenGL上下文中执行。又由于所有的GPU命令都是在同一个GPU线程中执行的,并且同一时刻GPU线程只能有一个OpenGL上下文,因此GPU线程必须在接收到一个GPU命令时,切换至合适的OpenGL上下文中去。切换OpenGL上下文的操作在Chromium中称为OpenGL上下文调度(Schedule)。
此外,由于WebGL端和Render端渲染出来的UI最终要交给Browser端合成和显示在屏幕中,因此这涉及到不同OpenGL上下文之间的同步(Synchronize)。也就是说,Browser端在合成WebGL端和Render端的UI时,要确保不会出现资源竞争。
在Chromium中,GPU线程只存在一个Browser端,但是有可能存在若干个Render端和WebGL端。例如,当我们打开多个网页时,就会存在多个Render端,而每一个网页又可能包含有若干个使用WebGL接口渲染的canvas标签,也就是存在若干个WebGL端。如果我们为每一个WebGL端和每一个Render端都创建一个OpenGL上下文,那么就会导致GPU线程同时创建很多OpenGL上下文。在有些平台上,可以同时创建的OpenGL上下文的个数是有限制的。为了解决这个问题,GPU线程为WebGL端、Render端和Browser端创建的是虚拟OpenGL上下文,如图2所示:
图2 GPU线程中的虚拟OpenGL上下文
虚拟OpenGL上下文使用GLContextVirtual对象描述,它们的个数不受平台限制,因此可以创建无数个。不过,每一个虚拟OpenGL上下文还是需要关联一个真实OpenGL上下文的。
WebGL端、Render端和Browser端的虚拟OpenGL上下文关联的真实OpenGL上下文是相同的。这个真实OpenGL上下文设置的绘图表面就是前面描述的GPU线程的默认离屏绘图表面。不过,Browser端的虚拟OpenGL上下文还关联有一个屏幕绘图表面,在Android平台上,这个屏幕绘图表面就是一个SurfaceView。
当GPU线程从一个虚拟OpenGL上下文切换至另一个虚拟OpenGL上下文时,需要执行以下两个步骤:
1. 找到另一个虚拟OpenGL上下文关联的真实OpenGL上下文,并且将找到的真实OpenGL上下文作为GPU线程的当前OpenGL上下文;
2. 将前面找到的真实OpenGL上下文的绘图表面设置为另一个虚拟OpenGL上下文关联的绘图表面。
从上面两个步骤可以看出,虽然WebGL端、Render端和Browser端的虚拟OpenGL上下文关联的真实OpenGL上下文是相同的,但是当该真实OpenGL上下文在不同时刻设置的绘图表面却是不一样的。其中,当该真实OpenGL上下文是切换给WebGL端和Render端使用时,设置的绘图表面是GPU线程的默认离屏绘图表面,而当该真实OpenGL上下文是切换给Browser端使用时,设置的绘图表面是一个屏幕绘图表面,也就是一个SurfaceView。
不管WebGL端、Render端和Browser端使用的是真实OpenGL上下文,还是虚拟OpenGL上下文,它们都是在同一个OpenGL上下文共享组中的(Share Group)。正是因为这些OpenGL上下都在同一个共享组中,Browser端的OpenGL上下文才可以访问WebGL端和Render端的OpenGL上下文中的资源,从而可以将它们合成并且显示在屏幕上。在合成过程中涉及到的资源同步过程如图3所示:
图3 不同OpenGL上下文之间的资源同步过程