Chromium为网页的<embed>标签创建了Plugin之后,Plugin就负责渲染<embed>标签的内容。Chromium为Plugin提供了OpenGL接口,使得Plugin可在网页上渲染3D内容。当然,我们也可通过WebGL接口在网页上渲染3D内容。不过,前者渲染效率会更高些,因为它是Native接口,后者是JavaScript接口。本文接下来就详细分析Plugin执行3D渲染的过程。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!
我们分析Plugin执行3D渲染的过程,更多的是为了理解Chromium与Plugin的交互过程,包括Chromium操作Plugin的过程,以及Plugin调用Chromium提供的接口的过程。接下来我们就以Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example为例,分析Plugin执行3D渲染的过程。
从前面Chromium网页加载过程简要介绍和学习计划这个系列的文章可以知道,WebKit会将网页抽象为一个DOM Tree。网页中的每一个<embed>标签在这个DOM Tree中都会有一个对应的节点。从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章又可以知道,网页的DOM Tree又会被Chromium转化为一个CC Layer Tree。其中,DOM Tree中的<embed>节点将对应于CC Layer Tree中的一个Texture Layer,如图1所示:
图1 DOM Tree中的<embed>标签与CC Layer Tree中的Texture Layer
Plugin在调用Chromium提供的OpenGL接口的时候,实际上是将<embed>标签的内容渲染在它在CC Layer Tree中对应的Texture Layer上。当Chromium对网页的CC Layer Tree进行绘制的时候,它内部的Texture Layer的内容就会显示在屏幕上。Texture Layer描述的实际上是一个纹理。在前面Chromium硬件加速渲染的UI合成过程分析一文中,我们提到,网页的canvas标签在CC Layer Tree同样是对应有一个Texture Layer。因此,<embed>标签对应的Texture Layer与canvas标签对应的Texture Layer显示在屏幕上的过程是一样的。这一点可以参考前面Chromium硬件加速渲染的UI合成过程分析一文。
在Android平台上,Chromium提供给Plugin调用的OpenGL接口称为PPB_OPENGLES2_INTERFACE接口。PPB_OPENGLES2_INTERFACE接口提供了一系列的函数,每一个函数都对应于一个glXXX接口,如图2所示:
图2 Plugin调用OpenGL接口的过程
在调用PPB_OPENGLES2_INTERFACE接口提供的函数时,GLES2Implementation类的相应成员函数会被调用。例如,当PPB_OPENGLES2_INTERFACE接口提供的函数ActiveTexture被调用时,GLES2Implementation类的成员函数ActiveTexture。GLES2Implementation类的成员函数会将要执行的GPU命令写入到一个缓冲区中去,然后通过一个PpapiCommandBufferProxy对象通知Render进程执行缓冲区中的GPU命令。Render进程又会通过一个CommandBufferProxyImpl对象将要执行的GPU命令转发给GPU进程中的一个GpuCommandBufferStub处理。这意味Plugin最终是通过GPU进程执行GPU命令的。关于GLES2Implementation、CommandBufferProxyImpl和GpuCommandBufferStub这三类执行GPU命令的过程,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。
Plugin在通过PPB_OPENGLES2_INTERFACE接口执行OpenGL函数(GPU命令)之前,首先要初始化一个OpenGL环境。这个初始化操作发生在Plugin第一次知道自己的视图大小时,也就是知道它对应的<embed>标签的视图大小时。初始化过程如图3所示:
图3 Plugin的OpenGL环境初始化过程
Plugin的视图大小是由WebKit计算出来的。计算出来之后,WebKit会通过运行在Render进程中的Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象,向运行Plugin进程中的Plugin Instance发出通知。Plugin Instance获得了这个通知之后,就可以初始化一个OpenGL环境了。
Plugin Instance Proxy是通过调用PPP_INSTANCE_INTERFACE_1_1接口提供的一个函数DidChangeView向Plugin Instance发出通知的,后者又是通过向目标Plugin进程发送一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息发出该通知的。
类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息携带了一个参数PpapiMsg_PPPInstance_DidChangeView。这个参数描述的是一个Routing ID,表示Plugin进程要将类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息交给一个PPP_Instance_Proxy对象处理。这个PPP_Instance_Proxy对象获得该消息后,又会在当前Plugin进程中获得一个PPP_INSTANCE_INTERFACE_1_1接口,并且调用该接口提供的函数DidChangeView。该函数会找到目标Plugin Instance,并且调用它的成员函数DidChangeView。这样,Plugin Instance就可以初始化OpenGL环境了。以我们在前面Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example为例,它的成员函数DidChangeView就通过调用另外一个成员函数InitGL初始化OpenGL环境的。
Plugin在初始化OpenGL环境的过程中,有两件重要的事情要做。第一件事情是创建一个OpenGL上下文,过程如图4所示:
图4 Plugin的OpenGL上文创建过程
在Plugin进程中,OpenGL上下文通过Graphics3D类描述。因此,创建OpenGL上下文意味着是创建一个Graphics3D对象。这个Graphics3D对象在创建的过程中,会调用PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的一个函数Create。该函数又会通过一个APP_ID_RESOURCE_CREATION接口向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息。在Plugin进程中,APP_ID_RESOURCE_CREATION接口是通过一个ResourceCreationProxy对象实现的,因此,Plugin进程实际上是通过ResourceCreationProxy类向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的。
Plugin进程在向Render进程发送类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息时,会指定一个参数APP_ID_PPB_GRAPHICS_3D,表示Render进程在接收到该消息后,要将其分发给一个PPB_Graphics3D_Proxy对象处理。这个PPB_Graphics3D_Proxy对象又会通过调用PPB_Graphics3D_Impl类的静态成员函数CreateRaw创建一个PPB_Graphics3D_Impl对象。这个PPB_Graphics3D_Impl对象在Render进程描述的就是一个OpenGL上下文。
Plugin在初始化OpenGL环境的过程中做的第二件事情就是将刚刚创建出来的OpenGL上下文指定为当前要使用的OpenGL上下文。这个过程称为OpenGL上下文绑定,如图5所示:
图5 Plugin绑定OpenGL上下文的过程
Plugin是通过调用PPB_INSTANCE_INTERFACE_1_0接口提供的函数BindGraphics进行OpenGL上下文绑定的。该函数又会通过一个APP_ID_PPB_INSTANCE接口向Render进程发送一个类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息。在Plugin进程中,APP_ID_PPB_INSTANCE接口是通过一个PPB_Instance_Proxy对象实现的,因此,Plugin进程实际上是通过PPB_Instance_Proxy类向Render进程发送一个类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息的。
Plugin进程在向Render进程发送类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息时,会指定一个参数APP_ID_PPB_INSTANCE,表示Render进程在接收到该消息后,要将其分发给一个PPB_Instance_Proxy对象处理。这个PPB_Instance_Proxy对象又会找到目标Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象,并且调用该PepperPluginInstanceImpl对象的成员函数BindGraphics。
PepperPluginInstanceImpl类的成员函数BindGraphics在执行的过程中,会将指定的OpenGL上下文,也就是前面创建一个PPB_Graphics3D_Impl对象标记为被绑定,这样它接下来就会作为Plugin当前使用的OpenGL上下文了。
OpenGL环境初始化完成之后,Plugin就可以使用图2所示的PPB_OPENGLES2_INTERFACE接口来执行3D渲染了。一帧渲染完毕,Plugin需要交换当前使用的OpenGL上下文的前后两个缓冲区,也就是执行一个SwapBuffers操作。这个操作的执行过程如图6所示:
图6 Plugin执行SwapBuffers操作的过程
前面提到,在Plugin进程中,OpenGL上下文是通过一个Graphics3D对象描述的。这个Graphics3D对象可以通过PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的成员函数SwapBuffers完成SwapBuffers操作。
PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的成员函数SwapBuffers在执行的过程中,会向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息。这个消息携带了一个参数API_ID_PPB_GRAPHICS_3D,表示Render进程需要将该消息分发给一个PPB_Graphics3D_Proxy对象处理。这个PPB_Graphics3D_Proxy对象会找到Plugin当前绑定的OpenGL上下文,也就是一个PPB_Graphics3D_Impl对象,并且调用该PPB_Graphics3D_Impl对象的成员函数DoSwapBuffers。这时候就可以完成一个SwapBuffers操作,从而也完成一个帧的渲染流程。
接下来,我们就从WebKit通知Plugin视图大小开始,分析Plugin执行3D渲染的完整流程。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,WebKit为<embed>标签创建了一个Plugin Instance之后,会将其放在一个由WebPluginContainerImpl类描述的Plugin View中。当<embed>标签的视图大小发生变化时,WebPluginContainerImpl类的成员函数reportGeometry就会被调用,它的实现如下所示:
void WebPluginContainerImpl::reportGeometry() { ...... IntRect windowRect, clipRect; Vector<IntRect> cutOutRects; calculateGeometry(frameRect(), windowRect, clipRect, cutOutRects); m_webPlugin->updateGeometry(windowRect, clipRect, cutOutRects, isVisible()); ...... }这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp中。
WebPluginContainerImpl类的成员函数reportGeometry首先是调用成员函数calulateGeometry计算<embed>标签的视图大小,然后再将计算得到的信息告知Content层,这是通过成员变量m_webPlugin指向的一个PepperWebPluginImpl对象的成员函数updateGeometry实现的。这个PepperWebPluginImpl对象的创建过程可以参考前面Chromium的Plugin进程启动过程分析一文。
接下来我们继续分析PepperWebPluginImpl类的成员函数updateGeometry的实现,以便了解Render进程通知Plugin它描述的<embed>标签的大小的过程,如下所示:
void PepperWebPluginImpl::updateGeometry( const WebRect& window_rect, const WebRect& clip_rect, const WebVector<WebRect>& cut_outs_rects, bool is_visible) { plugin_rect_ = window_rect; if (!instance_->FlashIsFullscreenOrPending()) { std::vector<gfx::Rect> cut_outs; for (size_t i = 0; i < cut_outs_rects.size(); ++i) cut_outs.push_back(cut_outs_rects[i]); instance_->ViewChanged(plugin_rect_, clip_rect, cut_outs); } }这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_webplugin_impl.cc中。
PepperWebPluginImpl类的成员变量instance_指向的是一个PepperPluginInstanceImpl对象。从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,这个PepperPluginInstanceImpl对象是用来在Render进程中描述一个Plugin Instance Proxy的。
PepperWebPluginImpl类的成员函数updateGeometry首先调用上述PepperPluginInstanceImpl对象的成员函数FlashIsFullscreenOrPending判断当前正在处理的Plugin是否是一个Flash Plugin。如果是一个Flash Plugin,并且它当前处于全屏状态或者即将进入全屏状态,那么PepperWebPluginImpl类的成员函数updateGeometry就不会通知Plugin它描述的<embed>标签的视图大小发生了变化。
我们假设当前正在处理的Plugin不是一个Flash Plugin。这时候PepperWebPluginImpl类的成员函数updateGeometry就会调用上述PepperPluginInstanceImpl对象的成员函数ViewChanged通知Plugin它描述的<embed>标签的视图大小发生了变化。
PepperPluginInstanceImpl类的成员函数ViewChanged的实现如下所示:
void PepperPluginInstanceImpl::ViewChanged( const gfx::Rect& position, const gfx::Rect& clip, const std::vector<gfx::Rect>& cut_outs_rects) { ...... view_data_.rect = PP_FromGfxRect(position); view_data_.clip_rect = PP_FromGfxRect(clip); ...... SendDidChangeView(); }这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。
PepperPluginInstanceImpl类的成员函数ViewChanged首先将当前正在处理的Plugin描述的<embed>标签的视图大小信息保存在成员变量view_data_描述的一个ppapi::ViewData对象中,然后再调用另外一个成员函数SendDidChangeView向运行在Plugin进程中的Plugin Instance发送一个视图大小变化通知。
PepperPluginInstanceImpl类的成员函数SendDidChangeView的实现如下所示:
void PepperPluginInstanceImpl::SendDidChangeView() { ...... ScopedPPResource resource( ScopedPPResource::PassRef(), (new PPB_View_Shared(ppapi::OBJECT_IS_IMPL, pp_instance(), view_data_)) ->GetReference()); ...... if (instance_interface_) { instance_interface_->DidChangeView( pp_instance(), resource, &view_data_.rect, &view_data_.clip_rect); } }
这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。
PepperPluginInstanceImpl类的成员函数SendDidChangeView首先是将成员变量view_data_描述的ppapi::ViewData对象封装在一个PPB_View_Shared对象。从前面的分析可以知道,被封装的ppapi::ViewData对象描述的当前正在处理的Plugin描述的<embed>标签的视图大小信息。封装得到的PPB_View_Shared对象接下来会传递给PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView使用。
从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PepperPluginInstanceImpl类的成员变量instance_interface_指向的是一个PPP_Instance_Combined对象。这个PPP_Instance_Combined对象描述的是一个PPP_INSTANCE_INTERFACE_1_1接口。运行在Render进程中的Plugin Instance Proxy可以通过这个接口与运行在Plugin进程中的Plugin Instance通信。
PepperPluginInstanceImpl类的成员函数SendDidChangeView主要就是调用上述PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView通知运行在Plugin进程中的Plugin Instance,它描述的<embed>标签的视图大小发生了变化,也就是通过调用成员变量instance_interface_指向的PPP_Instance_Combined对象的成员函数DidChangeView进行通知。
PPP_Instance_Combined类的成员函数DidChangeView的实现如下所示:
void PPP_Instance_Combined::DidChangeView(PP_Instance instance, PP_Resource view_changed_resource, const struct PP_Rect* position, const struct PP_Rect* clip) { if (instance_1_1_.DidChangeView) { CallWhileUnlocked( instance_1_1_.DidChangeView, instance, view_changed_resource); } ...... }这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。
从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Combined类的成员变量instance_1_1_描述的是一个PPP_Instance_1_1对象。这个PPP_Instance_1_1对象描述的就是上述的PPP_INSTANCE_INTERFACE_1_1接口。PPP_Instance_Combined类的成员函数DidChangeView通过一个帮助函数CallWhilleUnlocked调用这个PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView,以便向运行在Plugin进程中的Plugin Instance发出一个视图大小变化通知。
从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文还可以知道,上述PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView实现在ppp_instance_proxy.cc文件中,如下所示:
void DidChangeView(PP_Instance instance, PP_Resource view_resource) { HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance); EnterResourceNoLock<PPB_View_API> enter_view(view_resource, false); ...... dispatcher->Send(new PpapiMsg_PPPInstance_DidChangeView( API_ID_PPP_INSTANCE, instance, enter_view.object()->GetData(), flash_fullscreen)); }这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。
参数instance的类型为PP_Instance。PP_Instance描述的实际上是一个32位的有符号整数。这个32位的有符号整数是用来描述一个Plugin的ID。通过这个ID,运行在Render进程中的Plugin Instance Proxy与运行在Plugin进程中的Plugin Instance就能一一对应起来的。
函数DidChangeView首先通过调用HostDispatcher类的静态成员函数GetForInstance获得一个HostDispatcher对象。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,每一个Plugin进程在Render进程中都有一个对应的HostDispatcher对象。这个HostDispatcher对象就是用来与它对应的Plugin进程通信的。给出一个PP_Instance,HostDispatcher类的静态成员函数GetForInstance就可以获得这个PP_Instance描述的Plugin Instance所运行在的Plugin进程对应的HostDispatcher对象。
获得了目标Plugin进程对应的HostDispatcher对象之后,就可以向它发送一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息。这个IPC消息携带了四个参数:
1. API_ID_PPP_INSTANCE,要求Plugin进程将该IPC消息分发给API_ID_PPP_INSTANCE接口处理。
2. instance,表示目标Plugin Instance。
3. ppapi::ViewData,表示目标Plugin Instance的当前视图大小。
4. flash_fullscreen,表示目标Plugin Instance是否处于全屏状态。
其中,第3个参数描述的ppapi::ViewData对象来自于前面在PepperPluginInstanceImpl类的成员函数SendDidChangeView中封装的PPB_View_Shared对象。这个PPB_View_Shared对象对象是通过函数DidChangeView的第2个参数view_resource传递进来的。
函数DidChangeView首先将上述PPB_View_Shared对象封装在一个EnterResourceNoLock<PPB_View_API>对象中。接下来通过调用这个EnterResourceNoLock<PPB_View_API>对象的成员函数object就可以获得它封装的PPB_View_Shared对象。再调用这个PPB_View_Shared对象的成员函数GetData即可以获得它内部封装的一个ppapi::ViewData对象。这个ppapi::ViewData对象内部保存了目标Plugin Instance的当前视图大小信息,因此可以作为上述IPC消息的第3个参数。
从前面Chromium的Plugin进程启动过程分析一文可以知道,每一个Plugin进程都存在一个PluginDispatcher对象。Plugin进程将会通过这个PluginDispatcher对象的成员函数OnMessageReceived接收Render进程发送过来的IPC消息。这意味着前面从Render进程发送过来的类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息是通过PluginDispatcher类的成员函数OnMessageReceived接收的。
PluginDispatcher类的成员函数OnMessageReceived是从父类Dispatcher继承下来的,它的实现如下所示:
bool Dispatcher::OnMessageReceived(const IPC::Message& msg) { ...... InterfaceProxy* proxy = GetInterfaceProxy( static_cast<ApiID>(msg.routing_id())); ...... return proxy->OnMessageReceived(msg); }这个函数定义在文件external/chromium_org/ppapi/proxy/dispatcher.cc中。
从前面的分析可以知道,此时参数msg指向的Message对象描述的是一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息,该消息的Routing ID为API_ID_PPP_INSTANCE,表示要将该消息分发给一个API_ID_PPP_INSTANCE接口处理。这个API_ID_PPP_INSTANCE接口可以通过调用调用另外一个成员函数GetInterfaceProxy获得。
在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中,我们已经分析过Dispatcher类的成员函数GetInterfaceProxy的实现,因此这里不再复述。再结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_PPP_INSTANCE接口是由一个PPP_Instance_Proxy对象实现的,Dispatcher类的成员函数GetInterfaceProxy接下来会将参数msg描述的类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息分发给它处理,也就是调用它的成员函数OnMessageReceived。
PPP_Instance_Proxy类的成员函数OnMessageReceived的实现如下所示:
bool PPP_Instance_Proxy::OnMessageReceived(const IPC::Message& msg) { ...... bool handled = true; IPC_BEGIN_MESSAGE_MAP(PPP_Instance_Proxy, msg) ...... IPC_MESSAGE_HANDLER(PpapiMsg_PPPInstance_DidChangeView, OnPluginMsgDidChangeView) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。
从这里可以看到,PPP_Instance_Proxy类的成员函数OnMessageReceived将类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息分发给另外一个成员函数OnPluginMsgDidChangeView处理,如下所示:
void PPP_Instance_Proxy::OnPluginMsgDidChangeView( PP_Instance instance, const ViewData& new_data, PP_Bool flash_fullscreen) { ...... combined_interface_->DidChangeView(instance, resource, &new_data.rect, &new_data.clip_rect); }
这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。
从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Proxy类的成员变量combined_interface_指向的是一个PPP_Instance_Combined对象。PPP_Instance_Proxy类的成员函数OnPluginMsgDidChangeView主要是调用这个PPP_Instance_Combined对象的成员函数DidChangeView通知参数instance描述的Plugin Instance,它的视图大小发生了变化。
PPP_Instance_Combined类的成员函数DidChangeView的实现如下所示:
void PPP_Instance_Combined::DidChangeView(PP_Instance instance, PP_Resource view_changed_resource, const struct PP_Rect* position, const struct PP_Rect* clip) { if (instance_1_1_.DidChangeView) { CallWhileUnlocked( instance_1_1_.DidChangeView, instance, view_changed_resource); } ...... }这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。
从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Combined类的成员变量instance_1_1_指向的是一个PPP_Instance对象。这个PPP_Instance对象的成员变量DidChangeView是一个函数指针,它指向的函数为Instance_DidChangeView。PPP_Instance_Combined类的成员函数DidCreate主要是调用这个函数通知参数instance描述的Plugin Instance,它的视图大小发生了变化。
函数Instance_DidChangeView的实现,如下所示:
void Instance_DidChangeView(PP_Instance pp_instance, PP_Resource view_resource) { Module* module_singleton = Module::Get(); ...... Instance* instance = module_singleton->InstanceForPPInstance(pp_instance); ...... instance->DidChangeView(View(view_resource)); }这个函数定义在文件external/chromium_org/ppapi/cpp/module.cc中。
函数Instance_DidChangeView首先调用Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,这个pp::Module单例对象描述的就是在当前Plugin进程中加载的Plugin Module。
从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,一个Plugin Module创建的所有Plugin Instance都会以它们的ID为键值,保存在一个std::map中。因此,函数Instance_DidChangeView可以在这个std::map中找到与参数pp_instance对应的Plugin Instance,即一个pp::Instance对象。这个通过调用前面获得的pp::Module单例对象的成员函数InstanceForPPInstance实现的。获得了目标pp::Instance对象之后,就可以调用它的成员函数DidChangeView,以便通知它视图大小发生了变化。
我们在开发一个Plugin的时候,会自定义一个pp::Instance类。例如,在前面Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example,它自定义的pp::Instance类为GLES2DemoInstance。自定义的GLES2DemoInstance类是从pp::Instance类继承下来的,并且会重写成员函数DidChangeView。这意味着接下来GLES2DemoInstance类的成员函数DidChangeView会被调用。
GLES2DemoInstance类的成员函数DidChangeView的实现如下所示:
void GLES2DemoInstance::DidChangeView( const pp::Rect& position, const pp::Rect& clip_ignored) { ...... plugin_size_ = position.size(); // Initialize graphics. InitGL(0); }这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。
参数position描述的一个pp::Rect对象记录了当前正在处理的Plugin Instance的当前视图大小。GLES2DemoInstance类的成员函数DidChangeView首先将这个视图大小记录在成员变量plugin_size_中,接下来又调用另外一个成员函数InitGL初始化一个OpenGL环境。
GLES2DemoInstance类的成员函数InitGL的实现如下所示:
void GLES2DemoInstance::InitGL(int32_t result) { ...... if (context_) { context_->ResizeBuffers(plugin_size_.width(), plugin_size_.height()); return; } int32_t context_attributes[] = { PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, PP_GRAPHICS3DATTRIB_BLUE_SIZE, 8, PP_GRAPHICS3DATTRIB_GREEN_SIZE, 8, PP_GRAPHICS3DATTRIB_RED_SIZE, 8, PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 0, PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 0, PP_GRAPHICS3DATTRIB_SAMPLES, 0, PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0, PP_GRAPHICS3DATTRIB_WIDTH, plugin_size_.width(), PP_GRAPHICS3DATTRIB_HEIGHT, plugin_size_.height(), PP_GRAPHICS3DATTRIB_NONE, }; context_ = new pp::Graphics3D(this, context_attributes); ...... assert(BindGraphics(*context_)); ...... FlickerAndPaint(0, true); }这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。
初始化Plugin的OpenGL环境需要执行两个操作:
1. 创建一个OpenGL上下文。这个OpenGL上下文用一个pp::Graphics3D对象描述。
2. 将创建出来的OpenGL上下文与Plugin进行绑定,也就是将它指定为Plugin当前使用的OpenGL上下文。这可以通过调用父类pp::Instance继承下来的成员函数BindGraphics来完成。
GLES2DemoInstance类的成员变量context_就是用来描述Plugin当前使用的OpenGL上下文。如果这个OpenGL上下文还没有创建,那么GLES2DemoInstance类的成员函数InitGL就会根据Plugin当前的视图大小进行创建。否则的话,就只会改变这个OpenGL上下文描述的绘图缓冲区的大小。
完成以上两个操作之后,GLES2DemoInstance类的成员函数InitGL就会调用另外一个成员函数FlickerAndPaint渲染Plugin的视图。渲染出来的内容最后就会合成在网页的UI中显示出来。
接下来我们先分析OpenGL上下文的创建过程,也就是一个pp::Graphics3D对象的创建过程,我们pp::Graphics3D类的构造函数开始分析,它的实现如下所示:
Graphics3D::Graphics3D(const InstanceHandle& instance, const int32_t attrib_list[]) { if (has_interface<PPB_Graphics3D_1_0>()) { PassRefFromConstructor(get_interface<PPB_Graphics3D_1_0>()->Create( instance.pp_instance(), 0, attrib_list)); } }这个函数定义在文件external/chromium_org/ppapi/cpp/graphics_3d.cc中。
pp::Graphics3D类的构造函数首行调用模板函数has_interface<PPB_Graphics3D_1_0>检查在当前Plugin进程中加载的Plugin Module是否支持PPB_GRAPHICS_3D_INTERFACE_1_0。如果支持的话,那么就会再调用另外一个模板函数get_interface<PPB_Graphics3D_1_0>获得这个PPB_GRAPHICS_3D_INTERFACE_1_0接口。获得了PPB_GRAPHICS_3D_INTERFACE_1_0接口之后,就可以调用它提供的函数Create请求Render进程创建一个OpenGL上下文了。
从后面的分析我们可以知道,PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数Create创建出来的OpenGL上下文用一个proxy::proxy::Graphics3D对象描述,不过它返回给调用者的是分配给proxy::proxy::Graphics3D对象的一个资源ID。pp::Graphics3D类的构造函数会调用从父类pp::Resource继承下来的成员函数PassRefFromConstructor将这个资源ID保存在成员变量pp_resource_中,如下所示:
void Resource::PassRefFromConstructor(PP_Resource resource) { PP_DCHECK(!pp_resource_); pp_resource_ = resource; }这个函数定义在文件external/chromium_org/ppapi/cpp/resource.cc中。
以后通过调用pp::Resource类的成员函数pp_resource即可以获得这个资源ID,如下所示:
class Resource { public: ...... PP_Resource pp_resource() const { return pp_resource_; } ...... };这个函数定义在文件external/chromium_org/ppapi/cpp/resource.h中。
回到 pp::Graphics3D类的构造函数中,接下来我们首先分析PPB_GRAPHICS_3D_INTERFACE_1_0接口的获取过程,也就是模板函数get_interface<PPB_Graphics3D_1_0>的实现,如下所示:
template <typename T> inline T const* get_interface() { static T const* funcs = reinterpret_cast<T const*>( pp::Module::Get()->GetBrowserInterface(interface_name<T>())); return funcs; }这个模板函数定义在文件external/chromium_org/ppapi/cpp/module_impl.h中。
这里的模板参数T为PPB_Graphics3D_1_0,展开后得到模板函数get_interface<PPB_Graphics3D_1_0>的具体实现为:
inline PPB_Graphics3D_1_0 const* get_interface() { static PPB_Graphics3D_1_0 const* funcs = reinterpret_cast<PPB_Graphics3D_1_0 const*>( pp::Module::Get()->GetBrowserInterface(interface_name<PPB_Graphics3D_1_0>())); return funcs; }它首先会调用另外一个模板函数interface_name<PPB_Graphics3D_1_0>获得要获取的接口名称,接着再调用前面提到的pp::Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。有了这个pp::Module单例对象之后,就可以调用它的成员函数GetBrowserInterface根据名称获得接口。
我们首先分析模板函数interface_name<PPB_Graphics3D_1_0>的实现,以便知道要获取的接口名称是什么,如下所示:
template <> const char* interface_name<PPB_Graphics3D_1_0>() { return PPB_GRAPHICS_3D_INTERFACE_1_0; }这个模板函数定义在文件ppapi/cpp/graphics_3d.cc中。
从这里可以看到,要获取的接口名称为PPB_GRAPHICS_3D_INTERFACE_1_0,也就我们要获取的是PPB_GRAPHICS_3D_INTERFACE_1_0接口。这个接口可以通过调用前面获得的pp::Module单例对象的GetBrowserInterface获得。
在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_GRAPHICS_3D_INTERFACE_1_0接口是由一个PPB_Graphics3D_1_0对象实现的。这个PPB_Graphics3D_1_0对象的定义如下所示:
const PPB_Graphics3D_1_0 g_ppb_graphics3d_thunk_1_0 = { &GetAttribMaxValue, &Create, &IsGraphics3D, &GetAttribs, &SetAttribs, &GetError, &ResizeBuffers, &SwapBuffers };这个对象定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。
这个PPB_Graphics3D_1_0对象的成员变量Create是一个函数指针。这个函数指针指向了函数Create。这个函数也是定义在文件ppb_graphics_3d_thunk.cc中。
回到Graphics3D类的构造函数中,现在我们就可以知道,它实际上是通过调用上述函数Create请求Render进程为当前正在处理的Plugin创建一个OpenGL上下文的,如下所示:
PP_Resource Create(PP_Instance instance, PP_Resource share_context, const int32_t attrib_list[]) { ...... EnterResourceCreation enter(instance); ...... return enter.functions()->CreateGraphics3D(instance, share_context, attrib_list); }这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。
OpenGL上下文属于一种资源。在Plugin中,创建资源要使用到接口API_ID_RESOURCE_CREATION。函数Create通过构造一个EnterResourceCreation对象来封装对接口API_ID_RESOURCE_CREATION的调用。封装过程如下所示:
EnterResourceCreation::EnterResourceCreation(PP_Instance instance) : EnterBase(), functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) { ...... }这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。
在Plugin进程中,存在一个PluginGlobals单例对象。这个PluginGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数获得。注意,PluginGlobals类是从PpapiGlobals继承下来的。
获得上述PluginGlobals单例对象之后,EnterResourceCreation类的构造函数就调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:
thunk::ResourceCreationAPI* PluginGlobals::GetResourceCreationAPI( PP_Instance instance) { PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance); if (dispatcher) return dispatcher->GetResourceCreationAPI(); return NULL; }这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_globals.cc中。
从前面Chromium的Plugin进程启动过程分析一文可以知道,在Plugin进程中加载的Plugin Module对应有一个PluginDispatcher对象。这个PluginDispatcher对象与运行在Render进程中的HostDispatcher对象对应。所有属于该Plugin Module的Instance都使用相同的PluginDispatcher对象与Render进程通信。
PluginGlobals类的成员函数GetResourceCreationAPI首先调用PluginDispatcher类的静态成员函数GetForInstance获得与参数instance描述的Plugin Instance对应的PluginDispatcher对象。有了这个PluginDispatcher对象之后,就可以调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:
thunk::ResourceCreationAPI* PluginDispatcher::GetResourceCreationAPI() { return static_cast<ResourceCreationProxy*>( GetInterfaceProxy(API_ID_RESOURCE_CREATION)); }这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_dispatcher.cc中。
PluginDispatcher类的成员函数GetResourceCreationAPI调用另外一个成员函数GetInterfaceProxy获得一个API_ID_RESOURCE_CREATION接口。PluginDispatcher类的成员函数GetInterfaceProxy是从父类Dispatcher继承下来的,在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中,我们已经分析过它的实现了。
结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_RESOURCE_CREATION接口被指定为ResourceCreationProxy类的静态成员函数Create,Dispatcher类的成员函数GetInterfaceProxy将会调用这个函数创建一个ResourceCreationProxy对象,如下所示:
InterfaceProxy* ResourceCreationProxy::Create(Dispatcher* dispatcher) { return new ResourceCreationProxy(dispatcher); }这个函数定义在文件external/chromium_org/ppapi/proxy/resource_creation_proxy.cc中。
参数dispatcher指向的是一个PluginDispatcher对象。这个PluginDispatcher对象就是在前面分析的PluginGlobals类的成员函数GetResourceCreationAPI中获取的PluginDispatcher对象。ResourceCreationProxy类的静态成员函数Create使用该PluginDispatcher对象创建了一个ResourceCreationProxy对象,并且返回给最初的调用者,也就是EnterResourceCreation类的构造函数。EnterResourceCreation类的构造函数又会将这个ResourceCreationProxy对象保存在成员变量functions_中。
这一步执行完成后,回到前面分析的函数Create中。这时候就它构造了一个EnterResourceCreation对象,并且这个EnterResourceCreation对象的成员变量functions_指向了一个ResourceCreationProxy对象。
函数Create接下来会调用上述EnterResourceCreation对象的成员函数functions它的成员变量functions_指向的ResourceCreationProxy对象。有了这个ResourceCreationProxy对象之后,就可以调用它的成员函数CreateGraphics3D请求Render进程为当前正在处理的Plugin Instance创建一个OpenGL上下文了。
ResourceCreationProxy类的成员函数CreateGraphics3D的实现如下所示:
PP_Resource ResourceCreationProxy::CreateGraphics3D( PP_Instance instance, PP_Resource share_context, const int32_t* attrib_list) { return PPB_Graphics3D_Proxy::CreateProxyResource( instance, share_context, attrib_list); }这个函数定义在文件external/chromium_org/ppapi/proxy/resource_creation_proxy.cc中。
ResourceCreationProxy类的成员函数CreateGraphics3D调用PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource请求Render进程为当前正在处理的Plugin Instance创建一个OpenGL上下文,如下所示:
PP_Resource PPB_Graphics3D_Proxy::CreateProxyResource( PP_Instance instance, PP_Resource share_context, const int32_t* attrib_list) { PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance); ...... HostResource share_host; gpu::gles2::GLES2Implementation* share_gles2 = NULL; if (share_context != 0) { EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true); if (enter.failed()) return PP_ERROR_BADARGUMENT; PPB_Graphics3D_Shared* share_graphics = static_cast<PPB_Graphics3D_Shared*>(enter.object()); share_host = share_graphics->host_resource(); share_gles2 = share_graphics->gles2_impl(); } std::vector<int32_t> attribs; if (attrib_list) { for (const int32_t* attr = attrib_list; attr[0] != PP_GRAPHICS3DATTRIB_NONE; attr += 2) { attribs.push_back(attr[0]); attribs.push_back(attr[1]); } } attribs.push_back(PP_GRAPHICS3DATTRIB_NONE); HostResource result; dispatcher->Send(new PpapiHostMsg_PPBGraphics3D_Create( API_ID_PPB_GRAPHICS_3D, instance, share_host, attribs, &result)); ...... scoped_refptr<Graphics3D> graphics_3d(new Graphics3D(result)); if (!graphics_3d->Init(share_gles2)) return 0; return graphics_3d->GetReference(); }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。
PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource首先调用PluginDispatcher类的静态成员函数GetForInstance获得与参数instance描述的Plugin Instance对应的一个PluginDispatcher对象。后面将会通过这个PluginDispatcher对象向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息,用来请求Render进程创建一个OpenGL上下文了。
类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息传递了4个参数给Render进程,分别是:
1. API_ID_PPB_GRAPHICS_3D,表示Render进程要将该IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。
2. instance,描述要创建OpenGL上下文的Plugin Instance。
3. share_host,描述另外一个OpenGL上下文,新创建的OpenGL上下文要与它在同一个共享组中。
4. attribs,描述新创建的OpenGL上下文应具有的属性。
Render进程根据要求为目标Plugin Instance创建了一个OpenGL上下文之后,会为该OpenGL上下文分配一个ID,并且会将该ID返回给Plugin进程。Plugin进程再将这个ID封装在一个HostResource对象,然后将这个HostResource对象返回给PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource。
PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource再根据获得的HostResource对象创建一个ppapi::proxy::Graphics3D对象,并且调用它的成员函数Init对它进行初始化,相当于是对新创建的OpenGL上下文进行初始化。初始化完成后,就可以进行使用了。
新创建的Graphics3D对象初始化完成之后,PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource再调用它的成员函数GetReference获得一个类型为PP_Resource的引用。以后通过这个引用就可以使用该Graphics3D对象,也就是新创建的OpenGL上下文。
接下来,我们首先分析Render进程为目标Plugin Instance创建OpenGL上下文的过程,即处理类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的过程,然后再分析新创建的OpenGL上下文在Plugin进程的初始化过程。
前面提到,Render进程会将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的。
类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息在Render进程中的分发过程类似我们在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文提到的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息在Plugin进程中的分发过程,因此这里不再详细描述。
接下来我们继续分析PPB_Graphics3D_Proxy类的成员函数OnMessageReceived处理类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的过程,如下所示:
bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg) #if !defined(OS_NACL) IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_Create, OnMsgCreate) ...... #endif // !defined(OS_NACL) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() // FIXME(brettw) handle bad messages! return handled; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。
从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给另外一个成员函数OnMsgCreate处理,如下所示:
void PPB_Graphics3D_Proxy::OnMsgCreate(PP_Instance instance, HostResource share_context, const std::vector<int32_t>& attribs, HostResource* result) { ...... thunk::EnterResourceCreation enter(instance); if (enter.succeeded()) { result->SetHostResource( instance, enter.functions()->CreateGraphics3DRaw(instance, share_context.host_resource(), &attribs.front())); } }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。
参数instance要描述的是要创建OpenGL上下文的Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象。PPB_Graphics3D_Proxy类的成员函数OnMsgCreate首先是使用它构造一个EnterResourceCreation对象。前面提到,EnterResourceCreation类是用来封装资源创建接口API_ID_RESOURCE_CREATION的。因此,有了前面构造的EnterResourceCreation对象之后,PPB_Graphics3D_Proxy类的成员函数OnMsgCreate就可以使用API_ID_RESOURCE_CREATION接口来为参数instance描述的Plugin Instance创建一个OpenGL上下文了。
EnterResourceCreation对象在Render进程的构造过程与在Plugin进程的构造过程是不一样的。前面我们已经分析过EnterResourceCreation对象在Plugin进程的构造过程,接下来我们继续分析EnterResourceCreation对象在Render进程的构造过程,以便了解Render进程是如何实现API_ID_RESOURCE_CREATION接口的。
我们从EnterResourceCreation类的构造函数开始分析EnterResourceCreation对象在Render进程的构造过程,它的实现如下所示:
EnterResourceCreation::EnterResourceCreation(PP_Instance instance) : EnterBase(), functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) { ...... }这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。
在Render进程中,存在一个HostGlobals单例对象。这个HostGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数Get获得。HostGlobals类与前面提到的PluginGlobals类一样,都是从PpapiGlobals类继承下来的。
获得上述HostGlobals单例对象之后,EnterResourceCreation类的构造函数就调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:
ppapi::thunk::ResourceCreationAPI* HostGlobals::GetResourceCreationAPI( PP_Instance pp_instance) { PepperPluginInstanceImpl* instance = GetInstance(pp_instance); if (!instance) return NULL; return &instance->resource_creation(); }这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc中。
HostGlobals类的成员函数GetResourceCreationAPI首先调用成员函数GetInstance获得参数pp_instance描述的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。有了这个PepperPluginInstanceImpl对象之后,就可以调用它的成员函数resource_creation获得一个API_ID_RESOURCE_CREATION接口,如下所示:
class CONTENT_EXPORT PepperPluginInstanceImpl : public base::RefCounted<PepperPluginInstanceImpl>, public NON_EXPORTED_BASE(PepperPluginInstance), public ppapi::PPB_Instance_Shared, public NON_EXPORTED_BASE(cc::TextureLayerClient), public RenderFrameObserver { public: ...... ppapi::thunk::ResourceCreationAPI& resource_creation() { return *resource_creation_.get(); } ...... private: ...... scoped_ptr<ppapi::thunk::ResourceCreationAPI> resource_creation_; ...... };这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.h中。
PepperPluginInstanceImpl类的成员变量resource_creation_指向的是一个PepperInProcessResourceCreation对象。PepperPluginInstanceImpl类的成员函数resource_creation将这个PepperInProcessResourceCreation对象返回给调用者。这意味在Render进程中,API_ID_RESOURCE_CREATION接口是通过PepperInProcessResourceCreation类实现的。
这一步执行完成后,回到前面分析的PPB_Graphics3D_Proxy类的成员函数OnMsgCreate中。这时候就它构造了一个EnterResourceCreation对象,并且这个EnterResourceCreation对象的成员变量functions_指向了一个PepperInProcessResourceCreation对象。
PPB_Graphics3D_Proxy类的成员函数OnMsgCreate接下来会调用上述EnterResourceCreation对象的成员函数functions它的成员变量functions_指向的PepperInProcessResourceCreation对象。有了这个PepperInProcessResourceCreation对象之后,就可以调用它的成员函数CreateGraphics3DRaw为参数pp_instance描述的Plugin Instance创建一个OpenGL上下文。
PepperInProcessResourceCreation类的成员函数CreateGraphics3DRaw是从父类ResourceCreationImpl继承下来的,它的实现如下所示:
PP_Resource ResourceCreationImpl::CreateGraphics3DRaw( PP_Instance instance, PP_Resource share_context, const int32_t* attrib_list) { return PPB_Graphics3D_Impl::CreateRaw(instance, share_context, attrib_list); }这个函数定义在文件external/chromium_org/content/renderer/pepper/resource_creation_impl.cc中。
ResourceCreationImpl类的成员函数CreateGraphics3DRaw调用PPB_Graphics3D_Impl类的静态成员函数CreateRaw为参数pp_instance描述的Plugin Instance创建一个OpenGL上下文,如下所示:
PP_Resource PPB_Graphics3D_Impl::CreateRaw(PP_Instance instance, PP_Resource share_context, const int32_t* attrib_list) { PPB_Graphics3D_API* share_api = NULL; if (share_context) { EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true); if (enter.failed()) return 0; share_api = enter.object(); } scoped_refptr<PPB_Graphics3D_Impl> graphics_3d( new PPB_Graphics3D_Impl(instance)); if (!graphics_3d->InitRaw(share_api, attrib_list)) return 0; return graphics_3d->GetReference(); }这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。
在Render进程中,Plugin使用的OpenGL上下文通过一个PPB_Graphics3D_Impl对象描述。因此,PPB_Graphics3D_Impl类的静态成员函数CreateRaw会创建一个PPB_Graphics3D_Impl对象,并且调用它的成员函数InitRaw对它进行初始化,也就是对它描述的OpenGL上下文进行初始化。初始化完成后,就会获得它的一个PP_Resource引用。这个引用将会返回给Plugin进程。Plugin进程以后就可以通过这个引用来使用前面创建出来的OpenGL上下文了。
接下来我们继续分析Render进程为Plugin创建的OpenGL上下文的初始化过程,也就是PPB_Graphics3D_Impl类的成员函数InitRaw的实现,如下所示:
bool PPB_Graphics3D_Impl::InitRaw(PPB_Graphics3D_API* share_context, const int32_t* attrib_list) { ...... RenderThreadImpl* render_thread = RenderThreadImpl::current(); ...... channel_ = render_thread->EstablishGpuChannelSync( CAUSE_FOR_GPU_LAUNCH_PEPPERPLATFORMCONTEXT3DIMPL_INITIALIZE); ...... command_buffer_ = channel_->CreateOffscreenCommandBuffer( surface_size, share_buffer, attribs, GURL::EmptyGURL(), gpu_preference); ...... return true; }这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。
PPB_Graphics3D_Impl类的成员函数InitRaw首先调用RenderThreadImpl类的静态成员函数current获得一个RenderThreadImpl对象。这个RenderThreadImpl对象描述的是当前Render进程的Render线程。有了这个RenderThreadImpl对象之后,就可以调用它的成员函数EstablishGpuChannelSync创建一个GPU通道,也就是一个GpuChannelHost对象。这个GPU通道的详细创建过程,可以参考前面Chromium的GPU进程启动过程分析一文。
PPB_Graphics3D_Impl类的成员函数InitRaw接下来又会调用前面创建出来的GpuChannelHost对象的成员函数CreateOffscreenCommandBuffer请求GPU进程为其创建一个离屏渲染类型的OpenGL上下文。这个离屏渲染类型的OpenGL上下文是通过一个CommandBufferProxyImpl对象描述的。Render进程请求GPU进程创建OpenGL上下文的过程,可以参考前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文。以后Plugin执行GPU命令时,将会作用在上述离屏渲染的OpenGL上下文中。
这一步执行完成后,Render进程就为Plugin创建了一个OpenGL上下文。这个OpenGL上下文通过一个PPB_Graphics3D_Impl对象描述。这个PPB_Graphics3D_Impl对象会被分配一个ID,并且这个ID会返回给Plugin进程。如前所述,Plugin进程获得了这个ID之后,就会将其封装在一个Graphics3D对象中。这个Graphics3D对象是用来Plugin进程描述一个OpenGL上下文的。
回到前面分析的PPB_Graphics3D_Proxy类的成员函数CreateProxyResource中,这时候它就获得了一个OpenGL上下文之后。这个OpenGL上下文在Plugin进程中同样需要进行初始化。如前所述,这是通过调用ppapi::proxy::Graphics3D类的成员函数Init实现的。接下我们就继续分析Plugin进程初始化一个OpenGL上下文的过程,也就是分析ppapi::proxy::Graphics3D类的成员函数Init的实现,如下所示:
bool Graphics3D::Init(gpu::gles2::GLES2Implementation* share_gles2) { ...... command_buffer_.reset( new PpapiCommandBufferProxy(host_resource(), dispatcher)); return CreateGLES2Impl(kCommandBufferSize, kTransferBufferSize, share_gles2); }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。
ppapi::proxy::Graphics3D类的成员函数Init首先会创建一个PpapiCommandBufferProxy对象。这个PpapiCommandBufferProxy对象是用来与前面在Render进程中创建的CommandBufferProxyImpl对象通信的,也就是前者会将提交给它的GPU命令转发给后者处理,后者又会将接收到的GPU命令发送给GPU进程执行。
ppapi::proxy::Graphics3D类的成员函数Init接下来又会调用成员函数CreateGLES2Impl根据上述PpapiCommandBufferProxy对象创建一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,Plugin就可以调用它提供的OpenGL函数执行3D渲染了。
ppapi::proxy::Graphics3D类的成员函数CreateGLES2Impl是从父类PPB_Graphics3D_Shared继承下来的,它的实现如下所示:
bool PPB_Graphics3D_Shared::CreateGLES2Impl( int32 command_buffer_size, int32 transfer_buffer_size, gpu::gles2::GLES2Implementation* share_gles2) { gpu::CommandBuffer* command_buffer = GetCommandBuffer(); DCHECK(command_buffer); // Create the GLES2 helper, which writes the command buffer protocol. gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(command_buffer)); if (!gles2_helper_->Initialize(command_buffer_size)) return false; // Create a transfer buffer used to copy resources between the renderer // process and the GPU process. const int32 kMinTransferBufferSize = 256 * 1024; const int32 kMaxTransferBufferSize = 16 * 1024 * 1024; transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get())); ...... // Create the object exposing the OpenGL API. gles2_impl_.reset(new gpu::gles2::GLES2Implementation( gles2_helper_.get(), share_gles2 ? share_gles2->share_group() : NULL, transfer_buffer_.get(), bind_creates_resources, lose_context_when_out_of_memory, GetGpuControl())); if (!gles2_impl_->Initialize( transfer_buffer_size, kMinTransferBufferSize, std::max(kMaxTransferBufferSize, transfer_buffer_size), gpu::gles2::GLES2Implementation::kNoLimit)) { return false; } ...... return true; }
这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_graphics_3d_shared.cc。
在Chromium中,Command Buffer GL接口是通过GLES2Implementation类描述的。因此,PPB_Graphics3D_Shared类的成员函数CreateGLES2Impl将会创建一个GLES2Implementation对象,并且保存在成员变量gles2_impl_中。
创建这个GLES2Implementation对象需要一个Command Buffer和一个Transfer Buffer。前者通过一个GLES2CmdHelper对象描述,后者通过一个TransferBuffer对象描述的。在创建GLES2CmdHelper对象的过程中,又需要用到前面创建的一个PpapiCommandBufferProxy对象,以便可以将Plugin要执行的GPU命令转发给Render进程处理。这个PpapiCommandBufferProxy对象可以通过调用PPB_Graphics3D_Shared类的成员函数GetCommandBuffer获得。关于Command Buffer GL接口的创建和使用,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。
这一步执行完成后,回到前面分析的GLES2DemoInstance类的成员函数InitGL中,这时候它就创建了一个OpenGL上下文,并且这个OpenGL上下文已经初始化完成。接下来,GLES2DemoInstance类的成员函数InitGL会将前面创建出来的OpenGL上下文绑定到当前正在处理的Plugin Instance中去。执行了这个操作之后,Plugin Instance才可以在该OpenGL上下文中执行GPU命令。
给一个Plugin Instance绑定OpenGL上下文是通过调用pp::Instance类的成员函数BindGraphics实现的,如下所示:
bool Instance::BindGraphics(const Graphics3D& graphics) { if (!has_interface<PPB_Instance_1_0>()) return false; return PP_ToBool(get_interface<PPB_Instance_1_0>()->BindGraphics( pp_instance(), graphics.pp_resource())); }这个函数定义在文件external/chromium_org/ppapi/cpp/instance.cc中。
pp::Instance类的成员函数BindGraphics需要通过PPB_INSTANCE_INTERFACE_1_0接口为当前正在处理的Plugin Instance绑定一个OpenGL上下文。这个OpenGL上下文由参数graphics指向的一个Graphics3D对象描述。
因此,pp::Instance类的成员函数BindGraphics首先会检查当前的Plugin进程是否支持PPB_INSTANCE_INTERFACE_1_0接口。如果支持的话,那么就会通过一个模板函数get_interface<PPB_Instance_1_0>获得该PPB_INSTANCE_INTERFACE_1_0接口,如下所示:
template <typename T> inline T const* get_interface() { static T const* funcs = reinterpret_cast<T const*>( pp::Module::Get()->GetBrowserInterface(interface_name<T>())); return funcs; }这个模板函数定义在文件external/chromium_org/ppapi/cpp/module_impl.h中。
这里的模板参数T为PPB_Instance_1_0,展开后得到模板函数get_interface<PPB_Instance_1_0>的具体实现为:
inline PPB_Instance_1_0 const* get_interface() { static PPB_Instance_1_0 const* funcs = reinterpret_cast<PPB_Instance_1_0 const*>( pp::Module::Get()->GetBrowserInterface(interface_name<PPB_Instance_1_0>())); return funcs; }它首先会调用另外一个模板函数interface_name<PPB_Instance_1_0>获得要获取的接口名称,接着再调用前面提到的pp::Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。有了这个pp::Module单例对象之后,就可以调用它的成员函数GetBrowserInterface根据名称获得接口。
我们首先分析模板函数interface_name<PPB_Instance_1_0>的实现,以便知道要获取的接口名称是什么,如下所示:
template <> const char* interface_name<PPB_Instance_1_0>() { return PPB_INSTANCE_INTERFACE_1_0; }这个函数定义在文件external/chromium_org/ppapi/cpp/instance.cc中。
从这里可以看到,要获取的接口名称为PPB_INSTANCE_INTERFACE_1_0,也就我们要获取的是PPB_INSTANCE_INTERFACE_1_0接口。这个接口可以通过调用前面获得的pp::Module单例对象的GetBrowserInterface获得。
在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_INSTANCE_INTERFACE_1_0接口是由一个PPB_Instance_1_0对象实现的。这个PPB_Graphics3D_1_0对象的定义如下所示:
const PPB_Instance_1_0 g_ppb_instance_thunk_1_0 = { &BindGraphics, &IsFullFrame };这个对象定义在文件external/chromium_org/ppapi/thunk/ppb_instance_thunk.cc中。
这个PPB_Instance_1_0对象的成员变量BindGraphics是一个函数指针。这个函数指针指向了函数BindGraphics。这个函数也是定义在文件ppb_instance_thunk.cc中。
回到pp::Instance类的成员函数BindGraphics中,现在我们就可以知道,它实际上是通过调用上述函数BindGraphics请求Render进程为当前正在处理的Plugin绑定一个OpenGL上下文的,如下所示:
PP_Bool BindGraphics(PP_Instance instance, PP_Resource device) { ...... EnterInstance enter(instance); ...... return enter.functions()->BindGraphics(instance, device); }
这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_instance_thunk.cc中。
函数BindGraphics需要通过另外一个接口API_ID_PPB_INSTANCE向Render进程发送一个IPC消息,以便请求后者为参数instance描述的Plugin Instance绑定另外一个参数device描述的OpenGL上下文。
函数BindGraphics是通过EnterInstance类来使用接口API_ID_RESOURCE_CREATION,因此它首先会构造一个EnterInstance对象,如下所示:
EnterInstance::EnterInstance(PP_Instance instance) : EnterBase(), functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) { ...... }这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。
前面提到,在Plugin进程中,存在一个PluginGlobals单例对象。这个PluginGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数获得。获得了这个PluginGlobals单例对象之后,EnterInstance类的构造函数就调用它的成员函数GetInstanceAPI获得一个API_ID_PPB_INSTANCE接口,如下所示:
thunk::PPB_Instance_API* PluginGlobals::GetInstanceAPI(PP_Instance instance) { PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance); if (dispatcher) return dispatcher->GetInstanceAPI(); return NULL; }这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_globals.cc中。
PluginGlobals类的成员函数GetInstanceAPI首先调用PluginDispatcher类的静态成员函数GetForInstance获得一个与参数instance描述的Plugin Instance对应的PluginDispatcher对象。有了这个PluginDispatcher对象之后,再调用它的成员函数GetInstanceAPI获得一个API_ID_PPB_INSTANCE接口,如下所示:
thunk::PPB_Instance_API* PluginDispatcher::GetInstanceAPI() { return static_cast<PPB_Instance_Proxy*>( GetInterfaceProxy(API_ID_PPB_INSTANCE)); }这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_dispatcher.cc中。
PluginDispatcher类的成员函数GetInstanceAPI调用另外一个成员函数GetInterfaceProxy获得一个API_ID_PPB_INSTANCE接口。PluginDispatcher类的成员函数GetInterfaceProxy是从父类Dispatcher继承下来的。Dispatcher类的成员函数GetInterfaceProxy的实现,可以参考前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中。
再结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_PPB_INSTANCE接口被指定为模板函数ProxyFactory<PPB_Instance_Proxy>。Dispatcher类的成员函数GetInterfaceProxy将会调用这个函数创建一个PPB_Instance_Proxy对象。这意味着在Plugin进程中,API_ID_PPB_INSTANCE接口是通过一个PPB_Instance_Proxy对象实现的。这个PPB_Instance_Proxy对象会返回给EnterInstance类的构造函数。
回到前面分析的函数BindGraphics,这时候就它构造了一个EnterInstance对象,并且这个EnterInstance对象的成员变量functions_指向了一个PPB_Instance_Proxy对象。这个PPB_Instance_Proxy对象可以通过调用上述构造的EnterInstance对象的成员函数functions获得。有了这个PPB_Instance_Proxy对象之后,函数BindGraphics就可以调用它的成员函数BindGraphics向Render进程发送一个IPC消息,以便请求它为当前正在处理的Plugin绑定一个OpenGL上下文,如下所示:
PP_Bool PPB_Instance_Proxy::BindGraphics(PP_Instance instance, PP_Resource device) { // If device is 0, pass a null HostResource. This signals the host to unbind // all devices. HostResource host_resource; PP_Resource pp_resource = 0; if (device) { Resource* resource = PpapiGlobals::Get()->GetResourceTracker()->GetResource(device); if (!resource || resource->pp_instance() != instance) return PP_FALSE; host_resource = resource->host_resource(); pp_resource = resource->pp_resource(); } else { // Passing 0 means unbinding all devices. dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics( API_ID_PPB_INSTANCE, instance, 0)); return PP_TRUE; } // We need to pass different resource to Graphics 2D and 3D right now. Once // 3D is migrated to the new design, we should be able to unify this. EnterResourceNoLock<PPB_Compositor_API> enter_compositor(device, false); EnterResourceNoLock<PPB_Graphics2D_API> enter_2d(device, false); EnterResourceNoLock<PPB_Graphics3D_API> enter_3d(device, false); if (enter_compositor.succeeded() || enter_2d.succeeded()) { dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics( API_ID_PPB_INSTANCE, instance, pp_resource)); return PP_TRUE; } else if (enter_3d.succeeded()) { dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics( API_ID_PPB_INSTANCE, instance, host_resource.host_resource())); return PP_TRUE; } return PP_FALSE; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。
PPB_Instance_Proxy类的成员函数BindGraphics首先判断参数device的值是否不等于NULL。如果不等于NULL,那么它描述的就是一个OpenGL上下文,并且这个OpenGL上下文要与参数instance描述的Plugin Instance进行绑定。否则的话,就说明要将参数instance描述的Plugin Instance当前使用的OpenGL上下文设置为NULL。
在我们这个情景中,参数device的值不等于NULL。在这种情况下,PPB_Instance_Proxy类的成员函数BindGraphics首先会根据这个参数获得一个Resource对象。这个Resource对象描述的就是一个OpenGL上下文。这个OpenGL上下文可能是用来执行2D渲染的,也可能是用来执行3D渲染的。PPB_Instance_Proxy类的成员函数BindGraphics会对这两种情况进行区别,不过最终都会向Render进程发送一个类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息。这个IPC消息携带了3个参数:
1. API_ID_PPB_INSTANCE,要求Render进程将该IPC消息分发给API_ID_PPB_INSTANCE接口处理。
2. instance,表示目标Plugin Instance。
3. 通过参数device获得一个PP_Resource对象,描述的是一个要与目标Plugin Instance进行绑定的OpenGL上下文。
Render进程接收类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息的过程与前面分析的类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息是类似的,只不过前者最后会分发给API_ID_PPB_INSTANCE接口处理,而后者会分发给API_ID_PPB_GRAPHICS_3D接口处理。它们在Render进程的分发过程都可以参考在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文提到的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息在Plugin进程的分发过程。
从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文还可以知道,在Render进程中,API_ID_PPB_INSTANCE接口是由一个PPB_Instance_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息分发给该PPB_Instance_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:
bool PPB_Instance_Proxy::OnMessageReceived(const IPC::Message& msg) { ...... bool handled = true; IPC_BEGIN_MESSAGE_MAP(PPB_Instance_Proxy, msg) #if !defined(OS_NACL) ...... IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBInstance_BindGraphics, OnHostMsgBindGraphics) ...... #endif // !defined(OS_NACL) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。
从这里可以看到,PPB_Instance_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息分发给另外一个成员函数OnHostMsgBindGraphics处理,如下所示:
void PPB_Instance_Proxy::OnHostMsgBindGraphics(PP_Instance instance, PP_Resource device) { // Note that we ignroe the return value here. Otherwise, this would need to // be a slow sync call, and the plugin side of the proxy will have already // validated the resources, so we shouldn't see errors here that weren't // already caught. EnterInstanceNoLock enter(instance); if (enter.succeeded()) enter.functions()->BindGraphics(instance, device); }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。
参数instance要描述的是要绑定OpenGL上下文的Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象。PPB_Instance_Proxy类的成员函数OnHostMsgBindGraphics首先是使用它构造一个EnterInstanceNoLock对象。这个EnterInstanceNoLock对象是用来封装一个Instance API的。这个Instance API可以用来给一个Plugin Instance Proxy绑定一个OpenGL上下文。
接下来我们从EnterInstanceNoLock类的构造函数开始分析一个EnterInstanceNoLock对象的构造过程,如下所示:
EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance) : EnterBase(), functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) { ...... }这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。
前面提到,在Render进程中,存在一个HostGlobals单例对象。这个HostGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数Get获得。获得了这个HostGlobals单例对象之后,EnterInstanceNoLock类的构造函数就调用它的成员函数GetInstanceAPI获得一个Instance API,如下所示:
ppapi::thunk::PPB_Instance_API* HostGlobals::GetInstanceAPI( PP_Instance instance) { // The InstanceAPI is just implemented by the PluginInstance object. return GetInstance(instance); }这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc中。
HostGlobals类的成员函数GetInstanceAPI调用另外一个成员函数GetInstance获得参数instance描述的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象,如下所示:
PepperPluginInstanceImpl* HostGlobals::GetInstance(PP_Instance instance) { ...... InstanceMap::iterator found = instance_map_.find(instance); if (found == instance_map_.end()) return NULL; return found->second; }这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc。
在Render进程中,每一个Plugin Instance Proxy都会以分配给它的ID保存在HostGlobals类的成员变量instance_map_描述的一个std::map中。因此,给出一个ID,就可以在这个std::map中找到它对应的Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。
在Render进程中,Instance API是通过PPB_Instance_API类描述的。PepperPluginInstanceImpl类又是从PPB_Instance_API类继承下来的。因此,一个PepperPluginInstanceImpl对象可以当作一个Instance API使用。这个PepperPluginInstanceImpl对象将会返回给EnterInstanceNoLock类的构造函数,并且保存在EnterInstanceNoLock类的成员变量functions_中。
这一步执行完成后,回到前面分析的PPB_Instance_Proxy类的成员函数OnMessageReceived中。这时候就它构造了一个EnterInstanceNoLock对象,并且这个EnterInstanceNoLock对象的成员变量functions_指向了一个PepperInProcessResourceCreation对象。注意,这个PepperPluginInstanceImpl对象在用作Instance API的同时,也是即将要绑定OpenGL上下文的Plugin Instance Proxy。这个绑定操作是通过调用它的成员函数BindGraphics实现的,如下所示:
PP_Bool PepperPluginInstanceImpl::BindGraphics(PP_Instance instance, PP_Resource device) { ...... scoped_refptr<ppapi::Resource> old_graphics = bound_graphics_3d_.get(); if (bound_graphics_3d_.get()) { bound_graphics_3d_->BindToInstance(false); bound_graphics_3d_ = NULL; } if (bound_graphics_2d_platform_) { bound_graphics_2d_platform_->BindToInstance(NULL); bound_graphics_2d_platform_ = NULL; } if (bound_compositor_) { bound_compositor_->BindToInstance(NULL); bound_compositor_ = NULL; } ...... const ppapi::host::PpapiHost* ppapi_host = RendererPpapiHost::GetForPPInstance(instance)->GetPpapiHost(); ppapi::host::ResourceHost* host = ppapi_host->GetResourceHost(device); PepperGraphics2DHost* graphics_2d = NULL; PepperCompositorHost* compositor = NULL; if (host) { if (host->IsGraphics2DHost()) { graphics_2d = static_cast<PepperGraphics2DHost*>(host); } else if (host->IsCompositorHost()) { compositor = static_cast<PepperCompositorHost*>(host); } ...... } EnterResourceNoLock这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cpp。enter_3d(device, false); PPB_Graphics3D_Impl* graphics_3d = enter_3d.succeeded() ? static_cast (enter_3d.object()) : NULL; if (compositor) { if (compositor->BindToInstance(this)) { bound_compositor_ = compositor; ...... return PP_TRUE; } } else if (graphics_2d) { if (graphics_2d->BindToInstance(this)) { bound_graphics_2d_platform_ = graphics_2d; ...... return PP_TRUE; } } else if (graphics_3d) { // Make sure graphics can only be bound to the instance it is // associated with. if (graphics_3d->pp_instance() == pp_instance() && graphics_3d->BindToInstance(true)) { bound_graphics_3d_ = graphics_3d; ...... return PP_TRUE; } } // The instance cannot be bound or the device is not a valid resource type. return PP_FALSE; }
Plugin Instance不仅可以将UI渲染在一个2D上下文或者一个3D上下文中,还可以渲染在一个Compositor上下文中。一个2D上下文和一个3D上下文描述的都只是一个Layer,而一个Compositor上下文可以描述多个Layer。也就是说,Plugin Instance可以通过Compositor上下文将自己的UI划分为多个Layer进行渲染。Chromium提供了一个PPB_COMPOSITOR_INTERFACE_0_1接口,Plugin Instance可以通过这个接口为自己创建一个Compositor上下文。
其中,2D上下文通过一个PepperGraphics2DHost类描述,3D上下文通过PPB_Graphics3D_Impl类描述,而Compositor上下文通过PepperCompositorHost类描述。PepperPluginInstanceImpl类分别通过bound_graphics_2d_platform_、bound_graphics_3d_和bound_compositor_三个成员变量描述上述三种不同的绘图上下文。
PepperPluginInstanceImpl类的成员函数BindGraphics主要做的工作就是将参数device描述的绘图上下文绑定为当前正在处理的Plugin Instance Proxy的绘图上下文。如果当前正在处理的Plugin Instance Proxy以前绑定过其它的绘图上下文,那么PepperPluginInstanceImpl类的成员函数BindGraphics就会先解除对它们的绑定。
PepperPluginInstanceImpl类的成员函数BindGraphics接下来再判断参数device描述的绘图上下文是2D上下文、3D上下文,还是Compositor上下文,并且获取它所对应的PepperGraphics2DHost对象、PPB_Graphics3D_Impl对象和PepperCompositorHost对象,然后保存在对应的成员变量中。这样,当前正在处理的Plugin Instance Proxy就知道了自己最新绑定的绘图上下文是什么。最后,这些获得的PepperGraphics2DHost对象、PPB_Graphics3D_Impl对象和PepperCompositorHost对象的成员函数BindToInstance也会被调用,表示它们当前处于被绑定状态。
在我们这个情景中,参数device描述的绘图上下文是一个3D上下文,也就是一个3D的OpenGL上下文。因此, PepperPluginInstanceImpl类的成员函数BindGraphics最终会获得一个PPB_Graphics3D_Impl对象,并且保存在成员变量bound_graphics_3d_中。最后,这个PPB_Graphics3D_Impl对象的成员函数BindToInstance会被调用,表示它当前处于被绑定的状态,如下所示:
bool PPB_Graphics3D_Impl::BindToInstance(bool bind) { bound_to_instance_ = bind; return true; }这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。
从前面的调用过程可以知道,参数bind的值等于true。这时候PPB_Graphics3D_Impl类的成员变量bound_to_instance_的值也会被设置为true,表示当前正在处理的PPB_Graphics3D_Impl对象描述的3D上下文处于被绑定状态。
这一步执行完成后,GLES2DemoInstance类就通过PPB_GRAPHICS_3D_INTERFACE_1_0和PPB_INSTANCE_INTERFACE_1_0这两个接口创建和绑定了一个OpenGL上下文,也就是初始化好一个OpenGL环境。接下来,GLES2DemoInstance类就可以通过PPB_OPENGLES2_INTERFACE接口进行3D渲染了。这个PPB_OPENGLES2_INTERFACE接口是在GLES2DemoInstance类的构造函数中获取的,如下所示:
GLES2DemoInstance::GLES2DemoInstance(PP_Instance instance, pp::Module* module) : pp::Instance(instance), pp::Graphics3DClient(this), callback_factory_(this), module_(module), context_(NULL), fullscreen_(false) { assert((gles2_if_ = static_cast<const PPB_OpenGLES2*>( module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE)))); ...... }
这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。
GLES2DemoInstance类的构造函数是通过调用参数module指向的一个pp::Module对象的成员函数GetBrowserInterface获取PPB_OPENGLES2_INTERFACE接口的。在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_OPENGLES2_INTERFACE接口是由一个PPB_OpenGLES2对象实现的。这个PPB_OpenGLES2对象的定义如下所示:
static const struct PPB_OpenGLES2 ppb_opengles2 = { &ActiveTexture, &AttachShader, &BindAttribLocation, &BindBuffer, &BindFramebuffer, &BindRenderbuffer, &BindTexture, &BlendColor, &BlendEquation, &BlendEquationSeparate, &BlendFunc, &BlendFuncSeparate, &BufferData, &BufferSubData, &CheckFramebufferStatus, &Clear, ...... &UseProgram, &ValidateProgram, &VertexAttrib1f, &VertexAttrib1fv, &VertexAttrib2f, &VertexAttrib2fv, &VertexAttrib3f, &VertexAttrib3fv, &VertexAttrib4f, &VertexAttrib4fv, &VertexAttribPointer, &Viewport};这个对象定义在文件这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。
从这里我们就可以看到PPB_OPENGLES2_INTERFACE接口提供的函数,例如函数Clear,就相当于是OpenGL函数glClear。
有了这个PPB_OPENGLES2_INTERFACE接口之后,GLES2DemoInstance类就会在成员函数FlickerAndPaint中调用它提供的函数进行3D渲染,如下所示:
void GLES2DemoInstance::FlickerAndPaint(int32_t result, bool paint_blue) { ...... float r = paint_blue ? 0 : 1; float g = 0; float b = paint_blue ? 1 : 0; float a = 0.75; gles2_if_->ClearColor(context_->pp_resource(), r, g, b, a); gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT); ...... context_->SwapBuffers(cb); ...... }这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。
GLES2DemoInstance类的成员函数FlickerAndPaint执行的3D渲染很简单,仅仅是调用PPB_OPENGLES2_INTERFACE接口提供的函数ClearColor和Clear绘制一个背景色,相当于是调用了OpenGL函数glClearColor和glClear绘制一个背景色。在实际开发中,我们可以调用PPB_OPENGLES2_INTERFACE接口提供的其它函数完成更复杂的3D渲染效果。
从前面的分析可以知道,GLES2DemoInstance类的成员变量context_指向的是一个pp::Graphics3D对象。这个Graphics3D描述的是一个OpenGL上下文。每完成一帧渲染,GLES2DemoInstance类的成员函数FlickerAndPaint都需要调用这个pp::Graphics3D对象的成员函数SwapBuffers,用来交换当前使用的OpenGL上下文的前后两个缓冲区,相当于是调用了EGL函数eglSwapBuffers。
接下来,我们就以PPB_OPENGLES2_INTERFACE接口提供的函数Clear的执行过程为例,分析Plugin是如何通过PPB_OPENGLES2_INTERFACE接口执行3D渲染操作的,它的实现如下所示:
void Clear(PP_Resource context_id, GLbitfield mask) { Enter3D enter(context_id, true); if (enter.succeeded()) { ToGles2Impl(&enter)->Clear(mask); } }这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。
Enter3D定义为一个thunk::EnterResource<thunk::PPB_Graphics3D_API>类,如下所示:
typedef thunk::EnterResource<thunk::PPB_Graphics3D_API> Enter3D;这个类定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。
回到函数Clear中, 它的参数context_id描述的是一个OpenGL上下文资源ID。函数Clear首先将这个资源ID封装在一个EnterResource<thunk::PPB_Graphics3D_API>对象中,如下所示:
template<typename ResourceT, bool lock_on_entry = true> class EnterResource : public subtle::LockOnEntry<lock_on_entry>, // Must be first; see above. public subtle::EnterBase { public: EnterResource(PP_Resource resource, bool report_error) : EnterBase(resource) { Init(resource, report_error); } ...... };这个函数定义在文件external/chromium_org/ppapi/thunk/enter.h中。
EnterResource<thunk::PPB_Graphics3D_API>类的构造函数首先会调用父类EnterBase的构造函数。在调用的时候,会将参数resource描述的资源ID传递进去。EnterBase的构造函数通过这个资源ID获得对应的资源对象,保存在成员变量resource_中,如下所示:
EnterBase::EnterBase(PP_Resource resource) : resource_(GetResource(resource)), retval_(PP_OK) { PpapiGlobals::Get()->MarkPluginIsActive(); }这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。
从前面的分析可以知道,这个资源对象实际上就是一个ppapi::proxy::Graphics3D对象。与此同时,EnterBase的构造函数会通过调用Plugin进程的一个PluginGlobals单例对象的成员函数MarkPluginIsActive将当前正在执行3D渲染操作的Plugin标记为Active状态。
回到EnterResource<thunk::PPB_Graphics3D_API>类的构造函数中,它接下来又会调用另外一个成员函数Init执行其它的初始化工作,如下所示:
template<typename ResourceT, bool lock_on_entry = true> class EnterResource : public subtle::LockOnEntry<lock_on_entry>, // Must be first; see above. public subtle::EnterBase { public: ....... ResourceT* object() { return object_; } ...... private: void Init(PP_Resource resource, bool report_error) { if (resource_) object_ = resource_->GetAs<ResourceT>(); ...... } ResourceT* object_; ...... };这个函数定义在文件external/chromium_org/ppapi/thunk/enter.h中。
EnterResource<thunk::PPB_Graphics3D_API>类的成员函数Init主要是将从父类EnterBase继承下来的成员变量resource_指向的ppapi::proxy::Graphics3D对象转换为一个thunk::PPB_Graphics3D_API对象,并且保存在成员变量object_中。这个thunk::PPB_Graphics3D_API对象以后可以通过调用EnterResource<thunk::PPB_Graphics3D_API>类的成员函数object获得。
注意,ppapi::proxy::Graphics3D类是从ppapi::PPB_Graphics3D_Shared类继承下来的,ppapi::PPB_Graphics3D_Shared类又是从thunk::PPB_Graphics3D_API类继承下来的,因此,我们可以将ppapi::proxy::Graphics3D对象转换为一个thunk::PPB_Graphics3D_API对象。
这一步执行完成后,回到前面分析的函数函数Clear中,这时候它就构造了一个EnterResource<thunk::PPB_Graphics3D_API>对象,并且这个EnterResource<thunk::PPB_Graphics3D_API>对象的成员变量object_指向了一个thunk::PPB_Graphics3D_API对象。函数Clear接下来又会调用另外一个函数ToGles2Impl从前面构造的EnterResource<thunk::PPB_Graphics3D_API>对象中获得一个GLES2Implementation对象,如下所示:
gpu::gles2::GLES2Implementation* ToGles2Impl(Enter3D* enter) { ...... return static_cast<PPB_Graphics3D_Shared*>(enter->object())->gles2_impl(); }这个函数定义在文件ternal/chromium_org/ppapi$ vi shared_impl/ppb_opengles2_shared.cc中。
函数ToGles2Impl首先调用参数enter指向的EnterResource<thunk::PPB_Graphics3D_API>对象的成员函数object获得它的成员变量object_所指向的一个thunk::PPB_Graphics3D_API对象。
前面提到,由于EnterResource<thunk::PPB_Graphics3D_API>类的成员变量object_指向的实际上是一个ppapi::proxy::Graphics3D对象,并且ppapi::proxy::Graphics3D类是从ppapi::PPB_Graphics3D_Shared类继承下来的。因此函数ToGles2Impl又可以将它获得的thunk::PPB_Graphics3D_API对象转换为一个ppapi::PPB_Graphics3D_Shared对象。
有了这个ppapi::PPB_Graphics3D_Shared对象之后,函数ToGles2Impl就可以调用它的成员函数gles2_impl获得它内部维护的一个GLES2Implementation对象。这个GLES2Implementation对象就是在前面分析的ppapi::proxy::Graphics3D类的成员函数CreateGLES2Impl中创建的。
函数ToGles2Impl最后会将获得的GLES2Implementation对象返回给函数Clear。函数Clear获得了这个GLES2Implementation对象之后,就会调用它的成员函数Clear给参数context_id描述的OpenGL上下文设置一个背景色。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,当我们调用GLES2Implementation类的成员函数执行GPU命令时,这些GPU命令实际上只是写入到了一个Command Buffer中。这个Command Buffer在合适的时候将会调用它的成员函数WaitForGetOffsetInRange通知GPU进程执行它里面的GPU命令。
从前面的分析可以知道,为Plugin创建的GLES2Implementation对象所用的Command Buffer是通过一个PpapiCommandBufferProxy对象描述的。当这个WaitForGetOffsetInRange对象的成员函数WaitForGetOffsetInRange被调用的时候,它实际上只是向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息,如下所示:
void PpapiCommandBufferProxy::WaitForTokenInRange(int32 start, int32 end) { ...... bool success; gpu::CommandBuffer::State state; if (Send(new PpapiHostMsg_PPBGraphics3D_WaitForTokenInRange( ppapi::API_ID_PPB_GRAPHICS_3D, resource_, start, end, &state, &success))) UpdateState(state, success); }这个函数定义在文件external/chromium_org/ppapi/proxy/ppapi_command_buffer_proxy.cc中。
这个类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息携带了一个参数ppapi::API_ID_PPB_GRAPHICS_3D,表示Render进程接收到这个IPC消息之后,要分发一个API_ID_PPB_GRAPHICS_3D接口处理。
前面提到,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:
bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg) #if !defined(OS_NACL) ...... IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange, OnMsgWaitForGetOffsetInRange) ...... #endif // !defined(OS_NACL) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() // FIXME(brettw) handle bad messages! return handled; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc。
从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息分发给另外一个成员函数OnMsgWaitForGetOffsetInRange处理,如下所示:
void PPB_Graphics3D_Proxy::OnMsgWaitForGetOffsetInRange( const HostResource& context, int32 start, int32 end, gpu::CommandBuffer::State* state, bool* success) { EnterHostFromHostResource<PPB_Graphics3D_API> enter(context); ...... *state = enter.object()->WaitForGetOffsetInRange(start, end); *success = true; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc。
从前面的分析可以知道,参数context描述的OpenGL上下文在Render进程中用一个PPB_Graphics3D_Impl对象描述。PPB_Graphics3D_Proxy类的成员函数OnMsgWaitForGetOffsetInRange会通过构造一个EnterHostFromHostResource<PPB_Graphics3D_API>对象来获得这个PPB_Graphics3D_Impl对象,并且会调用这个PPB_Graphics3D_Impl对象的成员函数WaitForGetOffsetInRange,用来通知它将Plugin写入在Command Buffer中的GPU命令发送给GPU进程处理。
PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange的实现如下所示:
gpu::CommandBuffer::State PPB_Graphics3D_Impl::WaitForGetOffsetInRange( int32_t start, int32_t end) { GetCommandBuffer()->WaitForGetOffsetInRange(start, end); return GetCommandBuffer()->GetLastState(); }这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。
PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange首先调用成员函数GetCommandBuffer获得一个CommandBuffer对象,如下所示:
gpu::CommandBuffer* PPB_Graphics3D_Impl::GetCommandBuffer() { return command_buffer_; }这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。
PPB_Graphics3D_Impl类的成员函数GetCommandBuffer返回的是成员变量command_buffer_指向的一个CommandBuffer对象。从前面的分析可以知道,这个CommandBuffer对象实现上是一个CommandBufferProxyImpl对象,它是在前面分析的PPB_Graphics3D_Impl类的成员函数InitRaw中创建的。
回到PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange中,它获得了一个CommandBufferProxyImpl对象之后,就会调用它的成员函数WaitForGetOffsetInRange通知GPU进程执行Plugin写入在Command Buffer中的GPU命令。这个通知过程可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。
这一步执行完成后,回到前面分析的GLES2DemoInstance类的成员函数FlickerAndPaint中,它通过PPB_OPENGLES2_INTERFACE接口提供的函数渲染好一帧后,就会调用其成员变量context_指向的一个pp::Graphics3D对象的成员函数SwapBuffers交换Plugin当前使用的OpenGL上下文的前后两个缓冲区,也就是相当于是执行EGL函数eglSwapBuffers。
接下来,我们就从pp::Graphics3D类的成员函数SwapBuffers开始,分析Plugin交换当前使用的OpenGL上下文的前后两个缓冲区的过程,如下所示:
int32_t Graphics3D::SwapBuffers(const CompletionCallback& cc) { ...... return get_interface<PPB_Graphics3D_1_0>()->SwapBuffers( pp_resource(), cc.pp_completion_callback()); }这个函数定义在文件external/chromium_org/ppapi/cpp/graphics_3d.cc中。
pp::Graphics3D类的成员函数SwapBuffers首先调用我们前面分析过的模板函数get_interface<PPB_Graphics3D_1_0>获得一个PPB_GRAPHICS_3D_INTERFACE_1_0接口。获得了PPB_GRAPHICS_3D_INTERFACE_1_0接口之后,再调用它提供的函数SwapBuffers请求Render进程交换正在处理的pp::Graphics3D对象描述的OpenGL上下文的前后缓冲区。
PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数SwapBuffers的实现如下所示:
int32_t SwapBuffers(PP_Resource context, struct PP_CompletionCallback callback) { ...... EnterResource<PPB_Graphics3D_API> enter(context, callback, true); if (enter.failed()) return enter.retval(); return enter.SetResult(enter.object()->SwapBuffers(enter.callback())); }这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。
与前面分析的PPB_OPENGLES2_INTERFACE接口提供的函数Clear类似,PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数SwapBuffers也是通过构造一个EnterResource<thunk::PPB_Graphics3D_API>对象来获得参数context描述的一个OpenGL上下文资源对象,也就是一个ppapi::proxy::Graphics3D对象。有了这个ppapi::proxy::Graphics3D对象之后,就可以调用它的成员函数SwapBuffers请求Render进程交换它描述的OpenGL上下文的前后缓冲区了。
ppapi::proxy::Graphics3D类的成员函数SwapBuffers是从父类ppapi::PPB_Graphics3D_Shared继承下来的,它的实现如下所示:
int32_t PPB_Graphics3D_Shared::SwapBuffers( scoped_refptr<TrackedCallback> callback) { ...... return DoSwapBuffers(); }这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_graphics_3d_shared.cc中。
ppapi::proxy::Graphics3D类的成员函数SwapBuffers又会调用由子类实现的成员函数DoSwapBuffers请求Render进程交换当前正在处理的ppapi::proxy::Graphics3D对象描述的OpenGL上下文的前后缓冲区。
在我们这个情景中,这个子类即为ppapi::proxy::Graphics3D,因此接下来ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers会被调用,它的实现如下所示:
int32 Graphics3D::DoSwapBuffers() { gles2_impl()->SwapBuffers(); IPC::Message* msg = new PpapiHostMsg_PPBGraphics3D_SwapBuffers( API_ID_PPB_GRAPHICS_3D, host_resource()); msg->set_unblock(true); PluginDispatcher::GetForResource(this)->Send(msg); return PP_OK_COMPLETIONPENDING; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。
ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers首先是通过调用成员函数gles2_impl获得前面为Plugin创建的一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,就可以调用它的成员函数SwapBuffers向Command Buffer写入一个gles2::cmds::SwapBuffers命令。GPU进程在执行这个命令的时候,就会交换当前正在处理的ppapi::proxy::Graphics3D对象描述的OpenGL上下文的前后缓冲区。
ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers接下来又会调用PluginDispatcher类的静态成员函数GetForResource获得与当前正在处理的Plugin对应的一个PluginDispatcher对象,并且通过这个PluginDispatcher对象向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息,用来通知Render进程更新当前正在处理的Plugin在网页上的视图。这个IPC消息包含两个参数:
1. API_ID_PPB_GRAPHICS_3D,表示Render进程要将该IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。
2. 通过调用成员函数host_resource获得的一个HostResource对象,这个HostResource对象描述的就是要交换前面缓冲区的OpenGL上下文。
前面提到,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:
bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg) #if !defined(OS_NACL) ...... IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_SwapBuffers, OnMsgSwapBuffers) ...... #endif // !defined(OS_NACL) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() // FIXME(brettw) handle bad messages! return handled; }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。
从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息分发给另外一个成员函数OnMsgSwapBuffers处理,如下所示:
void PPB_Graphics3D_Proxy::OnMsgSwapBuffers(const HostResource& context) { EnterHostFromHostResourceForceCallback<PPB_Graphics3D_API> enter( context, callback_factory_, &PPB_Graphics3D_Proxy::SendSwapBuffersACKToPlugin, context); if (enter.succeeded()) enter.SetResult(enter.object()->SwapBuffers(enter.callback())); }这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。
参数context描述的是一个要交换前后缓冲区的OpenGL上下文。从前面的分析可以知道,在Render进程中,这个OpenGL上下文是通过一个PPB_Graphics3D_Impl对象描述的。PPB_Graphics3D_Proxy类的成员函数OnMsgSwapBuffers通过构造一个EnterHostFromHostResourceForceCallback<PPB_Graphics3D_API>来获得这个PPB_Graphics3D_Impl对象,并且调用这个PPB_Graphics3D_Impl对象的成员函数SwapBuffers更新当前正在处理的Plugin在网页上的视图。
PPB_Graphics3D_Impl类的成员函数SwapBuffers是从父类ppapi::PPB_Graphics3D_Shared继承下来的。前面我们已经分析过ppapi::PPB_Graphics3D_Shared类的成员函数SwapBuffers的实现了,它最终会调用由子类实现的成员函数DoSwapBuffers,即PPB_Graphics3D_Impl类的成员函数DoSwapBuffers。
PPB_Graphics3D_Impl类的成员函数DoSwapBuffers的实现如下所示:
int32 PPB_Graphics3D_Impl::DoSwapBuffers() { ...... if (bound_to_instance_) { // If we are bound to the instance, we need to ask the compositor // to commit our backing texture so that the graphics appears on the page. // When the backing texture will be committed we get notified via // ViewFlushedPaint(). // // Don't need to check for NULL from GetPluginInstance since when we're // bound, we know our instance is valid. HostGlobals::Get()->GetInstance(pp_instance())->CommitBackingTexture(); commit_pending_ = true; } ...... return PP_OK_COMPLETIONPENDING; }这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。
从前面的分析可以知道,当前正在处理的PPB_Graphics3D_Impl对象描述的OpenGL上下文此时处于绑定状态,也就是它的成员变量bound_to_instance_的值等于true。在这种情况下,PPB_Graphics3D_Impl类的成员函数DoSwapBuffers首先会通过HostGlobals类的静态成员函数Get获得当前Render进程中的一个HostGlobals单例对象。有了这个HostGlobals单例对象之后,就可以调用它的成员函数GetInstance获得与当前正在处理的PPB_Graphics3D_Impl对象进行绑定的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。
有了上述PepperPluginInstanceImpl对象之后,PPB_Graphics3D_Impl类的成员函数DoSwapBuffers就可以调用它的成员函数CommitBackingTexture更新它在网页上的视图,如下所示:
void PepperPluginInstanceImpl::CommitBackingTexture() { ...... gpu::Mailbox mailbox; uint32 sync_point = 0; bound_graphics_3d_->GetBackingMailbox(&mailbox, &sync_point); ...... texture_layer_->SetTextureMailboxWithoutReleaseCallback( cc::TextureMailbox(mailbox, GL_TEXTURE_2D, sync_point)); texture_layer_->SetNeedsDisplay(); }这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc。
从前面的分析可以知道,PepperPluginInstanceImpl类的成员变量bound_graphics_3d_指向的是一个PPB_Graphics3D_Impl对象。这个PPB_Graphics3D_Impl对象描述的是一个OpenGL上下文。这个OpenGL上下文就是当前正在处理的PepperPluginInstanceImpl对象绑定的OpenGL上下文。
PepperPluginInstanceImpl类的另外一个成员变量texture_layer_指向的是一个cc::TextureLayer对象。前面提到,网页中的每一个<embed>标签在网页的CC Layer Tree中都对应有一个Texture Layer。这个Texture Layer就是通过上述cc::TextureLayer对象描述的。
有了上述PPB_Graphics3D_Impl对象之后,PepperPluginInstanceImpl类的成员函数CommitBackingTexture就可以调用它的成员函数GetBackingMailbox获得已经被交换到后端的缓冲区。这个缓冲区实际上是一个纹理缓冲区。这个纹理缓冲区将会作为<embed>标签的内容,因此PepperPluginInstanceImpl类的成员函数CommitBackingTexture就会将它设置上述cc::TextureLayer对象所要绘制的内容。这是通过调用该cc::TextureLayer对象的成员函数SetTextureMailboxWithoutReleaseCallback实现的。
最后,PepperPluginInstanceImpl类的成员函数CommitBackingTexture调用上述cc::TextureLayer对象的成员函数SetNeedsDisplay将自己标记为需要更新。这样在下一个VSync信号到来的时候,前面设置为它的内容的纹理缓冲区就会被渲染和合成在网页的UI上。这个渲染和合成过程可以参考前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章。
这样,我们就分析完成了Plugin执行3D渲染的过程,也就是Plugin为<embed>标签绘制3D视图的过程,这是通过调用Chromium提供的PPB_OPENGLES2_INTERFACE接口完成的。从这个过程我们就可以看到Plugin和Chromium的交互过程,也就是它们可以互相调用对方提供的接口,从而使得Plugin可以像我们在前面Chromium扩展(Extension)机制简要介绍和学习计划这个系列的文章提到的Extension一样,增强网页的功能。
至此,我们也分析完成了Chromium的Plugin机制。重新学习可以参考前面Chromium插件(Plugin)机制简要介绍和学习计划一文。更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。