本文旨在按照从底层到上层的顺序,详细串联下android4.4 webview chromium架构下的硬件绘制实现细节。
目录结构
一.webview chromium与GPU交互的方式(单进程CommandBuffer系统的详细结构)
1.1 OpenGL ES2.0与EGL简介
1.2 android4.4中创建的供chromium内核使用的EGLContext和EGLSurface
1.3 chromium内核加载gles2及egl库并绑定其接口
1.4 chromium内核对GL Context及GL Surface的封装
1.5 GLContext的虚拟化及InProcessCommandBuffer对GLContext的封装
1.6 单进程CommandBuffer系统对client端提供的接口及client端创建该接口的过程
1.7 小结
1.8 CommandBuffer系统设计目标及基本实现原理
二.网页内容从webkit RenderTree合成到android系统WebView控件对应的Surface上的过程
一.webviewchromium与GPU交互的方式(单进程CommandBuffer系统的详细结构)
1.1 OpenGL ES2.0与EGL简介
OpenGLES2.0
是一套跨平台的在手持及嵌入式设备上渲染复杂3D图形的软件接口,由Khronos Group开发。
可以简单理解为操纵GPU的一组通用接口,由平台厂商提供具体实现,实现库libglesv2.so.
EGL
EGL是OpenGL ES关联本地窗口系统的接口标准。
功能类似于OpenGL在Windows平台上关联窗口系统使用的wgl,在Mac平台使用的agl,以及x-windows上使用的xgl。开发者需要查阅平台商的文档来决定支持哪个接口,由平台厂商提供具体实现,实现库的名字libegl.so.
OpenGLES2.0与EGL的关系
OpenGL ES本质上是个pipeline的状态机。OpenGLES命令的执行需要一个rendering context和一个drawing surface。
renderingcontext
存放的是状态机的当前状态。有当前的颜色、纹理坐标、变换矩阵、渲染模式等,这些状态作用于程序提交的顶点坐标等图元从而形成帧缓冲内的像素。在OpenGL的编程接口中,rendering context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状态,偶尔也从Context里获取一些信息。(这段取自网上某位同学的博客,没找到出处,不好意思)
drawingsurface
是图元绘制其上的目标surface。drawing surface指定了渲染需要的buffer类型,比如color buffer,depth buffer和stencil buffer.
rendering context与drawingsurface是由EGL创建并维护的。
OpenGL ES在开始渲染之前,需要使用EGL做如下事情:
●查询设备上可用的display,并初始化它们。
比如,翻盖手机可能有两个LCD面板,我们使用OpenGL ES渲染的surface可能可以在其中一个或两个面板上显示。
●创建一个renderingsurface.EGL创建的Surface可以分为两类:on-screen surfaces和off-screen surfaces。On-screen surfaces是attach到本地窗口系统的.而off-screen surfaces是不能不显示的pixel buffers,但是可以作为rendering surface使用。off-screen surfaces可以被用来渲染到texture上,或者在多重Khronos APIs之间共享。
●创建一个rendering context.EGL需要创建一个OpenGL ES rendering context.在rendering实际开始之前,renderingcontext需要attach到一个合适的surface上。
下面例子是EGL初始化的一个基本调用:
1.创建display并初始化
//返回与native display相连接的EGLdisplay.
display =eglGetDisplay(EGL_DEFAULT_DISPLAY);
//初始化EGL display.
eglInitialize(display,...).
//返回满足一定属性的EGL framebuffer配置列表
eglChooseConfig(&config)
2.创建rendering surface
//创建一个EGL windowsurface返回其句柄
surface = eglCreateWindowSurface(display,config, winType, NULL);
3.创建rendering context
//创建EGL renderingcontext,可以使用这个context渲染到EGL drawing surface
context = eglCreateContext(display, config,EGL_NO_CONTEXT, NULL);
display,surface,context三者之间的联系
surface必须绑定到调用线程的当前context,然后才能调用glesapi.绑定是通过eglMakeCurrent完成的。eglMakeCurrent(dpy, mSurface, mSurface, ctx);
调用完eglMakeCurrent,再调用gles2 api,才能保证当前执行的gl命令是在正确的context下合成到正确的surface上的。
1.2 android4.4中创建的供chromium内核使用的EGLContext和EGLSurface
前面讲过使用gles前,需要先调用egl完成三件事情:
1.创建display并初始化
2.创建rendering surface
3.创建rendering context
这三件事情是在android4.4系统HardwareRenderer.java中完成的。
HardwareRenderer.java中createContext()及createSurface()中创建了EGLContext和EGLSurface。
webview架构下,chromium内核使用的就是这里创建的EGLContext和EGLSurface.所以调用gl命令之前的环境设定,即调用eglMakeCurrent()是由HardwareRenderer.java负责的。chromium内核中不会有这样的调用。内核创建的是GLNonOwnedContext.后面会详细介绍。
HardwareRenderer.java draw()中调用的drawDisplayList()会导致chromium内核attach到android系统的绘制函数AwContents::DrawGL()被调用。
1.3 chromium内核加载gles2及egl库并绑定其接口
chromium内核中,为了调用GLES2和EGL命令,需要先加载gles2和egl库,并分别绑定这两个库提供的接口。加载并绑定gles2和egl库的过程如下图。
这个过程的作用是得到与gles2和egl绑定的接口,这样chromium内核在需要调用真正的gles2或egl命令时,就可以通过绑定好的接口来调用。绑定好的接口是在gl_bindings.h声明的两个全局变量:
与系统gles2库绑定的接口,实现了DriverGL中定义的ProcsGL fn相应接口与系统gl库对应接口的绑定。具体绑定过程在gl_bindings_autogen_gl.cc中。
GL_EXPORTextern DriverGL g_driver_gl;
与系统egl库绑定的接口,实现了DriverEGL中定义的ProcsEGL fn相应接口与系统egl库对应接口的绑定,具体绑定过程在gl_bindings_autogen_egl.cc中。
GL_EXPORTextern DriverEGL g_driver_egl;
为了支持虚拟context,chromium定义了如下两个类:
gl_gl_api_implementation.cc中定义了staticRealGLApi* g_real_gl;
gl_egl_api_implementation.cc中定义了RealEGLApi*g_real_egl;
并使用已经绑定的g_driver_gl初始化g_real_gl;使用已经绑定的g_driver_egl初始化g_real_egl。
chromium实际使用g_real_gl和g_real_egl分别调用gles2和egl的接口。
关于虚拟context的内容后面会有详细介绍。
现在我们只需要记住如下内容:
绑定过程完成后,我们可以通过g_real_gl和g_real_egl来实际调用gles2和egl接口了。
这样我们就看到了chromium内核中GPU之上的第一层,即与GPU打交道的接口层。
1.4 chromium内核对GLContext及GLSurface的封装
chromium中的GLSurface结构
GLSurface
封装可以使用GL渲染的Surface,隐藏平台相关的管理.
GLSurfaceEGL
EGL Surface接口,这个类本身只提供了EGLDisplay和EGLConfig。
NativeViewGLSurfaceEGL
封装绑定到view的一个EGL surface.继承自GLSurfaceEGL,除了提供EGLDisplay和EGLConfig外,还提供了EGLSurface。创建NativeViewGLSurfaceEGL时,会调用eglCreateWindowSurface,
创建一个关联到本地窗口系统的surface,前面讲过eglCreateWindowSurface的创建需要本地窗口系统句柄做参数,这个句柄可以从NativeView中得到。我们讲过webview chromium架构目标surface是由HardwareRenderer.java中创建的。
不会创建NativeViewGLSurfaceEGL实例。
AwGLSurface
在硬件绘制中用来表示App提供的surface。注意,offscreen contexts不能使用这个GLSurface.
webview chromium架构创建的是GLSurfaceEGL,注意这个类的实例并没有关联本地窗口系统。
下图是创建GLSurfaceEGL的过程:
创建GLSurfaceEGL的目的就是初始化gl_surface_egl.cc中定义的全局变量:
EGLConfig g_config;
EGLDisplay g_display;
EGLNativeDisplayType g_native_display;
const char* g_egl_extensions = NULL;
bool g_egl_create_context_robustness_supported= false;
bool g_egl_sync_control_supported = false;
bool g_egl_window_fixed_size_supported =false;
bool g_egl_surfaceless_context_supported =false;
这些变量通过GLSurfaceEGL的静态函数调用,供chromium使用。
这里需要注意的是并没有创建与窗口系统相关联的eglSurface.
chromium中的GLContext结构:
GLContext
封装OpenGL context,隐藏平台相关的管理;支持虚拟化context.
创建Context的过程在GLContext::CreateGLContext().
android平台,根据Surface类型的不同会创建不同类型的GLContext。
与本地窗口系统相关联的eglSurface对应创建GLContextEGL。
其余不关联本地窗口系统的Surface对应创建GLNonOwnedContext。
GLNonOwnedContext
用来渲染到当前已经存在的context+surface。不持有draw回调时使用。
已经存在的context+surface是在HardwareRenderer.java中创建的。
webview chromium架构创建的就是GLNonOwnedContext实例。
1.5 GLContext的虚拟化及InProcessCommandBuffer对GLContextVirtual的封装
GLContext的虚拟化
虚拟Context机制是为了解决某些型号的GPU不支持context切换或者切换Context的代价较高而设计的。需要调用gpu的客户端创建的是虚拟化的GLContext,所有客户端共用一个真实的glContext。每个客户端保存自己的state.在需要调用gl命令时,将自己持有的虚拟GLContext与真实的glContext相关联。切换VirtualContext主要是绑定真实的glcontext,通过软件方式恢复的绘制状态,这个切换是通过GLContextVirtual::MakeCurrent()完成的,不涉及GPU。
GLContext虚拟化的具体实现
GLContextVirtual::MakeCurrent()具体流程
virtualcontext的实现类图中,我们可以看到InProcessCommandBuffe包含的主要结构。
其中GLES2Decoder包含了GLContextVirtual.GLES2Decoder是真正调用gl指令的地方.
GLES2Decoder调用任何gl指令之前,都需要先调用GLContextVirtual的MakeCurrent接口,将虚拟glcontext与唯一的真实GLNonOwnedContext相关联(即关联真正的gl接口),关联之后,GLES2Decoder中的gl调用才能调用到gles库提供的gl指令。GLContextVirtual::MakeCurrent()的触发是在GLES2DecoderImpl::MakeCurrent().注意这里MakeCurrent()的语义与eglMakeCurrent()的区别。
1.6单进程CommandBuffer系统对client端提供的接口,及client端创建该接口的过程
下图是单进程CommandBuffer与GPU客户端的接口的内部结构:
cc::ContextProvider可以看作是单进程CommandBuffer与GPU客户端的接口,每个需要与GPU通信的客户端都需要持有一个cc::ContextProvider实例。
下面我们看webview架构下,compositor(browser)线程和webgl线程创建cc::ContextProvider的过程。
最后创建好的cc::ContextProvider保存在了OutputSurface中。gl指令都是通过OutputSurface的ContextProvider接口调用的。
1.7小结
从1.1到1.6基本是单进程CommandBuffer的主要实现结构。我们需要记住以下两点:
1.eglSurface,eglContext是由android系统HardwareRenderer.java中创建的,其中eglSurface也是我们网页内容最终合成的目标surface.
2.chromium内核调用gl命令的一条关键路径
1.8 CommandBuffer系统设计目标及基本实现原理
以下内容均来自GPUCommandBuffer官方文档
CommandBuffer系统设计目标:
1.安全性。
你可以申请一个texture或者一块buffer,这块返回的内存往往是其他的应用程序遗留下来的,
可能包含密码,图像或者其他不希望被调用程序看见的数据。
与此类似,还有很多有bug或设计不完善的api,对这些api的不适当调用会很容易导致浏览器崩溃。
2.跨系统的兼容性
比如,禁止使用一些高级的GLSL特性,通过重写shaders或其他技术绕过bug.
给客户端提供一致的体验。
3.速度。
速度提升的原因是GPU客户端只与CommandBuffer系统的客户端实现通信,并不会真正调用到gl指令。gl指令是在GPU进程中被更由效率的并发调用执行的。
CommandBuffer系统的基本实现
基本实现是"command buffer". 客户端(render 进程,pepper插件)将命令写入一些sharedmemory。客户端更新'put' 指针,通过IPC通知GPU进程它在这块buffer中写入了多少。
GPU进程或服务端从这块buffer中读出命令,逐条验证每个命令的有效性,命令的参数以及参数是否适用于os图形。
二.网页内容从webkit RenderTree合成到android系统WebView控件对应的Surface上的过程
webview chromium架构下合成器的实现分为两部分:
一部分运行在webkit线程(render线程,合成器main线程),主要结构是LayerTreeHost持有的一颗LayerTree,LayerTree上的节点是PictureLayer,PictureLayer的后端存储结构是SkPicture.这颗LayerTree是webkit RenderTree的后端存储。
还有一部分运行在compositor线程(browser线程,合成器impl线程)中,主要结构是LayerTreeHostImpl持有的一颗LayerTreeImpl,LayerTreeImpl上的节点是PictureLayerImpl。PictureLayerImpl的后端存储结构与PictureLayer共有的同一个(PicturePileBase)。LayerTreeImpl是LayerTree的克隆。实际上,为了实现webkit线程和compositor线程的相互独立,compositor线程中维护着两颗LayerTreeImpl,一颗叫ActiveTree,另一颗叫PendingTree.并且这两颗树各自持有一套PictureLayerTilingSet。
webkit线程同步的是PendingTree,compositor线程draw的是ActiveTree。在scheduler的调度下PendingTree在合适的时机会同步给ActiveTree。
整个合成器的运行逻辑可以细分为如下几步:
1.webkit线程通知cc层有新的内容需要commit。
2.录制webkit绘制命令,这个过程结束后,webkitRenderTree的绘制命令就存储到了LayerTree。
3.commit过程,完成webkit线程LayerTree到compositor线程PendingTree(LayerTreeImpl)的同步,这个过程结束网页绘制命令就转移到了compositor线程的PendingTree上。
4.raster过程,完成PendingTree上录制的绘制命令的实际执行,这个过程完成后网页信息就已像素的形式存储在了以下结构中的一种中:
PixelBuffer/SharedMemory/GraphicsBuffer/Texture
具体存储在哪种结构中取决于raster的方式,v37提供了四种类型的raster:
PixelBuffer raster;
SharedMemory作后端存储的Image Raster;
GraphicsBuffer作后端存储的Image Raster;
直接paint到Texture上的Direct Raster;
Raster方式的不同决定了后面纹理上传方式的不同。
这个过程结束后,compositor线程中的PendingTree就raster完了。
5.开始ActivePendingTree过程,将光栅化好的PendingTree同步给ActiveTree.同时将PendingTree改名为RecycleTree,回收不再使用的Tile.这步需要注意PendingTree与ActiveTree各持有一套PictureLayerTilingSet。也就是实际创建了两套Tile,当然相应的也就占了两份内存。
6.上传纹理到Texture,根据4中使用的raster方式的不同,纹理上传的方式也不同
●PixelBufferRaster
AsyncTransferThread/glTexImage2D/glTexSubImage2D
新开一个Transfer线程,调用glTexImage2D/glTexSubImage2D线程将纹理copy到Texture。
●ImageRaster SharedMemory
sharedmemory直接封装成Image,调用glTexImage2D,封装好的Image attach到Texture。
●ImageRaster GraphicsBuffer
GraphicsBuffer通过eglCreateImageKHR封装成eglImage。
调用glEGLImageTargetTexture2DOES将封装好的eglImage attach到Texture。
●Direct Raster
直接raster到texture,没有纹理上传步骤,常说的Skia的硬件加速。
7.将6中承载网页内容的Texture硬件合成到android webview控件对应的Surface上,这块Surface的后端存储是BufferQueue中的一块GraphicBuffer。
上述过程如图:
各个过程的触发时机如下:
1.webkit线程触发Scheduler::SetNeedsCommit();webkit线程有更新需要提交
2.ThreadProxy::BeginMainFrame()
3.ThreadProxy::ScheduledActionCommit()
4.LayerTreeHostImpl::ManageTiles()
5.ThreadProxy::ScheduledActionActivatePendingTree()
6,7均发生在ThreadProxy::ScheduledActionDrawAndSwapIfPossible()
三.关于android BufferQueue系统和SurfaceFlinger系统的一点简单说明
BufferQueue是连接SurfaceFlinger客户端和SurfaceFlinger的桥梁。客户端作为生产者,负责填充BufferQueue中GraphicBuffer,SurfaceFlinger作为消费者负责消耗BufferQueue中的已填好的Buffer。
客户端(比如浏览器应用)通过如下一对接口与BufferQueue通信.dequeueBuffer/queueBuffer。
SurfaceFlinger通过如下一对接口与BufferQueue通信。acquireBuffer/releaseBuffer。