在Chromium中,Render端和WebGL端绘制出来的UI最终是通过Browser端显示在屏幕上的。换句话说,就是Browser端负责合成Render端和WebGL端的UI。这涉及到不同OpenGL上下文之间的资源传递和同步问题。其中,资源传递问题通过Mailbox机制解决,同步问题通过Sync Point机制解决。本文接下来就分析Browser端合成Render端和WebGL端UI的过程。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!
Render端负责绘制的是网页UI。在Chromium中,网页UI被抽象为一棵Layer Tree,每一个Layer都用一个Layer对象描述,如图1最左边的图所示:
图1 Layer Tree、Pending Layer Tree和Active Layer Tree的关系
WebGL端负责绘制的是网页中的canvas标签。在Chromium中,网页中的canvas标签被抽象为一个TextureLayer对象,作为一个Layer出现在用来描述网页UI的一棵Layer Tree中。
Render端在内部维护了三棵Tree。除了上述的Layer Tree外,另外两棵Tree分别是Pending Layer Tree和Active Layer Tree。在Pending Layer Tree和Active Layer Tree中,每一个Layer都用一个LayerImpl对象描述。相应地,网页中的canvas标签在Pending Layer Tree和Active Layer Tree中被抽象为一个TextureLayerImpl对象。
Layer Tree由Render端的Render线程维护,而Pending Layer Tree和Active Layer Tree由Render端的Compositor线程维护。Render线程通过解析网页内容得到Layer Tree。Pending Layer Tree相当于是Layer Tree的一个副本。每当Layer Tree发生变化时,Compositor线程都会将其同步(Synchronize)到Pending Layer Tree中去。Pending Layer Tree经过光栅化(Rasterize)处理后,就变成Active Layer Tree。Active Layer Tree代表的是一个可以被Browser端合成的UI。
Layer Tree和Pending Layer Tree的存在使得Render线程和Compositor线程可以并发绘制网页的UI,如图2所示:
图2 网页UI并发绘制示意图
这相当于是网页UI的某一帧绘制分为两部分。前半部分由Render线程处理,后半部分由Compositor线程处理。其中,第N+1帧的前半部分和第N帧的后半部分可以并发处理。这一点类似于Android 5.0应用程序UI硬件加速渲染机制,具体可以参考Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章。其中,这里的Render线程和Compositor线程就相当于Android 5.0应用程序进程中的Main线程和Render线程。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,Render端执行GPU命令绘制网页UI时,只不过是将要执行的GPU写入到一个GPU命令缓冲区中,然后由GPU进程从这个GPU命令缓冲区读出GPU命令,并且调用相应的OpenGL函数执行它们。这个过程实际上是增加了网页渲染过程中的并行度,也就是Render端在GPU命令缓冲区写入GPU命令的同时,GPU进程从GPU命令缓冲区读出GPU命令进行处理。这样可以很好地利用设备的多核特性。这一点也是Chromium的硬件加速渲染与Android的硬件加速渲染的重要区别之一。
Pending Layer Tree和Active Layer Tree的存在使用Browser端在任何时刻都有网页UI可以合成。Active Layer Tree描述网页内容发生变化前的一帧UI。对Pending Layer Tree进行光栅化是一个漫长的过程。如果没有Active Layer Tree的存在,那么就会导致在Pending Layer Tree光栅化期间,Browser端没有网页UI可以显示在屏幕上。
早期,Render端先通过一个称为GL Renderer的渲染器将Active Layer Tree绘制在一个纹理中,然后再将绘制好的纹理传递给Browser端合成,如图3所示:
图3 GL Renderer
Active Layer Tree中的每一个Layer都对应有一个Render Pass,这些Render Pass组成一个Render Pass List。其中,每一个Render Pass又包含有一个Quad List。Quad List由一系列的Draw Quad组成。一个Draw Quad描述的是一个纹理绘制命令,这个纹理绘制命令指定了纹理对象和纹理坐标等信息。
前面提到,WebGL端负责绘制网页中的canvas标签。这个canvas标签的内容被绘制在一个纹理中。这个纹理是在WebGL端OpenGL上下文中生成的,然后通过Mailbox传递给Render端。
前面提到,网页中的canvas标签被抽象为一个TextureLayer对象。这个TextureLayer对象在Render Pass List中对应有一个Render Pass。这个Render Pass的Quad List包含有一个Texture Draw Quad。这个Texture Draw Quad指定的纹理对象描述的就是canvas标签的UI。换句话说,就是WebGL端将canvas标签的UI绘制在一个纹理上。注意,这个纹理对象是在WebGL端OpenGL上下文中生成的,Render端OpenGL上下文不能直接访问它。为了使得Render端可以访问这个纹理对象,WebGL端通过Mailbox将这个纹理对象传递给Render端,这样Render端就可以访问它了。
Render端最后通过GL Renderer将Render Pass List的所有Render Pass,包括canvas标签对应的Render Pass,都绘制在一个纹理中。这个纹理接下来又会通过Mailbox传递给Browser端。Browser端像Render端一样,也是将自己要绘制的UI(包括了从Render端传递过来的纹理对象)抽象成Layer Tree、Pending Layer Tree和Active Layer Tree三棵Tree。Browser端的Active Layer Tree也是对应有一个Render Pass List。这个Render Pass List最终也是通过一个GL Renderer进行绘制。不过,这个Render Pass List将会被绘制在屏幕中,而不是被绘制在一个纹理中。
从前面的分析就可以知道,通过GL Renderer,Render端首先将自己的Render Pass List绘制在一个纹理中。这个纹理传递给Broswer端后,再次被绘制在屏幕中。这意味着Render端的UI被绘制了两次。第一次在Render端OpenGL上下文中绘制,第二次是在Browser端OpenGL上下文中绘制。实际上,我们可以直接将Render端的Render Pass List传递给Browser端绘制。这样就可以减少一次绘制,从而提高效率。
将Render端的Render Pass List传递给Browser端是通过一个称为Delegated Renderer的渲染器实现的,如图4所示:
图4 Delegated Renderer
Render端的Render Pass List经过Delegated Renderer处理后,形成一个Compositor Frame。这个Compositor Frame传递给Browser端之后,就会形成Browser端的Active Layer Tree中的一个Layer,从而被绘制在屏幕中。Render端的Render Pass List包含的纹理对象,包括WebGL端生成的纹理对象,都是通过Mailbox传递给Browser端访问的。
在上面的描述过程中,我们提到了Layer Tree、Pending Layer Tree、Active Layer Tree、Render Thread、Compositor Thread、Render Pass、Draw Quad、GL Renderer、Delegated Renderer和Compositor Frame等概念,它们都是属于Chromium中的Compositor模块的概念。Chromium中的Compositor模块简称为CC模块,它负责合成Render端和Browser的UI,在接下来的分析过程中,我们只是简单涉及到它的内容。在以后分析网页渲染的文章中,我们会再详细分析CC模块的实现原理。
在上面的描述过程中,我们一再提到了Chromium的Mailbox机制,它在Browser端合成Render端和WebGL端UI的过程中起到传递资源的作用,使得一个OpenGL上下文可以访问在另外一个OpenGL上下文中创建的资源。因此,接下来我们就首先分析Chromium的Mailbox机制。
Mailbox的定义很简单,就是一个名称,如下所示:
struct GPU_EXPORT Mailbox {
Mailbox();
bool IsZero() const;
void SetZero();
void SetName(const int8_t* name);
// Generate a unique unguessable mailbox name.
static Mailbox Generate();
......
int8_t name[GL_MAILBOX_SIZE_CHROMIUM];
......
};
这个结构体定义在文件external/chromium_org/gpu/command_buffer/common/mailbox.h中。
从这里可以看到,Mailbox的名称使用一个int8_t数组name描述。一个有效的Mailbox的名称是非0的,也就是数组name不是全0的。
在创建一个Mailbox的时候,它的名称默认被设置为0,如下所示:
Mailbox::Mailbox() {
memset(name, 0, sizeof(name));
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/common/mailbox.cc中。
以后我们可以通过Mailbox结构体的成员函数SetName设置一个Mailbox的名称,如下所示:
void Mailbox::SetName(const int8* n) {
DCHECK(IsZero() || !memcmp(name, n, sizeof(name)));
memcpy(name, n, sizeof(name));
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/common/mailbox.cc中。
还可以通过Mailbox结构体的成员函数SetZero将一个Mailbox的名称设置为0,如下所示:
void Mailbox::SetZero() {
memset(name, 0, sizeof(name));
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/common/mailbox.cc中。
Mailbox结构体还提供了成员函数IsZero判断一个Mailbox的名称是否为0,如下所示:
bool Mailbox::IsZero() const {
for (size_t i = 0; i < arraysize(name); ++i) {
if (name[i])
return false;
}
return true;
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/common/mailbox.cc中。
我们可以通过Mailbox类静态成员函数Generate生成一个名称不为0的Mailbox,如下所示:
Mailbox Mailbox::Generate() {
Mailbox result;
// Generates cryptographically-secure bytes.
base::RandBytes(result.name, sizeof(result.name));
......
return result;
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/common/mailbox.cc中。
一般来说,我们不直接调用Mailbox类静态成员函数Generate生成一个Mailbox,而是通过OpenGL接口类GLES2Implementation的成员函数GenMailboxCHROMIUM生成,如下所示:
void GLES2Implementation::GenMailboxCHROMIUM(
GLbyte* mailbox) {
......
gpu::Mailbox result = gpu::Mailbox::Generate();
memcpy(mailbox, result.name, sizeof(result.name));
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
生成的Mailbox通过输出参数mailbox返回给调用者。调用者获得生成的Mailbox之后,继续调用GLES2Implementation的成员函数ProduceTextureCHROMIUM将该Mailbox与一个纹理对象进行关联,如下所示:
void GLES2Implementation::ProduceTextureCHROMIUM(GLenum target,
const GLbyte* data) {
......
helper_->ProduceTextureCHROMIUMImmediate(target, data);
CheckGLError();
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
参数data描述的是一个Mailbox,另外一个参数target描述的是要关联的纹理对象。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GLES2Implementation类的成员变量helper_指向的是一个GLES2CmdHelper对象,这里调用它的成员函数ProduceTextureCHROMIUMImmediate向GPU命令缓冲区写入一个gles2::cmds::ProduceTextureCHROMIUMImmediate命令,以便传递给GPU进程处理。
GPU进程将gles2::cmds::ProduceTextureCHROMIUMImmediate命令分发给GLES2DecoderImpl类的成员函数DoProduceTextureCHROMIUM处理,处理过程如下所示:
void GLES2DecoderImpl::DoProduceTextureCHROMIUM(GLenum target,
const GLbyte* data) {
......
TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget(
&state_, target);
ProduceTextureRef("glProduceTextureCHROMIUM", texture_ref, target, data);
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数DoProduceTextureCHROMIUM首先调用成员函数texture_manager获得一个TextureManager对象。这个TextureManager对象负责管理当前正在使用的OpenGL上下文所在的共享组的纹理对象。有了这个TextureManager对象之后,就可以调用它的成员函数GetTextureInfoForTarget获得参数target描述的纹理引用对象。有了这个纹理引用对象之后,GLES2DecoderImpl类的成员函数DoProduceTextureCHROMIUM接下来调用另外一个成员函数ProduceTextureRef执行下一步操作。
GLES2DecoderImpl类的成员函数ProduceTextureRef的实现如下所示:
void GLES2DecoderImpl::ProduceTextureRef(std::string func_name,
TextureRef* texture_ref, GLenum target, const GLbyte* data) {
const Mailbox& mailbox = *reinterpret_cast(data);
.......
Texture* produced = texture_manager()->Produce(texture_ref);
......
group_->mailbox_manager()->ProduceTexture(target, mailbox, produced);
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数ProduceTextureRef首先将参数data强制转换成一个Mailbox结构体,接着又调用前面描述的TextureManager对象的成员函数Produce获得参数texture_ref描述的纹理引用对象所引用的纹理对象,如下所示:
Texture* TextureManager::Produce(TextureRef* ref) {
DCHECK(ref);
return ref->texture();
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/texture_manager.cc中。
TextureManager类的成员函数Produce调用参数ref描述的TextureRef对象的成员函数texture即可获得它所引用的纹理对象。这个纹理对象使用一个Texture对象描述。
回到GLES2DecoderImpl类的成员函数ProduceTextureRef中,它接下来调用成员变量group_指向的一个ContextGroup对象的成员函数mailbox_manager获得一个MailboxManager对象。有了这个MailboxManager对象之后,就调用它的成员函数ProduceTexture执行下一步操作。
GLES2DecoderImpl类的成员变量group_指向的ContextGroup对象描述的是一个资源共享组,调用这个ContextGroup对象的成员函数mailbox_manager获得的MailboxManager对象用来管理当前正在使用的OpenGL上下文所属的OpenGL上下文共享组的那些被Mailbox引用的纹理对象。这意味着在同一个OpenGL上下文共享组的不同OpenGL上下文,可以直接访问与OpenGL上下文共享组关联的MailboxManager对象所管理的纹理对象,这是因为在同一个OpenGL上下文共享组的不同OpenGL上下文本来就是可以相互访问各自创建的纹理对象的。这一点我们后面可以看到。
上面我们提到了资源共享组和OpenGL上下文共享组,它们的区别可以参考前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文。现在我们继续分析MailboxManager类的成员函数ProduceTexture的实现,如下所示:
void MailboxManager::ProduceTexture(unsigned target,
const Mailbox& mailbox,
Texture* texture) {
TargetName target_name(target, mailbox);
......
InsertTexture(target_name, texture);
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/mailbox_manager.cc中。
参数texture指向的Texture对象实际上就是参数target描述的纹理对象。MailboxManager类的成员函数ProduceTexture所要做的事情就是将这个纹理对象与参数mailbox描述的Mailbox关联起来,也就是以参数target和参数mailbox组成的一个TargetName对象为键值,把参数texture描述的纹理对象保存在MailboxManager类的成员变量mailbox_to_textures_描述的一个std::map中。这是通过调用MailboxManager类的成员函数InsertTexture实现的,如下所示:
void MailboxManager::InsertTexture(TargetName target_name, Texture* texture) {
texture->SetMailboxManager(this);
TextureToMailboxMap::iterator texture_it =
textures_to_mailboxes_.insert(std::make_pair(texture, target_name));
mailbox_to_textures_.insert(std::make_pair(target_name, texture_it));
DCHECK_EQ(mailbox_to_textures_.size(), textures_to_mailboxes_.size());
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/mailbox_manager.cc中。
MailboxManager类的成员函数InsertTexture不仅以参数target_name描述的TargetName对象为键值,将参数texture描述的纹理对象保存在MailboxManager类的成员变量mailbox_to_textures_描述的一个std::map中,还会反过来以参数texture描述的纹理对象为键值,将参数target_name描述的TargetName对象保存在MailboxManager类的另外一个成员变量textures_to_mailboxes_描述的一个std::multimap中。这意味着给出一个Mailbox,可以通过MailboxManager类的成员变量mailbox_to_textures_找到对应的纹理对象;反过来,给出一个纹理对象,也可以通过MailboxManager类的成员变量textures_to_mailboxes_找到对应的Mailbox。注意,一个Mailbox只可以引用一个纹理对象,但是一个纹理对象可以被多个Mailbox引用。
将一个Mailbox关联上在某一个OpenGL上下文中创建的一个纹理对象之后,就可以将这个Mailbox传递给另外一个OpenGL上下文,使得该Mailbox关联的纹理对象可以从一个OpenGL上下文传递给另外一个OpenGL上下文访问。另外一个OpenGL上下文是通过调用OpenGL接口类GLES2Implementation的成员函数ConsumeTextureCHROMIUM访问这个纹理对象的。
GLES2Implementation类的成员函数ConsumeTextureCHROMIUM的实现如下所示:
void GLES2Implementation::ConsumeTextureCHROMIUM(GLenum target,
const GLbyte* data) {
......
helper_->ConsumeTextureCHROMIUMImmediate(target, data);
CheckGLError();
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数ConsumeTextureCHROMIUM调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数ConsumeTextureCHROMIUMImmediate向GPU命令缓冲区写入一个gles2::cmds::ConsumeTextureCHROMIUMImmediate命令,以便传递给GPU进程处理。
GPU进程将gles2::cmds::ConsumeTextureCHROMIUMImmediate命令分发给GLES2DecoderImpl类的成员函数DoConsumeTextureCHROMIUM处理,处理过程如下所示:
void GLES2DecoderImpl::DoConsumeTextureCHROMIUM(GLenum target,
const GLbyte* data) {
......
const Mailbox& mailbox = *reinterpret_cast(data);
......
scoped_refptr texture_ref =
texture_manager()->GetTextureInfoForTargetUnlessDefault(&state_, target);
......
GLuint client_id = texture_ref->client_id();
......
Texture* texture = group_->mailbox_manager()->ConsumeTexture(target, mailbox);
......
DeleteTexturesHelper(1, &client_id);
texture_ref = texture_manager()->Consume(client_id, texture);
glBindTexture(target, texture_ref->service_id());
......
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数DoConsumeTextureCHROMIUM首先将参数data强制转换成一个Mailbox结构体。这个Mailbox结构体关联有一个纹理对象,GLES2DecoderImpl类的成员函数DoConsumeTextureCHROMIUM所要做的事情就是将这个纹理对象与参数target描述的纹理目标在当前正在使用的OpenGL上下文中关联起来。
注意,参数target描述的纹理目标在当前正在使用的OpenGL上下文中原来也是关联有一个纹理对象的。GLES2DecoderImpl类的成员函数DoConsumeTextureCHROMIUM需要先解除参数target描述的纹理目标关联的旧纹理对象,才能给它关联新纹理对象,也就是参数data描述的Mailbox引用的纹理对象。
前面分析GLES2DecoderImpl类的成员函数DoProduceTextureCHROMIUM时提到,调用GLES2DecoderImpl类的成员函数texture_manager可以获得一个TextureManager对象。有了这个TextureManager对象之后,就可以调用它的成员函数GetTextureInfoForTargetUnlessDefault获得一个纹理引用对象。这个纹理引用对象引用的纹理对象就是参数target描述的纹理目标关联的旧纹理对象。有了这个纹理引用对象之后,可以通过调用它的成员函数client_id获得一个Client ID。有了这个Client ID之后,就可以调用GLES2DecoderImpl类的成员函数DeleteTexturesHelper解除参数target描述的纹理目标关联的旧纹理对象。
解除参数target描述的纹理目标关联的旧纹理对象之后,就要给它关联参数data描述的Mailbox引用的纹理对象。因此,这时候就需要获得参数data描述的Mailbox引用的纹理对象。前面分析GLES2DecoderImpl类的成员函数ProduceTextureRef时提到,GLES2DecoderImpl类的成员变量group_指向的是一个ContextGroup对象,调用这个ContextGroup对象的成员函数mailbox_manager可以获得一个MailboxManager对象。这个MailboxManager对象负责管理当前正在使用的OpenGL上下文所属的OpenGL上下文共享组的被Mailbox引用了的所有纹理对象。调用这个MailboxManager对象的成员函数ConsumeTexture即可获得参数data描述的Mailbox引用的纹理对象,如下所示:
Texture* MailboxManager::ConsumeTexture(unsigned target,
const Mailbox& mailbox) {
TargetName target_name(target, mailbox);
MailboxToTextureMap::iterator it =
mailbox_to_textures_.find(target_name);
if (it != mailbox_to_textures_.end())
return it->second->first;
if (sync_) {
// See if it's visible in another mailbox manager, and if so make it visible
// here too.
Texture* texture = sync_->CreateTextureFromMailbox(target, mailbox);
if (texture) {
InsertTexture(target_name, texture);
DCHECK_EQ(0U, texture->refs_.size());
}
return texture;
}
return NULL;
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/mailbox_manager.cc中。
前面分析MailboxManager类的成员函数ProduceTexture时提到,给出一个Mailbox,可以在MailboxManager类的成员变量mailbox_to_textures_描述的一个std::map中找到找到其引用的纹理对象。但是有可能参数mailbox描述的Mailbox引用的纹理对象,不是被当前正在处理的MailboxManager对象管理的。这样就会造成不能在当前正在处理的MailboxManager对象的成员变量mailbox_to_textures_描述的std::map中找到参数mailbox描述的Mailbox所引用的纹理对象。这个问题在参数mailbox描述的Mailbox引用的纹理对象所属的OpenGL上下文与当前正在使用的OpenGL上下文不是在同一个OpenGL上下文共享组的情况下就会出现。
上述问题的解决办法似乎很简单,只要我们找到负责管理参数mailbox描述的Mailbox引用的纹理对象的MailboxManager对象,就可以得到我们想要的纹理对象。但是由于这个纹理对象所属的OpenGL上下文与当前正在使用的OpenGL上下文不是在同一个OpenGL上下文共享组,因此即使我们得到了想要的纹理对象,也是无法使用它的。这时候我们需要设法使得当前正在使用的OpenGL上下文可以访问参数mailbox描述的Mailbox引用的纹理对象。这个问题其实可以用另外一种方式表达,就是如何让一个OpenGL上下文访问在另外一个不是在同一个OpenGL上下文共享组中的OpenGL上下文创建的纹理对象。
回忆前面Chromium硬件加速渲染的GPU数据上传机制分析一文,我们提到,可以通过EGL中的EGLImageKHR对象,解决两个不是在同一个OpenGL上下文共享组中的OpenGL上下文共享纹理的问题。假设纹理对象T是OpenGL上下文C1中创建的,现在我们要让OpenGL上下文C2可以访问纹理对象T。基于EGLImageKHR对象,解决过程是这样的:
1. 在OpenGL上下文C1中调用EGL函数eglCreateImageKHR根据纹理T创建一个EGLImageKHR对象。
2. 将EGLImageKHR对象传递给OpenGL上下文C2。
3. 在OpenGL上下文C2中调用OpenGL函数glEGLImageTargetTexture2DOES根据前面得到的EGLImageKHR对象创建一个纹理对象T'。
纹理对象T和纹理对象T'使用的是相同的纹理数据,于是就实现了在OpenGL上下文C2中访问在在OpenGL上下文C1中创建的纹理对象。
MailboxManager类的成员变量sync_描述的是一个MailboxSynchronizer对象。这个MailboxSynchronizer对象在GPU进程中是一个单例对象,调用它的成员函数CreateTextureFromMailbox就可以基于EGLImageKHR对象在当前正在使用的OpenGL上下文中创建一个纹理对象,这个纹理对象与参数mailbox描述的Mailbox引用的纹理对象使用的是相同的纹理数据,于是这个新创建的纹理对象就相当于是参数mailbox描述的Mailbox引用的纹理对象了。
现在我们需要解决的另外一个问题是,MailboxManager类的成员变量sync_描述的MailboxSynchronizer对象是如何得到参数mailbox描述的Mailbox引用的纹理对象的,因为只有得到了这个纹理对象之后,才可以基于EGLImageKHR对象在当前正在使用的OpenGL上下文中创建新的纹理对象。
在前面Chromium硬件加速渲染的OpenGL上下文调度过程分析一文分析Chromium的Sync Point时提到,当一个Sync Point被Retired时,也就是当GpuCommandBufferStub类的成员函数OnRetireSyncPoint被调用时,与当前正在使用的OpenGL上下文关联的一个MailboxManager对象的成员函数PushTextureUpdates会被调用,如下所示:
void GpuCommandBufferStub::OnRetireSyncPoint(uint32 sync_point) {
......
if (context_group_->mailbox_manager()->UsesSync() && MakeCurrent())
context_group_->mailbox_manager()->PushTextureUpdates();
......
}
这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
MailboxManager类的成员函数PushTextureUpdates的实现如下所示:
void MailboxManager::PushTextureUpdates() {
if (sync_)
sync_->PushTextureUpdates(this);
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/mailbox_manager.cc中。
MailboxManager类的成员函数PushTextureUpdates所做的事情就是将当前正在处理的MailboxManager对象管理的纹理对象同步到成员变量sync_描述的GPU进程中的MailboxSynchronizer单例对象中去。这样,GPU进程中的MailboxSynchronizer单例对象就可以找到一个任意的Mailbox引用的纹理对象了。
如果我们再回忆前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文,会发现Chromium中的所有OpenGL上下文都是在同一个OpenGL上下文共享组的。这意味着不需要通过上述的MailboxSynchronizer单例对象来解决纹理共享问题。也就是Mailbox引用的纹理对象,可以当前正在使用的OpenGL上下文直接访问。
回到前面分析的GLES2DecoderImpl类的成员函数DoConsumeTextureCHROMIUM中,它调用MailboxManager类的成员函数ConsumeTexture获得了参数data描述的Mailbox引用的纹理对象之后,就再次调用成员函数texture_manager获得一个TextureManager对象,并且调用这个TextureManager对象的成员函数Consume为前面获得的纹理对象创建一个纹理引用对象,如下所示:
TextureRef* TextureManager::Consume(
GLuint client_id,
Texture* texture) {
DCHECK(client_id);
scoped_refptr ref(new TextureRef(this, client_id, texture));
bool result = textures_.insert(std::make_pair(client_id, ref)).second;
DCHECK(result);
return ref.get();
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/texture_manager.cc中。
从这里可以看到,创建出来的纹理引用对象会被保存在TextureManager类的成员变量textures_描述的一个Hash Map中,其中键值为参数client_id描述的一个Client ID。
再回到前面分析的GLES2DecoderImpl类的成员函数DoConsumeTextureCHROMIUM中,现在万事已俱备,只需要调用OpenGL函数glBindTexture就可以将参数target描述的纹理目标与参数data描述的Mailbox引用的纹理对象关联起来,以实现在当前正在使用的OpenGL上下文中访问参数data描述的Mailbox引用的纹理对象的目的。
这样,我们就分析完成了Chromium的Mailbox机制。总结来说,Mailbox就只是一个名称,它用来将一个纹理对象从一个OpenGL上下文C1传递到另一个OpenGL上下文C2中,并且保证这个纹理对象可以被另外一个OpenGL上下文C2访问。Mailbox的使用步骤如下所示:
1. 在OpenGL上下文C1中调用GLES2Implementation类的成员函数GenMailboxCHROMIUM生成一个Mailbox。
2. 在OpenGL上下文C1中调用GLES2Implementation类的成员函数ProduceTextureCHROMIUM给前面生成的Mailbox关联一个纹理对象,这个纹理对象就是要进行传递的纹理对象。
3. 将前面引用了纹理对象的Mailbox传递给OpenGL上下文C2。
4. 在OpenGL上下文C2中调用GLES2Implementation类的成员函数ConsumeTextureCHROMIUM使用前面获得的Mailbox引用的纹理对象。
关于Mailbox,我们还有一点没有分析,就是它需要结合Sync Point来使用,后面我们分析WebGL端将代表了canvas标签UI的纹理对象传递给Browser端合成的过程时,就会看到这一点。接下来我们先分析WebGL端将canvas标签UI绘制在一个纹理的过程。
前面提到,网页中的每一个canvas标签都被抽象为一个TextureLayer对象,这个TextureLayer对象作为Render端的Layer Tree中的一个Layer。Render端在更新网页UI时,会调用Layer Tree中的每一个Layer的成员函数Update,以便让每一个Layer都更新自己负责的那部分UI。这意味着当网页包含有canvas标签时,TextureLayer类的成员函数Update就会被调用。TextureLayer类的成员函数Update在调用的过程中,就会将canvas标签UI的绘制在一个纹理中。
接下来我们首先分析Render端为网页中的canvas标签创建TextureLayer对象的过程。在前面Chromium的GPU进程启动过程分析一文中提到 ,WebKit在解析网页时碰到canvas标签时,就会为其创建一个HTMLCanvasElement对象。在JavaScript中调用这个HTMLCanvasElement对象的成员函数getContext可以获得WebGL接口。这个WebGL接口是通过调用WebGLRenderingContext类的静态成员函数create创建的,如下所示:
CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
{
......
// Accept the the provisional "experimental-webgl" or official "webgl" context ID.
ContextType contextType;
bool is3dContext = true;
if (type == "experimental-webgl")
contextType = ContextExperimentalWebgl;
else if (type == "webgl")
contextType = ContextWebgl;
else
is3dContext = false;
if (is3dContext) {
......
if (!m_context) {
......
m_context = WebGLRenderingContext::create(this, static_cast(attrs));
......
}
return m_context.get();
}
return 0;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLCanvasElement.cpp中。
WebGLRenderingContext类的静态成员函数create创建的是一个WebGLRenderingContext对象。
WebGLRenderingContext类继承了WebGLRenderingContextBase类。WebGLRenderingContextBase类有一个成员函数platformLayer,用来返回一个WebLayer对象。这个WebLayer对象描述的是canvas标签在Render端的Layer Tree中对应的Layer。
WebGLRenderingContextBase类的成员函数platformLayer的实现如下所示:
blink::WebLayer* WebGLRenderingContextBase::platformLayer() const
{
return isContextLost() ? 0 : m_drawingBuffer->platformLayer();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/canvas/WebGLRenderingContextBase.cpp中。
WebGLRenderingContextBase类有一个成员变量m_drawingBuffer,它指向的是一个DrawingBuffer对象。这个DrawingBuffer对象描述的canvas标签的UI所绘制在的一个缓冲区。这个缓冲区实际上就是一个纹理。WebGLRenderingContextBase类的成员函数platformLayer调用这个DrawingBuffer对象的成员函数platformLayer获得一个WebLayer对象,如下所示:
blink::WebLayer* DrawingBuffer::platformLayer()
{
if (!m_layer) {
m_layer = adoptPtr(blink::Platform::current()->compositorSupport()->createExternalTextureLayer(this));
......
}
return m_layer->layer();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp。
DrawingBuffer类的成员函数platformLayer调用成员变量m_layer描述的一个blink::WebExternalTextureLayer对象的成员函数layer获得一个blink::WebLayer对象,并且将这个blink::WebLayer对象返回给调用者。
如果成员变量m_layer还没有指向一个blink::WebExternalTextureLayer对象,那么DrawingBuffer类的成员函数platformLayer会先创建这个blink::WebExternalTextureLayer对象。这个blink::WebExternalTextureLayer对象是由具体的实现平台负责创建的,也就是由WebKit的使用者负责创建的。在我们这个场景中,WebKit的使用者就是Chromium。Chromium通过提供了一个WebCompositorSupportImpl类,这个类实现了blink::WebCompositorSupport接口。通过调用WebCompositorSupportImpl类的成员函数createExternalTextureLayer就可以创建一个blink::WebExternalTextureLayer对象。
WebCompositorSupportImpl类的成员函数createExternalTextureLayer的实现如下所示:
WebExternalTextureLayer* WebCompositorSupportImpl::createExternalTextureLayer(
WebExternalTextureLayerClient* client) {
return new WebExternalTextureLayerImpl(client);
}
这个函数定义在文件external/chromium_org/content/renderer/compositor_bindings/web_compositor_support_impl.cc中。
从这里可以看到,WebCompositorSupportImpl类的成员函数createExternalTextureLayer返回的是一个WebExternalTextureLayerImpl对象。这个WebExternalTextureLayerImpl对象的创建过程如下所示:
WebExternalTextureLayerImpl::WebExternalTextureLayerImpl(
blink::WebExternalTextureLayerClient* client)
: client_(client) {
cc::TextureLayerClient* cc_client = client_ ? this : NULL;
scoped_refptr layer = TextureLayer::CreateForMailbox(cc_client);
layer->SetIsDrawable(true);
layer_.reset(new WebLayerImpl(layer));
}
这个函数定义在文件external/chromium_org/content/renderer/compositor_bindings/web_external_texture_layer_impl.cc中。
从前面的调用过程可以知道,参数client描述的是一个DrawingBuffer对象,这个DrawingBuffer对象保存在WebExternalTextureLayerImpl类的成员变量client_中。
WebExternalTextureLayerImpl类的构造函数接下来调用TextureLayer类的静态成员函数CreateForMailbox创建了一个TextureLayer对象。这个TextureLayer接下来又被封装在一个WebLayerImpl对象中。这个WebLayerImpl对象保存在WebExternalTextureLayerImpl类的成员变量layer_中。
由此可见,前面分析DrawingBuffer类的成员变量m_layer指向的实际上是一个WebExternalTextureLayerImpl对象。当调用这个WebExternalTextureLayerImpl对象的成员函数layer时,获得的是其成员变量layer_指向的一个WebLayerImpl对象,如下所示:
blink::WebLayer* WebExternalTextureLayerImpl::layer() {
return layer_.get();
}
这个函数定义在文件external/chromium_org/content/renderer/compositor_bindings/web_external_texture_layer_impl.cc中。
回到前面分析的WebExternalTextureLayerImpl类的构造函数中,接下来我们继续分析它调用TextureLayer类的静态成员函数CreateForMailbox创建TextureLayer对象的过程,如下所示:
scoped_refptr TextureLayer::CreateForMailbox(
TextureLayerClient* client) {
return scoped_refptr(new TextureLayer(client));
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer.cc中。
从前面的调用过程可以知道,参数client描述的是一个WebExternalTextureLayerImpl对象,TextureLayer类的静态成员函数CreateForMailbox以它为参数,创建了一个TextureLayer对象,并且将这个TextureLayer对象返回给调用者。
TextureLayer对象的创建过程,也就是TextureLayer类的构造函数的实现,如下所示:
TextureLayer::TextureLayer(TextureLayerClient* client)
: ......,
client_(client),
...... {
......
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer.cc中。
TextureLayer类的构造函数主要是将参数client描述的一个WebExternalTextureLayerImpl对象保存在成员变量client_中。
前面创建出来的TextureLayer对象就作为一个Layer出现在Render端的Layer Tree中。当Render端需要重绘网页UI时,就会调用这个TextureLayer对象的成员函数Update,询问其描述的canvas标签是否需要更新UI。
TextureLayer类的成员函数Update的实现如下所示:
bool TextureLayer::Update(ResourceUpdateQueue* queue,
const OcclusionTracker* occlusion) {
bool updated = Layer::Update(queue, occlusion);
if (client_) {
TextureMailbox mailbox;
scoped_ptr release_callback;
if (client_->PrepareTextureMailbox(
&mailbox,
&release_callback,
layer_tree_host()->UsingSharedMemoryResources())) {
// Already within a commit, no need to do another one immediately.
bool requires_commit = false;
bool allow_mailbox_reuse = false;
SetTextureMailboxInternal(mailbox,
release_callback.Pass(),
requires_commit,
allow_mailbox_reuse);
updated = true;
}
}
// SetTextureMailbox could be called externally and the same mailbox used for
// different textures. Such callers notify this layer that the texture has
// changed by calling SetNeedsDisplay, so check for that here.
return updated || !update_rect_.IsEmpty();
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer.cc中。
TextureLayer类是从Layer类继承下来的,TextureLayer类的成员函数Update首先调用它的成员函数,询问其是否需要更新UI。
TextureLayer类的成员函数Update接下来调用成员变量client_描述的一个WebExternalTextureLayerImpl对象的成员函数PrepareTextureMailbox将它所描述的canvas标签的内容绘制在一个纹理中,并且将这个纹理设置给一个Mailbox。这个Mailbox封装在一个TextureMailbox对象。这个TextureMailbox对象最终通过调用TextureLayer类的成员函数SetTextureMailboxInternal保存在TextureLayer类的内部,如下所示:
void TextureLayer::SetTextureMailboxInternal(
const TextureMailbox& mailbox,
scoped_ptr release_callback,
bool requires_commit,
bool allow_mailbox_reuse) {
......
// If we never commited the mailbox, we need to release it here.
if (mailbox.IsValid()) {
holder_ref_ =
TextureMailboxHolder::Create(mailbox, release_callback.Pass());
}
......
needs_set_mailbox_ = true;
......
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer.cc中。
TextureLayer类的成员函数SetTextureMailboxInternal将参数mailbox描述的一个TextureMailbox对象封装在一个MainThreadReference对象中,并且将这个MainThreadReference对象保存在成员变量holder_ref_中,以及将成员变量needs_set_mailbox_的值设置为true,表示内部使用的Mailbox发生了变化。
上述MainThreadReference对象是通过调用TextureMailboxHolder类的静态成员函数Create创建的,它的实现如下所示:
scoped_ptr
TextureLayer::TextureMailboxHolder::Create(
const TextureMailbox& mailbox,
scoped_ptr release_callback) {
return scoped_ptr(new MainThreadReference(
new TextureMailboxHolder(mailbox, release_callback.Pass())));
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer.cc中。
TextureMailboxHolder类的静态成员函数Create首先创建一个TextureMailboxHolder对象,然后再以这个TextureMailboxHolder对象为参数,创建了一个MainThreadReference对象,最后将这个MainThreadReference对象返回给调用者。
TextureMailboxHolder对象的创建过程,也就是TextureMailboxHolder类的构造函数的实现,如下所示:
TextureLayer::TextureMailboxHolder::TextureMailboxHolder(
const TextureMailbox& mailbox,
scoped_ptr release_callback)
: ......,
mailbox_(mailbox),
......,
sync_point_(mailbox.sync_point()),
...... {}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer.cc中。
TextureMailboxHolder类的构造函数将参数mailbox描述的一个TextureMailbox对象保存在成员变量mailbox_中,并且调用这个TextureMailbox对象的成员函数sync_point获得一个Sync Point,保存在另外一个成员变量sync_point_中。这个Sync Point是前面调用WebExternalTextureLayerImpl类的成员函数PrepareTextureMailbox时创建的。
接下来,我们就继续分析WebExternalTextureLayerImpl类的成员函数PrepareTextureMailbox的实现,如下所示:
bool WebExternalTextureLayerImpl::PrepareTextureMailbox(
cc::TextureMailbox* mailbox,
scoped_ptr* release_callback,
bool use_shared_memory) {
blink::WebExternalTextureMailbox client_mailbox;
WebExternalBitmapImpl* bitmap = NULL;
if (use_shared_memory)
bitmap = AllocateBitmap();
if (!client_->prepareMailbox(&client_mailbox, bitmap)) {
......
}
gpu::Mailbox name;
name.SetName(client_mailbox.name);
if (bitmap) {
*mailbox = cc::TextureMailbox(bitmap->shared_memory(), bitmap->size());
} else {
*mailbox =
cc::TextureMailbox(name, GL_TEXTURE_2D, client_mailbox.syncPoint);
}
......
return true;
}
这个函数定义在文件external/chromium_org/content/renderer/compositor_bindings/web_external_texture_layer_impl.cc中。
当参数use_shared_memory的值等于true时,表示Render端要求WebGL端最终将canvas标签的内容绘制在一个共享缓冲区中。这个共享缓冲区是通过调用WebExternalTextureLayerImpl类的成员函数AllocateBitmap创建的。在这种情况下,WebGL端需要从GPU里面将已经绘制好的canvas标签UI读回到CPU来。我们知道,从GPU读取数据到CPU是很低效的。我们将不讨论这种情况。
当参数use_shared_memory的值等于true时,WebExternalTextureLayerImpl类的成员函数PrepareTextureMailbox将会调用成员变量client_描述的一个DrawingBuffer对象的成员函数prepareMailbox创建一个blink::WebExternalTextureMailbox对象。这个blink::WebExternalTextureMailbox对象的成员变量name和syncPoint描述的分别是一个Mailbox的名称和一个Sync Point。
最后,WebExternalTextureLayerImpl类的成员函数PrepareTextureMailbox根据上述blink::WebExternalTextureMailbox对象包含的Mailbox和Sync Point创建一个TextureMailbox对象。
TextureMailbox对象的创建过程,即TextureMailbox类的构造函数的实现,如下所示:
TextureMailbox::TextureMailbox(const gpu::Mailbox& mailbox,
uint32 target,
uint32 sync_point)
: mailbox_holder_(mailbox, target, sync_point),
...... {}
这个函数定义在文件external/chromium_org/cc/resources/texture_mailbox.cc中。
TextureMailbox类的构造函数将参数mailbox和sync_point描述的Mailbox和Sync Point封装在一个MailboxHolder对象中,并且将这个MailboxHolder对象保存在成员变量mailbox_holder_中。同时封装在上述MailboxHolder对象还包括参数target的值。从前面的调用过程可以知道,参数target的值为GL_TEXTURE_2D,这表示参数mailbox描述的Mailbox关联的纹理对象的类型为GL_TEXTURE_2D。
回到WebExternalTextureLayerImpl类的成员函数PrepareTextureMailbox中,接下来我们继续分析它调用成员变量client_描述的DrawingBuffer对象的成员函数prepareMailbox创建blink::WebExternalTextureMailbox对象的过程。
DrawingBuffer类的成员函数prepareMailbox创建blink::WebExternalTextureMailbox的实现如下所示:
bool DrawingBuffer::prepareMailbox(blink::WebExternalTextureMailbox* outMailbox, blink::WebExternalBitmap* bitmap)
{
......
// First try to recycle an old buffer.
RefPtr frontColorBufferMailbox = recycledMailbox();
// No buffer available to recycle, create a new one.
if (!frontColorBufferMailbox) {
TextureInfo newTexture;
newTexture.textureId = createColorTexture();
allocateTextureMemory(&newTexture, m_size);
// Bad things happened, abandon ship.
if (!newTexture.textureId)
return false;
frontColorBufferMailbox = createNewMailbox(newTexture);
}
if (m_preserveDrawingBuffer == Discard) {
swap(frontColorBufferMailbox->textureInfo, m_colorBuffer);
// It appears safe to overwrite the context's framebuffer binding in the Discard case since there will always be a
// WebGLRenderingContext::clearIfComposited() call made before the next draw call which restores the framebuffer binding.
// If this stops being true at some point, we should track the current framebuffer binding in the DrawingBuffer and restore
// it after attaching the new back buffer here.
m_context->bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
if (m_multisampleMode == ImplicitResolve)
m_context->framebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0, m_sampleCount);
else
m_context->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffer.textureId, 0);
} else {
m_context->copyTextureCHROMIUM(GL_TEXTURE_2D, m_colorBuffer.textureId, frontColorBufferMailbox->textureInfo.textureId, 0, GL_RGBA, GL_UNSIGNED_BYTE);
}
......
m_context->bindTexture(GL_TEXTURE_2D, frontColorBufferMailbox->textureInfo.textureId);
m_context->produceTextureCHROMIUM(GL_TEXTURE_2D, frontColorBufferMailbox->mailbox.name);
m_context->flush();
frontColorBufferMailbox->mailbox.syncPoint = m_context->insertSyncPoint();
......
*outMailbox = frontColorBufferMailbox->mailbox;
......
return true;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp中。
DrawingBuffer类在内部维护了一个MailboxInfo对象队列。每一个MailboxInfo对象都关联有一个Mailbox和一个纹理对象。每次DrawingBuffer类的成员函数prepareMailbox被调用时,就会从上述MailboxInfo队列中取出一个MailboxInfo对象,目的是为了能够快速获得一个Mailbox和一个纹理对象。这样就可以快速地将canvas标签的内容绘制在获得的纹理对象里面,并且将这个纹理对象设置到获得的Mailbox里面去,以便传递给Render端处理。更通俗地说,上述MailboxInfo队列就相当于是DrawingBuffer类内部使用的“双缓冲”或者“多缓冲”技术。
DrawingBuffer类的成员函数prepareMailbox是通过调用成员函数recycledMailbox从上述MailboxInfo对象队列获得一个空闲的MailboxInfo对象的,并且保存在本地变量frontColorBufferMailbox中。如果不能从MailboxInfo对象队列中获得一个空闲的MailboxInfo对象,那么就会调用成员函数createNewMailbox新创建一个。
创建新的MailboxInfo对象需要有一个新的纹理对象。这个新的纹理对象通过一个TextureInfo对象描述。其中,这个TextureInfo对象的成员变量textureId描述的是一个纹理ID。这个纹理ID是通过调用DrawingBuffer类的成员函数createColorTexture创建的。DrawingBuffer类的成员函数createColorTexture最后是通过调用OpenGL接口类GLES2Implementation的成员函数GenTextures创建的。有了纹理ID之后,接下来还需要调用DrawingBuffer类的成员函数allocateTextureMemory为新的纹理对象分配GPU内存。DrawingBuffer类的成员函数allocateTextureMemory最后是通过调用OpenGL接口类GLES2Implementation的成员函数TexImage2D为新的纹理对象分配GPU内存。
DrawingBuffer类是如何将canvas标签的内容绘制在纹理上的呢?DrawingBuffer类会创建一个FBO,作为WebGL端OpenGL上下文当前使用的Frame Buffer,并且通过调用OpenGL接口类GLES2Implementation的成员函数FramebufferTexture2DMultisampleEXT或者FramebufferTexture2D给这个FBO绑定一个纹理。这样canvas标签的内容最终就会绘制在被绑定的纹理中。这个被绑定的纹理就记录在DrawingBuffer类的成员变量m_colorBuffer指向的一个TextureInfo对象中。
理论上说,当DrawingBuffer类的成员函数prepareMailbox被调用时,只要将记录在成员变量m_colorBuffer描述的TextureInfo对象中的纹理对象绑定在本地变量frontColorBufferMailbox描述的Mailbox中,并且将这人Mailbox返回给调用者就可以了。但是这样做的话会产生一个问题,就是调用者(Render端)得到这个纹理之后,可能会对它进行修改,这样就会影响到WebGL端接下来对canvas标签的绘制。换句话说,就是Render端和WebGL端可能会同时修改这个纹理对象的内容。
为了解决这个问题,DrawingBuffer类的成员函数prepareMailbox并不是直接将绑定在FBO中的纹理对象返回给Render端,而是将绑定在FBO中的纹理对象的内容拷一份到另外一个纹理对象中去,然后将另外的那个纹理对象返回给Render端处理。这个拷贝操作是通过调用OpenGL接口类GLES2Implementation的成员函数CopyTextureCHROMIUM实现的。
OpenGL接口类GLES2Implementation的成员函数CopyTextureCHROMIUM是借助FBO实现纹理拷贝的。假设被拷贝的纹理对象是src,要拷贝到的纹理对象是dst,拷贝过程如下所示:
1. 创建一个FBO。
2. 绑定纹理对象dst到上述FBO中。
3. 将纹理对象src绘制到上述FBO中。
DrawingBuffer类的成员函数prepareMailbox提供了以上两种方式给调用者返回一个纹理对象,取决于成员变量m_preserveDrawingBuffer的值。当这个成员变量的值等于Discard时,表示一旦将绑定在FBO的纹理对象返回给调用者,DrawingBuffer类就不会再使用它。
因此,当成员变量m_preserveDrawingBuffer的值等于Discard时,DrawingBuffer类的成员函数prepareMailbox就会直接将绑定在FBO中的纹理对象返回给调用者,也就是将记录在成员变量m_colorBuffer描述的TextureInfo对象中的纹理对象返回给用者。将这个纹理对象返回给调用者之后,需要给DrawingBuffer类使用的FBO重新绑定一个纹理对象。这个重新绑定的纹理对象就是本地变量frontColorBufferMailbox描述的纹理对象。这个重新绑定的操作就是通过我们前面描述的OpenGL接口类GLES2Implementation的成员函数FramebufferTexture2DMultisampleEXT或者FramebufferTexture2D实现的。这意味着以后DrawingBuffer类会将canvas标签的内容绘制在这个重新绑定的纹理对象中。等到下一次DrawingBuffer类的成员函数prepareMailbox被调用时,这个重新绑定的纹理对象又会直接返回给调用者,然后再又重新绑定第三个纹理对象。这个过程重复进行着。
另一方面,当成员变量m_preserveDrawingBuffer的值等于Discard时,DrawingBuffer类的成员函数prepareMailbox通过调用OpenGL接口类GLES2Implementation的成员函数CopyTextureCHROMIUM将绑定在FBO中的纹理对象拷贝一份到本地变量frontColorBufferMailbox描述的纹理对象中去。在这种情况下,就不需要给DrawingBuffer类使用的FBO重新绑定纹理对象。
无论使用哪一种方式,最后记录在本地变量frontColorBufferMailbox中的纹理对象就是最终要返回给调用者的纹理对象。这个纹理对象需要设置到一个Mailbox去,才能返回给调用者使用。这个Mailbox也是记录在本地变量frontColorBufferMailbox中的。根据我们前面的分析,将一个纹理对象设置给一个Mailbox需要通过调用OpenGL接口类GLES2Implementation的成员函数ProduceTextureCHROMIUM实现。
DrawingBuffer类的成员函数prepareMailbox将设置了纹理对象的Mailbox返回给调用者之前,也就是将这个Mailbox保存在输出参数outMailbox之前,还需要做一件事情,就是在当前的OpenGL上下文,也就是WebGL端OpenGL上下文,插入一个Sync Point,表示调用者,即Render端OpenGL上下文,在使用这个Mailbox关联的纹理对象之前,要等待该Sync Point被Retired。
向OpenGL上下文插入Sync Point是通过调用OpenGL接口类GLES2Implementation的成员函数InsertSyncPointCHROMIUM实现,并且这个插入的Sync Point需要返回给调用者,以便调用者可以通过调用OpenGL接口类GLES2Implementation类的成员函数WaitSyncPointCHROMIUM等待它被Retired。
注意,上面我们多次提到了OpenGL接口类GLES2Implementation的成员函数,这些成员函数是通过DrawingBuffer类的成员变量m_context指向的一个blink::WebGraphicsContext3D对象进行间接调用者。例如,调用这个blink::WebGraphicsContext3D对象的成员函数copyTextureCHROMIUM最后就会导致OpenGL接口类GLES2Implementation的成员函数CopyTextureCHROMIUM被调用。
从上面的分析就可以知道,从DrawingBuffer类的成员函数prepareMailbox返回之后,WebGL端就向Render端返回了一个Mailbox,这个Mailbox关联的纹理对象包含了WebGL端所绘制的UI,也就是canvas标签的UI。Render端将得到Mailbox保存在了其Layer Tree中的一个TextureLayer对象中。
前面分析图1时提到,Render端在渲染网页之前,需要将Layer Tree的内容同步到Pending Layer Tree中去,这发生在LayerTreeHost类的成员函数FinishCommitOnImplThread中,如下所示:
void LayerTreeHost::FinishCommitOnImplThread(LayerTreeHostImpl* host_impl) {
......
LayerTreeImpl* sync_tree = host_impl->sync_tree();
......
if (needs_full_tree_sync_)
sync_tree->SetRootLayer(TreeSynchronizer::SynchronizeTrees(
root_layer(), sync_tree->DetachLayerTree(), sync_tree));
{
......
TreeSynchronizer::PushProperties(root_layer(), sync_tree->root_layer());
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
参数host_impl指向的是一个LayerTreeHostImpl对象。调用这个LayerTreeHostImpl对象的成员函数sync_tree获得的是一个LayerTreeImpl对象。这个LayerTreeImpl对象描述的就是Render端的Pending Layer Tree。
另一方面,调用LayerTreeHost类的成员函数root_layer获得的是一个Layer对象。这个Layer对象描述的是Render端的Layer Tree的Root Layer。有了描述Render端的Layer Tree的Root Layer的Layer对象和Pending Layer Tree的LayerTreeImpl对象,就可以将Layer Tree的内容同步到Pending Layer Tree中去了。
这个同步过程分两步进行。第一步是同步两棵Tree的结构,这是通过调用TreeSynchronizer类的静态成员函数SynchronizeTrees实现的,也就是针Layer Tree的每一个Layer对象,在Pending Layer Tree中创建一个LayerImpl对象。第二步是将Layer Tree的每一个Layer对象的属性同步Pending Layer Tree中的对应LayerImpl对象中去,这是通过调用TreeSynchronizer类的静态成员函数PushProperties实现的。
接下来我们只关注将Layer Tree中的TextureLayer对象的属性同步到Pending Layer Tree中的对应的TextureLayerImpl对象的过程,也就是TreeSynchronizer类的静态成员函数PushProperties的实现,以便继续了解WebGL端将代表了canvas标签UI的纹理对象传递给Render端的过程。
TreeSynchronizer类的静态成员函数PushProperties的实现如下所示:
void TreeSynchronizer::PushProperties(Layer* layer,
LayerImpl* layer_impl) {
size_t num_dependents_need_push_properties = 0;
PushPropertiesInternal(
layer, layer_impl, &num_dependents_need_push_properties);
......
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数PushProperties调用另外一个成员函数PushPropertiesInternal执行Layer属性同步工作,后者的实现如下所示:
template
void TreeSynchronizer::PushPropertiesInternal(
LayerType* layer,
LayerImpl* layer_impl,
size_t* num_dependents_need_push_properties_for_parent) {
......
bool push_layer = layer->needs_push_properties();
bool recurse_on_children_and_dependents =
layer->descendant_needs_push_properties();
if (push_layer)
layer->PushPropertiesTo(layer_impl);
size_t num_dependents_need_push_properties = 0;
if (recurse_on_children_and_dependents) {
......
for (size_t i = 0; i < layer->children().size(); ++i) {
PushPropertiesInternal(layer->child_at(i),
impl_children[i],
&num_dependents_need_push_properties);
}
......
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数PushPropertiesInternal是被递归调用的,Layer Tree中的每一个Layer对象的成员函数PushPropertiesTo都会被调用,并且会被传递一个参数。这个参数就是在Pending Layer Tree中对应的LayerImpl对象。这意味着如果Layer Tree中包含有一个TextureLayer对象,那么它的成员函数PushPropertiesTo就会被调用,并且会获得一个类型为TextureLayerImpl的对象。
TextureLayer类的成员函数PushPropertiesTo的实现如下所示:
void TextureLayer::PushPropertiesTo(LayerImpl* layer) {
......
TextureLayerImpl* texture_layer = static_cast(layer);
......
if (needs_set_mailbox_) {
TextureMailbox texture_mailbox;
scoped_ptr release_callback;
if (holder_ref_) {
TextureMailboxHolder* holder = holder_ref_->holder();
texture_mailbox = holder->mailbox();
release_callback = holder->GetCallbackForImplThread();
}
texture_layer->SetTextureMailbox(texture_mailbox, release_callback.Pass());
needs_set_mailbox_ = false;
}
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer.cc中。
参数layer指向的实际上是一个TextureLayerImpl对象,因此TextureLayer类的成员函数PushPropertiesTo将它强制转换为一个TextureLayerImpl对象。
前面分析TextureLayer类的成员函数SetTextureMailboxInternal时提到,当TextureLayer类内部使用的Mailbox发生变化时,它的成员变量needs_set_mailbox_的值就会被设置为true。在这种情况下,TextureLayer类的成员函数PushPropertiesTo就会调用成员变量holder_ref_描述的一个MainThreadReference对象的成员函数mailbox获得一个TextureMailbox对象,并且将这个TextureMailbox对象设置给参数layer描述的TextureLayerImpl对象,这是通过调用TextureLayerImpl类的成员函数SetTextureMailbox实现的。
TextureLayerImpl类的成员函数SetTextureMailbox的实现如下所示:
void TextureLayerImpl::SetTextureMailbox(
const TextureMailbox& mailbox,
scoped_ptr release_callback) {
......
texture_mailbox_ = mailbox;
......
own_mailbox_ = true;
valid_texture_copy_ = false;
......
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer_impl.cc中。
TextureLayerImpl类的成员函数SetTextureMailbox主要是将参数mailbox描述的一个TextureMailbox对象保存在成员变量texture_mailbox_中,并且分别将成员变量own_mailbox_和valid_texture_copy_的值设置为true和false。将成员变量own_mailbox_的值为true表示成员变量texture_mailbox_描述的Mailbox的所有者是当前正在处理的TextureLayerImpl对象。将成员变量valid_texture_copy_的值设置为false表示成员变量texture_mailbox_描述的Mailbox关联的是一个纹理对象,而不是一块共享缓冲区。
Render端的Layer Tree的内容同步到Pending Layer Tree之后,Pending Layer Tree中的LayerImpl对象描述的UI会被光栅化。我们假设是通过GPU进行光栅化的,那么光栅化的结果就是使得每一个LayerImpl对象描述的UI都是通过一系列的纹理来描述。经过光栅化之后,Pending Layer Tree就变成了Active Layer Tree。这个Active Layer Tree最终会被图2所示的Compositor Thread进行绘制。
Render端内部有一个调度器,它会在合适的时候调用ThreadProxy类的成员函数ScheduledActionDrawAndSwapIfPossible通知Compositor Thread对Active Layer Tree进行绘制。接下来我们就继续分析ThreadProxy类的成员函数ScheduledActionDrawAndSwapIfPossible的实现,以便了解Render端将自己的UI交给Browser端合成的过程。
ThreadProxy类的成员函数ScheduledActionDrawAndSwapIfPossible的实现如下所示:
DrawResult ThreadProxy::ScheduledActionDrawAndSwapIfPossible() {
......
bool forced_draw = false;
return DrawSwapInternal(forced_draw);
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类的成员函数ScheduledActionDrawAndSwapIfPossible调用另外一个成员函数DrawSwapInternal对Render端的Active Layer Tree进行绘制。
ThreadProxy类的成员函数DrawSwapInternal的实现如下所示:
DrawResult ThreadProxy::DrawSwapInternal(bool forced_draw) {
......
LayerTreeHostImpl::FrameData frame;
bool draw_frame = false;
if (impl().layer_tree_host_impl->CanDraw()) {
result = impl().layer_tree_host_impl->PrepareToDraw(&frame);
draw_frame = forced_draw || result == DRAW_SUCCESS;
}
......
if (draw_frame) {
impl().layer_tree_host_impl->DrawLayers(
&frame, impl().scheduler->LastBeginImplFrameTime());
result = DRAW_SUCCESS;
......
}
......
if (draw_frame) {
bool did_request_swap = impl().layer_tree_host_impl->SwapBuffers(frame);
......
}
......
return result;
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
调用ThreadProxy类的成员函数impl返回的是一个CompositorThreadOnly对象。这个CompositorThreadOnly对象的成员变量layer_tree_host_impl描述的是一个LayerTreeHostImpl对象。这个LayerTreeHostImpl对象负责管理Render端的Pending Layer Tree和Active Layer Tree。
ThreadProxy类的成员函数DrawSwapInternal首先是判断Render端的Active Layer Tree是否可绘制。如果可以绘制,那么就调用上述LayerTreeHostImpl对象的成员函数PrepareToDraw根据Active Layer Tree中的各个LayerImpl对象计算出Render Pass,形成一个Render Pass List,保存在本地变量frame描述的一个LayerTreeHostImpl::FrameData对象中。
ThreadProxy类的成员函数DrawSwapInternal接下来又分别调用上述LayerTreeHostImpl对象的成员函数DrawLayers和SwapBuffers,前一个调用为了绘制前面计算得到的Render Pass List,而后一个调用是为了通知Browser端合成Render端前面已经绘制好的UI。
接下来我们就分别分析LayerTreeHostImpl类的成员函数PrepareToDraw、DrawLayers和SwapBuffers的实现,以便了解Browser端合成Render端和WebGL端的UI的过程。
LayerTreeHostImpl类的成员函数PrepareToDraw的实现如下所示:
DrawResult LayerTreeHostImpl::PrepareToDraw(FrameData* frame) {
......
frame->render_surface_layer_list = &active_tree_->RenderSurfaceLayerList();
......
DrawResult draw_result = CalculateRenderPasses(frame);
......
return draw_result;
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
LayerTreeHostImpl类的成员变量active_tree_指向的是一个LayerTreeImpl对象。这个LayerTreeImpl描述的是就是Render端的Active Layer Tree。LayerTreeHostImpl类的成员函数PrepareToDraw首先通过调用这个LayerTreeImpl对象的成员函数RenderSurfaceLayerList获得Render端的Active Layer Tree中的LayerImpl对象列表,保存在参数frame描述的一个FrameData对象的成员变量render_surface_layer_list中。
LayerTreeHostImpl类的成员函数PrepareToDraw接下来调用另外一个成员函数CalculateRenderPasses根据前面获得的LayerImpl对象列表中的每一个LayerImpl对象计算出一个以后用来绘制的Render Pass List,如下所示:
DrawResult LayerTreeHostImpl::CalculateRenderPasses(
FrameData* frame) {
......
LayerIteratorType end =
LayerIteratorType::End(frame->render_surface_layer_list);
for (LayerIteratorType it =
LayerIteratorType::Begin(frame->render_surface_layer_list);
it != end;
++it) {
RenderPass::Id target_render_pass_id =
it.target_render_surface_layer()->render_surface()->RenderPassId();
RenderPass* target_render_pass =
frame->render_passes_by_id[target_render_pass_id];
......
AppendQuadsData append_quads_data(target_render_pass_id);
if (it.represents_target_render_surface()) {
if (it->HasCopyRequest()) {
have_copy_request = true;
it->TakeCopyRequestsAndTransformToTarget(
&target_render_pass->copy_requests);
}
} else if (it.represents_contributing_render_surface() &&
it->render_surface()->contributes_to_drawn_surface()) {
RenderPass::Id contributing_render_pass_id =
it->render_surface()->RenderPassId();
RenderPass* contributing_render_pass =
frame->render_passes_by_id[contributing_render_pass_id];
AppendQuadsForRenderSurfaceLayer(target_render_pass,
*it,
contributing_render_pass,
occlusion_tracker,
&append_quads_data);
} else if (it.represents_itself() &&
!it->visible_content_rect().IsEmpty()) {
bool occluded = occlusion_tracker.Occluded(it->render_target(),
it->visible_content_rect(),
it->draw_transform());
if (!occluded && it->WillDraw(draw_mode, resource_provider_.get())) {
......
AppendQuadsForLayer(target_render_pass,
*it,
occlusion_tracker,
&append_quads_data);
}
......
}
......
}
......
return draw_result;
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
在CC中,一个Render Pass代表的是一个Render Surface。一个Render Surface被Active Layer Tree中的若干个Layer对象共用。Render Surface描述的是一个有后端储存的绘图表面。这意味着共用一个Render Surface的所有LayerImpl对象都将UI绘制在同一个绘图表面中。在软件渲染方式中,绘制表面可以理解为一个缓冲区;在硬件渲染方式中,绘制表面可以理解为一个FBO。
CC将Render Surface也抽象为一个LayerImpl对象,这种类型的LayerImpl对象称为Target Render Surface。一个Render Surface可能是绘制在另外一个Render Surface上面的。在这种情况下,前一种Render Surface称为Contributing Render Surface。Contributing Render Surface同样也是抽象为一个LayerImpl对象。
综上所述,参数frame指向的一个FrameData对象的成员变量render_surface_layer_list描述的LayerImpl对象列表包含了以下三种类型的LayerImpl对象:
1. 代表网页元素的LayerImpl对象。
2. Contributing Render Surface类型的LayerImpl对象。
3. Target Render Surface类型的LayerImpl对象。
为了方便描述,我们分别将上述三种LayerImpl对象称为Layer、Contributing Render Surface和Target Render Surface。对于共用同一个Target Render Surface的Contributing Render Surface和Layer,能够保证它们在上述LayerImpl对象列表中,出现顺序比它们共用的Target Render Surface之前。
LayerTreeHostImpl类的成员函数CalculateRenderPasses所做的事情就是遍历上述LayerImpl对象。在遍历的过程中,每一个LayerImpl对象都封装成一个LayerIteratorType对象进行访问:
1. 对于Layer类型的LayerImpl对象,调用其对应的LayerIteratorType对象的成员函数represents_itself得到的返回值为true。
2. 对于Contributing Render Surface类型的LayerImpl对象,调用其对应的LayerIteratorType对象的成员函数represents_contributing_render_surface得到的返回值为true。
3. 对于Target Render Surface类型的LayerImpl对象,调用其对应的LayerIteratorType对象的成员函数represents_target_render_surface得到的返回值为true。
当遍历到一个Layer类型的LayerImpl对象时,如果该LayerImpl对象的可见区域不为空,并且没有被阻挡,那么它的成员函数WillDraw就会被调用,并且代表它的绘制命令的一系列Draw Quad会通过LayerTreeHostImpl类的成员函数AppendQuadsForLayer添加到与其Target Render Surface的Render Pass中去,如下所示:
static void AppendQuadsForLayer(
RenderPass* target_render_pass,
LayerImpl* layer,
const OcclusionTracker& occlusion_tracker,
AppendQuadsData* append_quads_data) {
QuadSink quad_culler(target_render_pass, &occlusion_tracker);
layer->AppendQuads(&quad_culler, append_quads_data);
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
从这里可以看到,LayerTreeHostImpl类的成员函数AppendQuadsForLayer是通过调用一个LayerImpl对象的成员函数AppendQuads代表其绘制命令的一系列Draw Quads添加到其Target Render Surface的Render Pass中去的。这意味着对于Layer类型的LayerImpl对象来说,在计算Active Layer Tree的Render Pass期间,它的成员函数WillDraw和AppendQuads将会被调用。
回到LayerTreeHostImpl类的成员函数CalculateRenderPasses中,当遍历到一个Contributing Render Surface类型的LayerImpl对象时,代表其绘制命令的Draw Quads也会通过LayerTreeHostImpl类的成员函数AppendQuadsForRenderSurfaceLayer添加到其Target Render Surface的Render Pass中去。
当遍历到一个Target Render Surface类型的LayerImpl对象时,代表其绘制命令的Draw Quads就收集完毕。对于Target Render Surface类型的LayerImpl对象,它们可能会关联有一些Copy请求。这些Copy请求在其关联的Target Render Surface被绘制出来之后执行,就是将绘制出来的Target Render Surface的指定区域拷贝出来,并且将请求发送给请求者,这相当于从是当前绘制的FBO里面Read Back指定区域的图像数据出来。
接下来我们只关注Layer类型的LayerImpl对象将代表自己的绘制命令的一系列Draw Quad添加到其Target Render Surface的Render Pass中的过程。根据前面的分析,这个过程是通过调用LayerImpl类的成员函数WillDraw和AppendQuads实现。我们以TextureLayerImpl类为例,说明上述过程。
TextureLayerImpl类的成员函数WillDraw的实现如下所示:
bool TextureLayerImpl::WillDraw(DrawMode draw_mode,
ResourceProvider* resource_provider) {
......
if (own_mailbox_) {
DCHECK(!external_texture_resource_);
if ((draw_mode == DRAW_MODE_HARDWARE && texture_mailbox_.IsTexture()) ||
(draw_mode == DRAW_MODE_SOFTWARE &&
texture_mailbox_.IsSharedMemory())) {
external_texture_resource_ =
resource_provider->CreateResourceFromTextureMailbox(
texture_mailbox_, release_callback_.Pass());
DCHECK(external_texture_resource_);
texture_copy_.reset();
valid_texture_copy_ = false;
}
if (external_texture_resource_)
own_mailbox_ = false;
}
......
return (external_texture_resource_ || valid_texture_copy_) &&
LayerImpl::WillDraw(draw_mode, resource_provider);
}
这个函数定义在文件external/chromium_org/cc/layers/texture_layer_impl.cc中。
从前面的分析可以知道,当一个TextureLayerImpl对象的成员变量own_mailbox_的值等于true的时候,就表示该TextureLayerImpl对象描述的canvas标签的UI发生了变化。这时候新的UI可以通过成员变量texture_mailbox_指向的一个TextureMailbox对象描述。调用参数resource_provider描述的一个ResourceProvider对象的成员函数CreateResourceFromTextureMailbox可以根据上述TextureMailbox对象创建一个资源,如下所示:
ResourceProvider::ResourceId ResourceProvider::CreateResourceFromTextureMailbox(
const TextureMailbox& mailbox,
scoped_ptr release_callback) {
......
ResourceId id = next_id_++;
......
Resource& resource = resources_[id];
if (mailbox.IsTexture()) {
resource = Resource(0,
gfx::Size(),
Resource::External,
mailbox.target(),
GL_LINEAR,
0,
GL_CLAMP_TO_EDGE,
TextureUsageAny,
RGBA_8888);
}
......
resource.mailbox = mailbox;
......
return id;
}
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
从前面的分析可以知道,一个TextureMailbox对象描述的是一个绑定了纹理对象的Mailbox。ResourceProvider类的成员函数CreateResourceFromTextureMailbox所要做的事情就是为参数mailbox描述的Mailbox创建一个资源,并且给这个资源分配一个ID。
ResourceProvider类的成员函数CreateResourceFromTextureMailbox最后将创建出来的资源保存在成员变量resources_描述的一个Hash Map中,并且将分配给该资源的ID返回给调用者。
回到TextureLayerImpl类的成员函数WillDraw中,根据成员变量texture_mailbox_指向的TextureMailbox对象创建出来的资源的ID被保存在成员变量external_texture_resource_中,同时另外一个成员变量valid_texture_copy_的值被设置为false,表示当前正在处理的TextureLayerImpl对象的UI通过成员变量external_texture_resource_描述的资源进行绘制。这一点体现在TextureLayerImpl类的成员函数AppendQuads中,如下所示:
void TextureLayerImpl::AppendQuads(QuadSink* quad_sink,
AppendQuadsData* append_quads_data) {
......
scoped_ptr quad = TextureDrawQuad::Create();
ResourceProvider::ResourceId id =
valid_texture_copy_ ? texture_copy_->id() : external_texture_resource_;
quad->SetNew(shared_quad_state,
quad_rect,
opaque_rect,
visible_quad_rect,
id,
premultiplied_alpha_,
uv_top_left_,
uv_bottom_right_,
bg_color,
vertex_opacity_,
flipped_);
quad_sink->Append(quad.PassAs());
}
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
TextureLayerImpl类的成员函数AppendQuads创建了一个Texture Draw Quad,并且将成员变量external_texture_resource_描述的资源设置为该Texture Draw Quad使用的资源,也就是一个通过Mailbox传递的纹理对象。这个Texture Draw Quad最后会被添加到参数append_quads_data描述的一个Draw Quad List中去。
以上就是LayerTreeHostImpl类的成员函数PrepareToDraw调用另外一个成员函数CalculateRenderPasses计算Render端的Active Layer Tree的Render Pass List的过程。从前面分析的ThreadProxy类的成员函数DrawSwapInternal可以知道,接下来LayerTreeHostImpl类的成员函数DrawLayers会被调用来绘制上述Render Pass List。
LayerTreeHostImpl类的成员函数DrawLayers的实现如下所示:
void LayerTreeHostImpl::DrawLayers(FrameData* frame,
base::TimeTicks frame_begin_time) {
......
if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE) {
......
} else {
renderer_->DrawFrame(&frame->render_passes,
device_scale_factor_,
DeviceViewport(),
DeviceClip(),
false);
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
我们假设LayerTreeHostImpl类的成员函数DrawLayers是以硬件加速渲染的方式绘制参数frame指向的一个FrameData对象的成员变量render_passes描述的Render Pass List的,这时候LayerTreeHostImpl类的成员函数DrawLayers将会调用成员变量renderer_描述的一个Renderer对象的成员函数DrawFrame执行具体的绘制操作。
LayerTreeHostImpl类的成员变量renderer_描述的Renderer对象是在成员函数CreateAndSetRenderer中创建的,如下所示:
void LayerTreeHostImpl::CreateAndSetRenderer() {
......
if (output_surface_->capabilities().delegated_rendering) {
renderer_ = DelegatingRenderer::Create(
this, &settings_, output_surface_.get(), resource_provider_.get());
} else if (output_surface_->context_provider()) {
renderer_ = GLRenderer::Create(this,
&settings_,
output_surface_.get(),
resource_provider_.get(),
texture_mailbox_deleter_.get(),
settings_.highp_threshold_min);
} else if (output_surface_->software_device()) {
renderer_ = SoftwareRenderer::Create(
this, &settings_, output_surface_.get(), resource_provider_.get());
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
LayerTreeHostImpl类的成员函数CreateAndSetRenderer创建的Renderer对象与成员变量output_surface_指向的一个OutputSurface对象有关:
1. 如果调用该OutputSurface对象的成员函数capabilities得到的Capabilities对象的成员变量delegated_rendering的值等于true,那么创建的就是一个类型为DelegatingRenderer的Renderer。
2. 如果调用该OutputSurface对象的成员函数context_provider能够得到一个ContextProvider对象,那么创建的就是一个类型为GLRenderer的Renderer。
3. 其它情况下,创建的是一个类型为SoftwareRenderer的Renderer。
其中,第1种和第2种情况创建的Renderer通过硬件方式绘制UI。它们的区别在于,前者将UI委托给别人绘制,而后者自己绘制自己的UI。第3种情况创建的Renderer通过软件方式绘制UI。本文只考虑第1种和第2种情况。
在Android 5.0自带的Chromium中,Render端使用的Renderer是DelegatingRenderer,Browser端使用的是GLRenderer。为了验证这个结构,接下来我们分别观察它们所使用的OutputSurface的创建过程。
从前面Chromium的GPU进程启动过程分析一文可以知道,Render端使用的OutputSurface是通过RenderWidget类的成员函数CreateOutputSurface创建的,如下所示:
scoped_ptr RenderWidget::CreateOutputSurface(bool fallback) {
......
uint32 output_surface_id = next_output_surface_id_++;
if (command_line.HasSwitch(switches::kEnableDelegatedRenderer)) {
DCHECK(is_threaded_compositing_enabled_);
return scoped_ptr(
new DelegatedCompositorOutputSurface(
routing_id(),
output_surface_id,
context_provider));
}
......
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
从这里可以看到, 只要Render进程的启动参数设置了switches::kEnableDelegatedRenderer选项,那么RenderWidget类的成员函数CreateOutputSurface为Render端创建的OutputSurface的类型就为DelegatedCompositorOutputSurface。
DelegatedCompositorOutputSurface对象的创建过程,也就是DelegatedCompositorOutputSurface类的构造函数的实现,如下所示:
DelegatedCompositorOutputSurface::DelegatedCompositorOutputSurface(
int32 routing_id,
uint32 output_surface_id,
const scoped_refptr& context_provider)
: CompositorOutputSurface(routing_id,
output_surface_id,
context_provider,
scoped_ptr(),
true) {
capabilities_.delegated_rendering = true;
capabilities_.max_frames_pending = 1;
}
这个函数定义在文件external/chromium_org/content/renderer/gpu/delegated_compositor_output_surface.cc中。
从这里可以看到,DelegatedCompositorOutputSurface类的构造函数会将成员变量capabilities_描述的一个Capabilities对象的成员变量delegated_rendering的值等于true,这样就会使用得前面分析的LayerTreeHostImpl类的成员函数CreateAndSetRenderer为Render端创建一个类型为DelegatingRenderer的Renderer。
Browser进程在启动Render进程之前,会调用函数AppendCompositorCommandLineFlags判断是否给Render进程的启动参数设置一个switches::kEnableDelegatedRenderer选项,如下所示:
static void AppendCompositorCommandLineFlags(CommandLine* command_line) {
......
if (IsThreadedCompositingEnabled())
command_line->AppendSwitch(switches::kEnableThreadedCompositing);
if (IsDelegatedRendererEnabled())
command_line->AppendSwitch(switches::kEnableDelegatedRenderer);
......
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。
只要调用函数IsDelegatedRendererEnabled得到的返回值为true,那么Render进程的启动参数就会包含有一个switches::kEnableDelegatedRenderer选项。从这里还可以看到,只要调用函数IsThreadedCompositingEnabled得到的返回值为true,那么Render进程的启动参数也会包含有一个switches::kEnableThreadedCompositing选项。
函数IsThreadedCompositingEnabled的实现如下所示:
bool IsThreadedCompositingEnabled() {
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
// Command line switches take precedence over blacklist.
if (command_line.HasSwitch(switches::kDisableThreadedCompositing))
return false;
if (command_line.HasSwitch(switches::kEnableThreadedCompositing))
return true;
......
}
这个函数定义在文件external/chromium_org/content/browser/gpu/compositor_util.cc中。
从这里可以看到,只要Browser进程的启动参数包含有switches::kEnableThreadedCompositing选项,那么函数IsThreadedCompositingEnabled的返回值就为true。
函数IsDelegatedRendererEnabled的实现如下所示:
bool IsDelegatedRendererEnabled() {
const CommandLine& command_line = *CommandLine::ForCurrentProcess();
bool enabled = false;
......
// Flags override.
enabled |= command_line.HasSwitch(switches::kEnableDelegatedRenderer);
enabled &= !command_line.HasSwitch(switches::kDisableDelegatedRenderer);
// Needs compositing, and thread.
if (enabled && !IsThreadedCompositingEnabled()) {
enabled = false;
......
}
return enabled;
}
这个函数定义在文件external/chromium_org/content/browser/gpu/compositor_util.cc中。
从这里可以看到,只要Browser进程的启动参数包含有switches::kEnableDelegatedRenderer和switches::kEnableThreadedCompositing选项,并且不包含有switches::kDisableDelegatedRenderer选项,那么函数IsDelegatedRendererEnabled的返回值就为true。
Browser进程在启动的过程中,会调用函数SetContentCommandLineFlags给启动参数设置switches::kEnableDelegatedRenderer和switches::kEnableThreadedCompositing选项的,如下所示:
void SetContentCommandLineFlags(int max_render_process_count,
const std::string& plugin_descriptor) {
......
CommandLine* parsed_command_line = CommandLine::ForCurrentProcess();
......
parsed_command_line->AppendSwitch(switches::kEnableThreadedCompositing);
......
parsed_command_line->AppendSwitch(switches::kEnableDelegatedRenderer);
......
}
这个函数定义在文件external/chromium_org/content/browser/android/content_startup_flags.cc中。
综合以上的分析,可以得出结论,LayerTreeHostImpl类的成员函数CreateAndSetRenderer为Render端创建的Renderer是DelegatingRenderer。
接下来我们再分析Browser端使用的OutputSurface的创建过程。从前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文可以知道,Browser端使用的OutputSurface是通过调用CompositorImpl类的成员函数CreateOutputSurface创建的,如下所示:
scoped_ptr CompositorImpl::CreateOutputSurface(
bool fallback) {
......
scoped_refptr context_provider;
BrowserGpuChannelHostFactory* factory =
BrowserGpuChannelHostFactory::instance();
scoped_refptr gpu_channel_host = factory->GetGpuChannel();
if (gpu_channel_host && !gpu_channel_host->IsLost()) {
context_provider = ContextProviderCommandBuffer::Create(
CreateGpuProcessViewContext(gpu_channel_host, attrs, surface_id_),
"BrowserCompositor");
}
......
return scoped_ptr(
new OutputSurfaceWithoutParent(context_provider));
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。
CompositorImpl类的成员函数CreateOutputSurface首先创建了一个类型为ContextProviderCommandBuffer的ContextProvider,然后使用这个ContextProvider对象创建了一个OutputSurfaceWithoutParent对象,作为Browser端的OutputSurface。
OutputSurfaceWithoutParent对象的创建过程,也就是OutputSurfaceWithoutParent类的构造函数的实现,如下所示:
class OutputSurfaceWithoutParent : public cc::OutputSurface {
public:
OutputSurfaceWithoutParent(const scoped_refptr<
content::ContextProviderCommandBuffer>& context_provider)
: cc::OutputSurface(context_provider) {
capabilities_.adjust_deadline_for_parent = false;
}
......
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。
从这里可以看到,OutputSurfaceWithoutParent类的构造函数并没有将其成员变量capabilities_描述的Capabilities对象的成员变量delegated_rendering的值设置为true,但是它又包含了一个类型为ContextProviderCommandBuffer的ContextProvider,即参数context_provider描述的一个ContextProviderCommandBuffer对象,因此前面分析LayerTreeHostImpl类的成员函数CreateAndSetRenderer为Browser端创建的Renderer是GLRenderer。
有了以上的背景知识之后,回到LayerTreeHostImpl类的成员函数DrawLayers中,我们就可以知道,对于Render端来说,它调用的是DelegatingRenderer类的成员函数DrawFrame对参数frame指向的一个FrameData对象的成员变量render_passes描述的一个Render Pass List进行绘制。
DelegatingRenderer类的成员函数DrawFrame的实现如下所示:
void DelegatingRenderer::DrawFrame(RenderPassList* render_passes_in_draw_order,
float device_scale_factor,
const gfx::Rect& device_viewport_rect,
const gfx::Rect& device_clip_rect,
bool disable_picture_quad_image_filtering) {
......
delegated_frame_data_ = make_scoped_ptr(new DelegatedFrameData);
DelegatedFrameData& out_data = *delegated_frame_data_;
out_data.device_scale_factor = device_scale_factor;
// Move the render passes and resources into the |out_frame|.
out_data.render_pass_list.swap(*render_passes_in_draw_order);
// Collect all resource ids in the render passes into a ResourceIdArray.
ResourceProvider::ResourceIdArray resources;
DrawQuad::ResourceIteratorCallback append_to_array =
base::Bind(&AppendToArray, &resources);
for (size_t i = 0; i < out_data.render_pass_list.size(); ++i) {
RenderPass* render_pass = out_data.render_pass_list.at(i);
for (size_t j = 0; j < render_pass->quad_list.size(); ++j)
render_pass->quad_list[j]->IterateResources(append_to_array);
}
resource_provider_->PrepareSendToParent(resources, &out_data.resource_list);
}
这个函数定义在文件external/chromium_org/cc/output/delegating_renderer.cc中。
DelegatingRenderer类的成员函数DrawFrame实际上没有绘制参数render_passes_in_draw_order描述的一个Render Pass List,它仅仅是将该Render Pass List及其引用的资源的ID分别保存在其成员变量delegated_frame_data_描述的一个DelegatedFrameData对象的成员变量render_pass_list和resource_list中。
其中,保存资源ID的操作是通过调用DelegatedFrameData对象的成员变量resource_provider_描述的一个ResourceProvider对象的成员函数PrepareSendToParent进行的,如下所示:
void ResourceProvider::PrepareSendToParent(const ResourceIdArray& resources,
TransferableResourceArray* list) {
......
GLES2Interface* gl = ContextGL();
bool need_sync_point = false;
for (ResourceIdArray::const_iterator it = resources.begin();
it != resources.end();
++it) {
TransferableResource resource;
TransferResource(gl, *it, &resource);
if (!resource.mailbox_holder.sync_point && !resource.is_software)
need_sync_point = true;
++resources_.find(*it)->second.exported_count;
list->push_back(resource);
}
if (need_sync_point) {
GLuint sync_point = gl->InsertSyncPointCHROMIUM();
for (TransferableResourceArray::iterator it = list->begin();
it != list->end();
++it) {
if (!it->mailbox_holder.sync_point)
it->mailbox_holder.sync_point = sync_point;
}
}
}
这个函数定义在文件external/chromium_org/cc/output/delegating_renderer.cc中。
对于参数resources描述的每一个资源,ResourceProvider类的成员函数PrepareSendToParent首先调用另外一个成员函数TransferResource将其转换为一个可以跨OpenGL上下文传输的资源(因为这些资源要从Render端传递给Browser端使用),然后再将转换后得到的可传输资源保存在另外一个参数list描述的一个资源列表中返回给调用者。
ResourceProvider类的成员函数PrepareSendToParent还做有的另外一件重要事情是检查是否每一次可传输资源都关联有一个Sync Point。只要其中的一个资源没有关联,那么ResourceProvider类的成员函数PrepareSendToParent都会往当前正在处理的OpenGL上下文(也就是Render端OpenGL上下文)插入一个Sync Point,作为之前还没有关联Sync Point的可传输资源的Sync Point。这是因为这些资源传递给Browser端的时候,可能还没有创建完毕,这样Browser端就可以通过这个Sync Point等待它们创建完毕之后再使用。
现在我们继续分析ResourceProvider类的成员函数TransferResource的实现,如下所示:
void ResourceProvider::TransferResource(GLES2Interface* gl,
ResourceId id,
TransferableResource* resource) {
Resource* source = GetResource(id);
......
resource->id = id;
resource->format = source->format;
resource->mailbox_holder.texture_target = source->target;
resource->filter = source->filter;
resource->size = source->size;
resource->is_repeated = (source->wrap_mode == GL_REPEAT);
if (source->type == Bitmap) {
resource->mailbox_holder.mailbox = source->shared_bitmap_id;
resource->is_software = true;
} else if (!source->mailbox.IsValid()) {
LazyCreate(source);
......
GLC(gl,
gl->BindTexture(resource->mailbox_holder.texture_target,
source->gl_id));
......
// This is a resource allocated by the compositor, we need to produce it.
// Don't set a sync point, the caller will do it.
GLC(gl, gl->GenMailboxCHROMIUM(resource->mailbox_holder.mailbox.name));
GLC(gl,
gl->ProduceTextureCHROMIUM(resource->mailbox_holder.texture_target,
resource->mailbox_holder.mailbox.name));
source->mailbox = TextureMailbox(resource->mailbox_holder);
} else {
......
// This is either an external resource, or a compositor resource that we
// already exported. Make sure to forward the sync point that we were given.
resource->mailbox_holder.mailbox = source->mailbox.mailbox();
resource->mailbox_holder.texture_target = source->mailbox.target();
resource->mailbox_holder.sync_point = source->mailbox.sync_point();
source->mailbox.set_sync_point(0);
}
}
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数TransferResource要做的事情就是将参数id描述的资源转换为参数resource描述的一个可跨OpenGL上下文传输的资源。从前面的分析可以知道,一个资源可以通过一个Mailbox从一个OpenGL上下文传递给另外一个OpenGL上下文。因此,只要给要传输的资源关联上一个Mailbox,就可以将它转换成一个可跨OpenGL上下文传输的资源。当然,这一点只针对硬件方式渲染的资源。对于软件方式渲染的资源,它们的type被设置为Bitmap,并且它们的内容是保存共享内存中的。由于共享内存本身就是可以跨OpenGL上下文进行传输的,因此就不需要通过Mailbox进行传输了。
对于硬件方式渲染的资源,分两种情况考虑。第一种情况是该资源本身已经关联有Mailbox。例如图1所示的TextureLayer描述的纹理对象。这个纹理对象由WebGL端创建,描述的是网页中的canvas标签的UI,它在创建的时候已经关联有Mailbox和Sync Point。这种类型的资源在转换为可跨OpenGL上下文传输的资源时,复用已有的Mailbox和Sync Point即可。
第二种情况是要传输的资源本身还没有关联Mailbox。这时候就需要为它创建一个Mailbox。从前面的分析可以知道,我们可以通过调用参数gl描述的一个OpenGL接口的成员函数GenMailboxCHROMIUM和ProduceTextureCHROMIUM创建一个Mailbox,并且将这个Mailbox与要传输的资源进行关联。
这意味着,当DelegatingRenderer类的成员函数DrawFrame调用结束后,Render端要传递给Browser端的Render Pass List就保存在DelegatingRenderer类的成员变量delegated_frame_data_描述的一个DelegatedFrameData对象中了,并且该Render Pass List涉及到的所有以硬件方式渲染的资源都关联一个Mailbox和一个Sync Point。正是由这些资源都关联有Mailbox,它们才可以从一个OpenGL上下文传递到另外一个OpenGL上下文使用。又正是由于这些资源都关联有Sync Point,才能保证目标OpenGL上下文在使用它们的时候,它们在源OpenGL上下文中是已经创建完成了的。
回到前面分析的ThreadProxy类的成员函数DrawSwapInternal中,最后LayerTreeHostImpl类的成员函数SwapBuffers会被调用来将上述Render Pass List从Render端传递给Browser端绘制。
LayerTreeHostImpl类的成员函数SwapBuffers的实现如下所示:
bool LayerTreeHostImpl::SwapBuffers(const LayerTreeHostImpl::FrameData& frame) {
......
CompositorFrameMetadata metadata = MakeCompositorFrameMetadata();
......
renderer_->SwapBuffers(metadata);
return true;
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
从前面的分析可以知道,在我们这个情景中,LayerTreeHostImpl类的成员变量renderer_指向的是一个DelegatingRenderer对象,这里调用它的成员函数SwapBuffers将其内部维护的一个Render Pass List传递给Browser端绘制。
DelegatingRenderer类的成员函数SwapBuffers的实现如下所示:
void DelegatingRenderer::SwapBuffers(const CompositorFrameMetadata& metadata) {
......
CompositorFrame compositor_frame;
compositor_frame.metadata = metadata;
compositor_frame.delegated_frame_data = delegated_frame_data_.Pass();
output_surface_->SwapBuffers(&compositor_frame);
}
这个函数定义在文件external/chromium_org/cc/output/delegating_renderer.cc中。
从前面的分析可以知道,在我们这个情景中,DelegatingRenderer类的成员变量output_surface_指向的是一个DelegatedCompositorOutputSurface对象,这里调用它的成员函数SwapBuffers将DelegatingRenderer类的成员变量delegated_frame_data_描述的一个Render Pass List传递给Browser端绘制。
DelegatedCompositorOutputSurface类的成员函数SwapBuffers是从父类CompositorOutputSurface继承下来的,它的实现如下所示:
void CompositorOutputSurface::SwapBuffers(cc::CompositorFrame* frame) {
......
if (use_swap_compositor_frame_message_) {
Send(new ViewHostMsg_SwapCompositorFrame(routing_id_,
output_surface_id_,
*frame));
......
return;
}
......
OutputSurface::SwapBuffers(frame);
}
这个函数定义在文件external/chromium_org/content/renderer/gpu/compositor_output_surface.cc。
当成员变量use_swap_compositor_frame_message_的值等于true时,CompositorOutputSurface类的成员函数SwapBuffers直接向Browser端发送一个类型为ViewHostMsg_SwapCompositorFrame的IPC消息,请求它绘制包含在参数frame里面的一个Render Pass List。
当成员变量use_swap_compositor_frame_message_的值等于false时,CompositorOutputSurface类的成员函数SwapBuffers调用父类OutputSurface的成员函数
SwapBuffers向GPU命令缓冲区写入一个gles2::cmds::SwapBuffers命令,用来请求Browser端合成Render端的UI。
CompositorOutputSurface类的成员变量use_swap_compositor_frame_message_的值什么时候等于true,什么时候又等于false呢?这个成员变量是在CompositorOutputSurface类的构造函数中设置的,如下所示:
CompositorOutputSurface::CompositorOutputSurface(
int32 routing_id,
uint32 output_surface_id,
const scoped_refptr& context_provider,
scoped_ptr software_device,
bool use_swap_compositor_frame_message)
: ......,
use_swap_compositor_frame_message_(use_swap_compositor_frame_message),
...... {
......
}
这个函数定义在文件external/chromium_org/content/renderer/gpu/compositor_output_surface.cc中。
从这里可以看到,CompositorOutputSurface类的成员变量use_swap_compositor_frame_message_由参数use_swap_compositor_frame_message进行初始化。
前面提到,Render端使用的OutputSurface实际上是一个DelegatedCompositorOutputSurface。DelegatedCompositorOutputSurface类的构造函数在调用父类CompositorOutputSurface的构造函数的时候,传递进来的参数use_swap_compositor_frame_message的值等于true。
这意味着对于Render端来说,当它使用的OutputSurface是一个DelegatedCompositorOutputSurface时,它会通过CompositorOutputSurface类的成员函数SwapBuffers向Browser端发送一个类型为ViewHostMsg_SwapCompositorFrame的IPC消息。
当Render端使用的OutputSurface不是一个DelegatedCompositorOutputSurface时,它有可能使用一个MailboxOutputSurface或者CompositorOutputSurface,如下所示:
scoped_ptr RenderWidget::CreateOutputSurface(bool fallback) {
......
if (command_line.HasSwitch(switches::kEnableDelegatedRenderer)) {
......
return scoped_ptr(
new DelegatedCompositorOutputSurface(
routing_id(),
output_surface_id,
context_provider));
}
if (!context_provider.get()) {
scoped_ptr software_device(
new CompositorSoftwareOutputDevice());
return scoped_ptr(new CompositorOutputSurface(
routing_id(),
output_surface_id,
NULL,
software_device.Pass(),
true));
}
if (command_line.HasSwitch(cc::switches::kCompositeToMailbox)) {
......
return scoped_ptr(
new MailboxOutputSurface(
routing_id(),
output_surface_id,
context_provider,
scoped_ptr(),
format));
}
bool use_swap_compositor_frame_message = false;
return scoped_ptr(
new CompositorOutputSurface(
routing_id(),
output_surface_id,
context_provider,
scoped_ptr(),
use_swap_compositor_frame_message));
}
这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。
前面我们已经分析过,当Render进程的启动参数包含有switches::kEnableDelegatedRenderer选项时,Render端使用的是DelegatedCompositorOutputSurface。
Render进程的启动参数不包含有switches::kEnableDelegatedRenderer选项,但是包含有cc::switches::kCompositeToMailbox选项时,Render端使用的是MailboxOutputSurface。这时候Render端自己将自己的UI绘制在一个纹理上,然后MailboxOutputSurface类的成员函数SwapBuffers被调用时,会通过Mailbox将这个纹理传递给Browser端合成。这个Mailbox又是通过上述的ViewHostMsg_SwapCompositorFrame消息发送给Browser端的。
当Render进程的启动参数既不包含有switches::kEnableDelegatedRenderer选项,也不包含有cc::switches::kCompositeToMailbox选项时,Render端使用的是CompositorOutputSurface。但是当Render端使用的是软件渲染方式时,它使用的CompositorOutputSurface的成员变量use_swap_compositor_frame_message_的值等于true。这意味着这时候Render端也是通过上述的ViewHostMsg_SwapCompositorFrame消息请Browser端绘制自己的UI的。而当Render端使用的是硬件渲染方式时,它使用的CompositorOutputSurface的成员变量use_swap_compositor_frame_message_的值等于false。这意味着这时候Render端是通过向GPU命令缓冲区写入一个gles2::cmds::SwapBuffers命令请求Browser端绘制Render端的UI的。
GPU进程在处理的Render端发送过来的gles2::cmds::SwapBuffers命令时,首先会将Render端的UI绘制在一个纹理上,然后再通过一个Mailbox将该纹理传递给Browser端进行合成。这种处理方式与Render端使用MailboxOutputSurface时是类似的。主要区别是前者是通过GPU进程将UI绘制在纹理上,而后者是自己将自己的UI绘制在纹理上。绘制出来的纹理最后都是通过Mailbox传递给Browser端合成的。
本文只考虑Render端使用DelegatedCompositorOutputSurface的情况,因此接下来我们就继续分析Browser进程处理类型为ViewHostMsg_SwapCompositorFrame的IPC消息的过程,以便了解Browser端绘制Render端UI的过程。
每一个Render端在Browser进程都有一个对应的RenderWidgetHostImpl对象。这个RenderWidgetHostImpl对象的成员函数OnMessageReceived负责处理其对应的Render端发送过来的类型为ViewHostMsg_SwapCompositorFrame的IPC消息的,如下所示:
bool RenderWidgetHostImpl::OnMessageReceived(const IPC::Message &msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostImpl, msg)
......
IPC_MESSAGE_HANDLER_GENERIC(ViewHostMsg_SwapCompositorFrame,
OnSwapCompositorFrame(msg))
......
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
......
return handled;
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。
RenderWidgetHostImpl类的成员函数OnMessageReceived将类型为ViewHostMsg_SwapCompositorFrame的IPC消息的分发给另外一个成员函数OnSwapCompositorFrame处理。
RenderWidgetHostImpl类的成员函数OnSwapCompositorFrame的实现如下所示:
bool RenderWidgetHostImpl::OnSwapCompositorFrame(
const IPC::Message& message) {
......
ViewHostMsg_SwapCompositorFrame::Param param;
if (!ViewHostMsg_SwapCompositorFrame::Read(&message, ¶m))
return false;
scoped_ptr frame(new cc::CompositorFrame);
uint32 output_surface_id = param.a;
param.b.AssignTo(frame.get());
......
if (view_) {
view_->OnSwapCompositorFrame(output_surface_id, frame.Pass());
......
}
......
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。
从前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文可以知道,在Android平台上,RenderWidgetHostImpl类的成员变量view_指向的是一个RenderWidgetHostViewAndroid对象。RenderWidgetHostImpl类的成员函数OnSwapCompositorFrame将会调用这个RenderWidgetHostViewAndroid对象的成员函数OnSwapCompositorFrame绘制Render端的UI。
RenderWidgetHostViewAndroid类的成员函数OnSwapCompositorFrame的实现如下所示:
void RenderWidgetHostViewAndroid::OnSwapCompositorFrame(
uint32 output_surface_id,
scoped_ptr frame) {
InternalSwapCompositorFrame(output_surface_id, frame.Pass());
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。
RenderWidgetHostViewAndroid类的成员函数OnSwapCompositorFrame调用另外一个成员函数InternalSwapCompositorFrame绘制Render端的UI,如下所示:
void RenderWidgetHostViewAndroid::InternalSwapCompositorFrame(
uint32 output_surface_id,
scoped_ptr frame) {
......
cc::RenderPass* root_pass =
frame->delegated_frame_data->render_pass_list.back();
texture_size_in_layer_ = root_pass->output_rect.size();
......
SwapDelegatedFrame(output_surface_id, frame->delegated_frame_data.Pass());
......
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。
在从Render端传递过来的Render Pass List中,最末尾的Render Pass是一个Root Render Pass。这个Root Render Pass代表的就是整个Render端最后的UI。
RenderWidgetHostViewAndroid类的成员函数InternalSwapCompositorFrame首先通过Render端传递过来的Root Render Pass获得Render端UI的大小,并且记录在成员变量texture_size_in_layer_中。
RenderWidgetHostViewAndroid类的成员函数InternalSwapCompositorFrame接下来又通过调用另外一个成员函数SwapDelegatedFrame绘制Render端的UI,如下所示:
void RenderWidgetHostViewAndroid::SwapDelegatedFrame(
uint32 output_surface_id,
scoped_ptr frame_data) {
bool has_content = !texture_size_in_layer_.IsEmpty();
......
if (!has_content) {
DestroyDelegatedContent();
} else {
if (!resource_collection_.get()) {
resource_collection_ = new cc::DelegatedFrameResourceCollection;
resource_collection_->SetClient(this);
}
if (!frame_provider_ ||
texture_size_in_layer_ != frame_provider_->frame_size()) {
RemoveLayers();
frame_provider_ = new cc::DelegatedFrameProvider(
resource_collection_.get(), frame_data.Pass());
layer_ = cc::DelegatedRendererLayer::Create(frame_provider_);
AttachLayers();
} else {
frame_provider_->SetFrameData(frame_data.Pass());
}
}
if (layer_.get()) {
......
layer_->SetNeedsDisplay();
}
base::Closure ack_callback =
base::Bind(&RenderWidgetHostViewAndroid::SendDelegatedFrameAck,
weak_ptr_factory_.GetWeakPtr(),
output_surface_id);
ack_callbacks_.push(ack_callback);
......
}
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。
Browser端与Render端一样,也是通过CC来绘制UI的,也就是它的UI也通过图1所示的Layer Tree、Pending Layer Tree和Active Layer Tree三棵Tree描述。其中,每一个Render端在Browser端的Layer Tree中,都对应有一个DelegatedRendererLayer对象;相应地,在Pending Layer Tree和Active Layer Tree中,分别对应有一个DelegatedRendererLayerImpl对象。
通过调用DelegatedRendererLayer类的静态成员函数Create可以创建一个DelegatedRendererLayer对象。创建一个DelegatedRendererLayer对象需要一个DelegatedFrameProvider对象。DelegatedFrameProvider对象负责向DelegatedRendererLayer对象提供一个Compositor Frame。这个Compositor Frame包含了从Render端传递过来的Render Pass List。
从图1可以知道,Browser端的Layer Tree的内容首先会被同步到Pending Layer Tree中。这时候Layer Tree中的DelegatedRendererLayer对象就会将其内部的Render Pass List设置给在Pending Layer Tree中对应的DelegatedRendererLayerImpl对象。接下来Pending Layer Tree会变成Active Tree Layer,Active Layer Tree最后又会被绘制。这时候Active Layer Tree中的DelegatedRendererLayerImpl对象就可以将其内部的Render Pass List包含的Draw Quad贡献出来,作为Browser端UI的一部分进行绘制。
在创建一个DelegatedFrameProvider对象的时候,除了需要提供一个Compositor Frame,还需要提供一个DelegatedFrameResourceCollection对象。这个DelegatedFrameResourceCollection对象负责管理Compositor Frame引用的资源。这些资源是从Render端传递过来的,DelegatedFrameResourceCollection对象的作用就是当Browser端不需要使用它们时,将它们返回给Render端进行回收。
有了上面所述的背景知识之后,我们就可以分析RenderWidgetHostViewAndroid类的成员函数SwapDelegatedFrame的实现了。
RenderWidgetHostViewAndroid类的成员函数SwapDelegatedFrame首先是检查成员变量texture_size_in_layer_的值。前面提到,RenderWidgetHostViewAndroid类的成员变量texture_size_in_layer_描述的是Render端UI的大小。如果这个大小值等于0,那么就说明Render端UI是空的。这时候Render端在Browser端的Layer Tree中对应的DelegatedRendererLayer对象就需要移除。这是通过调用RenderWidgetHostViewAndroid类的成员函数DestroyDelegatedContent实现的。
如果Render端UI是不为空,RenderWidgetHostViewAndroid类的成员函数SwapDelegatedFrame会执行以下逻辑:
1. 检查用来管理Render端传递过来的Compositor Frame引用的资源的DelegatedFrameResourceCollection对象是否已经创建。如果还没有创建,那么就创建一个,并且保存在成员变量resource_collection_中。
2. 检查用来向代表Render端UI的DelegatedRendererLayer对象提供Compositor Frame的DelegatedFrameProvider对象是否已经创建。如果还没有创建,那么就创建一个,并且保存在成员变量frame_provider_中。这时候还需要根据新创建出来的DelegatedFrameProvider对象创建一个DelegatedRendererLayer对象,并且保存在成员变量layer_中。创建出来的DelegatedFrameProvider对象需要通过添加到Browser端的Layer Tree中去,这是通过调用RenderWidgetHostViewAndroid类的成员函数AttachLayers实现的。
3. 如果用来向代表Render端UI的DelegatedRendererLayer对象提供Compositor Frame的DelegatedFrameProvider对象已经创建,但是Render端UI的大小发生了变化。这时候也需要创建一个新的DelegatedFrameProvider对象,并且根据这个新的DelegatedFrameProvider对象创建一个DelegatedRendererLayer对象,以及将该DelegatedRendererLayer对象添加到Browser端的Layer Tree中去。不过在执行这些操作之前,需要调用RenderWidgetHostViewAndroid类的成员函数RemoveLayers将旧的DelegatedRendererLayer对象从Browser端的Layer Tree中移除。
4. 如果用来向代表Render端UI的DelegatedRendererLayer对象提供Compositor Frame的DelegatedFrameProvider对象已经创建,并且Render端UI的大小也没有发生变化,那么只需要将Render端传递过来的Compositor Frame设置给已有的DelegatedFrameProvider对象即可,即这时候原有的DelegatedRendererLayer对象和DelegatedFrameProvider对象可以复用。将Render端传递过来的Compositor Frame设置给已有的DelegatedFrameProvider对象可以通过调用已有的DelegatedFrameProvider对象的成员函数SetFrameData实现。
RenderWidgetHostViewAndroid类的成员函数SwapDelegatedFrame执行完成以上逻辑之后,最后还有两个工作需要做:
1. 在Render端UI的大小不为空的情况下,调用成员变量layer_指向的DelegatedRendererLayer对象的成员函数SetNeedsDisplay。从前面的分析可以知道,这个DelegatedRendererLayer对象描述的是Render端的UI,调用它的成员函数SetNeedsDisplay是用来通知Browser端它的Layer Tree需要进行更新,也就是需要重新进行绘制。
2. 创建一个Closure对象,保存在成员变量ack_callbacks_描述的一个std::queue中。这个Closure绑定了当前正在处理的RenderWidgetHostViewAndroid对象的成员函数SendDelegatedFrameAck。当Browser端绘制完成下一帧时,就会通过这个Closure对象调用当前正在处理的RenderWidgetHostViewAndroid对象的成员函数SendDelegatedFrameAck,用来将那些从Render端传递过来的、当前又没有被引用的资源返回给Render端回收。
前面提到,Browser端使用的OutputSurface是一个OutputSurfaceWithoutParent对象。这个OutputSurfaceWithoutParent对象从父类OutputSurface继承下来的一个Capabilities对象的成员变量delegated_rendering的值为false。根据前面我们对LayerTreeHostImpl类的成员函数CreateAndSetRenderer的分析可以知道,Browser端使用的Renderer是一个GL Renderer,而不是像Render端一样,使用一个Delegating Renderer作为Renderer。这意味着Browser端最后会通过调用GLRenderer类的成员函数DrawFrame来绘制它的Active Layer Tree。
在分析GLRenderer类的成员函数DrawFrame绘制Browser端的Active Layer Tree之前,我们首先分析Browser端同步它的Layer Tree中的DelegatedRendererLayer对象的内容到Pending Layer Tree中对应的DelegatedRendererLayerImpl对象的过程,以及Browser端的Active Layer Tree中的DelegatedRendererLayerImpl对象的Draw Quad List的收集过程。
根据前面分析的Render端的Layer Tree与Pending Layer Tree的同步过程可以知道,当Browser端将其Layer Tree的内容同步到Pending Layer Tree时,其中包含的DelegatedRendererLayer对象的成员函数PushPropertiesTo会被调用,如下所示:
void DelegatedRendererLayer::PushPropertiesTo(LayerImpl* impl) {
......
DelegatedRendererLayerImpl* delegated_impl =
static_cast(impl);
......
if (frame_data_)
delegated_impl->SetFrameData(frame_data_, frame_damage_);
frame_data_ = NULL;
......
}
这个函数定义在文件external/chromium_org/cc/layers/delegated_renderer_layer.cc中。
参数impl描述的是一个DelegatedRendererLayerImpl对象,因此DelegatedRendererLayer类的成员函数PushPropertiesTo首先将它转换成一个DelegatedRendererLayerImpl对象。
DelegatedRendererLayer类的成员变量frame_data_指向的是一个DelegatedFrameData对象。这个DelegatedFrameData对象是从Render端传递过来的Compositor Frame创建的,代表的是Render端的UI,它里面包含了一个Render Pass List。这个Render Pass List包含的Draw Quad将会由Browser端进行绘制。
DelegatedRendererLayer类的成员函数PushPropertiesTo接下来要做的事情就是将成员变量frame_data_描述的DelegatedFrameData对象设置给参数impl描述的DelegatedRendererLayerImpl对象。这是通过调用DelegatedRendererLayerImpl类的成员函数SetFrameData实现的,如下所示:
void DelegatedRendererLayerImpl::SetFrameData(
const DelegatedFrameData* frame_data,
const gfx::RectF& damage_in_frame) {
......
ResourceProvider* resource_provider = layer_tree_impl()->resource_provider();
......
ScopedPtrVector render_pass_list;
RenderPass::CopyAll(frame_data->render_pass_list, &render_pass_list);
......
ResourceProvider::ResourceIdArray resources_in_frame;
DrawQuad::ResourceIteratorCallback remap_resources_to_parent_callback =
base::Bind(&ResourceRemapHelper,
&invalid_frame,
resource_map,
&resources_in_frame);
for (size_t i = 0; i < render_pass_list.size(); ++i) {
RenderPass* pass = render_pass_list[i];
for (size_t j = 0; j < pass->quad_list.size(); ++j) {
DrawQuad* quad = pass->quad_list[j];
quad->IterateResources(remap_resources_to_parent_callback);
}
}
......
// Declare we are using the new frame's resources.
resources_.swap(resources_in_frame);
resource_provider->DeclareUsedResourcesFromChild(child_id_, resources_);
......
SetRenderPasses(&render_pass_list);
......
}
这个函数定义在文件external/chromium_org/cc/layers/delegated_renderer_layer_impl.cc中。
DelegatedRendererLayerImpl类的成员函数SetFrameData主要是做了三件事情:
1. 将参数frame_data描述的DelegatedFrameData对象包含的Render Pass List拷贝到本地变量render_pass_list描述的Render Pass List中去。从前面的分析可以知道,这个Render Pass List是从Render端传递过来的,它描述的是Render端的UI。
2. 将上述Render Pass List引用的资源记录在DelegatedRendererLayerImpl类的成员变量resources_描述的一个Resource Id Array中,并且将这些资源交给Browser端的Resource Provider管理,表示Browser端的Resource Provider要使用这些资源。
3. 调用DelegatedRendererLayerImpl类的成员函数SetRenderPasses将上述Render Pass List保存在内部,以便以后可以绘制它里面包含的Draw Quad List。
DelegatedRendererLayerImpl类的成员函数SetRenderPasses的实现如下所示:
void DelegatedRendererLayerImpl::SetRenderPasses(
ScopedPtrVector* render_passes_in_draw_order) {
......
for (size_t i = 0; i < render_passes_in_draw_order->size(); ++i) {
ScopedPtrVector::iterator to_take =
render_passes_in_draw_order->begin() + i;
......
scoped_ptr taken_render_pass =
render_passes_in_draw_order->take(to_take);
render_passes_in_draw_order_.push_back(taken_render_pass.Pass());
}
......
}
这个函数定义在文件external/chromium_org/cc/layers/delegated_renderer_layer_impl.cc中。
DelegatedRendererLayerImpl类的成员函数SetRenderPasses将参数render_passes_in_draw_order描述的Render Pass List包含的Render Pass依次提取出来,并且保存在成员变量render_passes_in_draw_order_描述的一个Render Pass List中。
以上就是Browser端的Layer Tree中的DelegatedRendererLayer对象将内容到Pending Layer Tree中对应的DelegatedRendererLayerImpl对象的过程。接下来我们继续分析以及Browser端Pending Layer Tree变成Active Layer Tree之后,它里面的DelegatedRendererLayerImpl对象的Render Pass的收集过程。
根据前面分析的Render端的Active Layer Tree的Render Pass List的计算过程可以知道,Browser端在计算它的Active Layer Tree的Render Pass List的时候,其中包含的DelegatedRendererLayerImpl对象的成员函数AppendQuads将会被调用,如下所示:
void DelegatedRendererLayerImpl::AppendQuads(
QuadSink* quad_sink,
AppendQuadsData* append_quads_data) {
......
RenderPass::Id target_render_pass_id = append_quads_data->render_pass_id;
const RenderPass* root_delegated_render_pass =
render_passes_in_draw_order_.back();
......
// If the index of the RenderPassId is 0, then it is a RenderPass generated
// for a layer in this compositor, not the delegating renderer. Then we want
// to merge our root RenderPass with the target RenderPass. Otherwise, it is
// some RenderPass which we added from the delegating renderer.
bool should_merge_root_render_pass_with_target = !target_render_pass_id.index;
if (should_merge_root_render_pass_with_target) {
......
AppendRenderPassQuads(
quad_sink, append_quads_data, root_delegated_render_pass, frame_size);
} else {
......
int render_pass_index = IdToIndex(target_render_pass_id.index);
const RenderPass* delegated_render_pass =
render_passes_in_draw_order_[render_pass_index];
AppendRenderPassQuads(
quad_sink, append_quads_data, delegated_render_pass, frame_size);
}
}
这个函数定义在文件external/chromium_org/cc/layers/delegated_renderer_layer_impl.cc中。
从Render端传递过来的Render Pass List中的每一个Render Pass都作为Browser端的Render Pass List中的一个Render Pass出现。因此,DelegatedRendererLayerImpl类的成员函数AppendQuads可能会被多次调用。每一次调用,都指定了一个Render Pass Index。通过这个Render Pass Index,DelegatedRendererLayerImpl类的成员函数AppendQuads就知道要从Render端传递过来的Render Pass List中取出哪一个Render Pass,进而将这个Render Pass包含的Draw Quad取出来,保存在参数append_quads_data描述的一个Draw Quad List中。这是通过调用DelegatedRendererLayerImpl类的成员函数AppendRenderPassQuads实现的。
以上就是Browser端的Active Layer Tree中的DelegatedRendererLayerImpl对象的Render Pass的收集过程。收集到的每一个Render Pass都包含了一个Draw Quad List。这些Draw Quad List最终就会被GLRenderer类的成员函数DrawFrame进行绘制。接下来我们就分析GLRenderer类的成员函数DrawFrame的实现。
GLRenderer类的成员函数DrawFrame是从父类DirectRenderer继承下来的,它的实现如下所示:
void DirectRenderer::DrawFrame(RenderPassList* render_passes_in_draw_order,
float device_scale_factor,
const gfx::Rect& device_viewport_rect,
const gfx::Rect& device_clip_rect,
bool disable_picture_quad_image_filtering) {
......
const RenderPass* root_render_pass = render_passes_in_draw_order->back();
......
DrawingFrame frame;
frame.root_render_pass = root_render_pass;
......
for (size_t i = 0; i < render_passes_in_draw_order->size(); ++i) {
RenderPass* pass = render_passes_in_draw_order->at(i);
DrawRenderPass(&frame, pass);
for (ScopedPtrVector::iterator it =
pass->copy_requests.begin();
it != pass->copy_requests.end();
++it) {
......
CopyCurrentRenderPassToBitmap(&frame, pass->copy_requests.take(it));
}
}
......
}
这个函数定义在文件external/chromium_org/cc/output/direct_renderer.cc中。
保存在参数render_passes_in_draw_order描述的一个Render Pass List的最后一个位置上的Render Pass是一个Root Render Pass,DirectRenderer类的成员函数DrawFrame将它记录在一个DrawingFrame对象的成员变量root_render_pass之后,就调用另外一个成员函数DrawRenderPass绘制该Render Pass List的每一个Render Pass。
每一个Render Pass绘制完成之后,DirectRenderer类的成员函数DrawFrame都会检查该Render Pass是否关联有拷贝请求。如果关联有,就调用成员函数CopyCurrentRenderPassToBitmap执行这些拷贝请求。前面我们提到,每一个Render Pass都是绘制在一个Render Surface上的。在硬件加速渲染方式中,一个Render Surface就一个FBO。这意味着属于同一个Render Pass的Draw Quad都是绘制在一个FBO上的。因此,拷贝Render Pass实际上就拷贝其对应的FBO的内容。
有两种方式拷贝出FBO的内容。第一种方式是将FBO的内容拷贝到一个纹理上,这可以通过OpenGL接口类GLES2Implemetation的成员函数CopyTexImage2D实现。拷贝出来的纹理再通过Mailbox传递给请求者。第二种方式是将FBO的内容Read Back到一个共享内存中,这可以过OpenGL接口类GLES2Implemetation的成员函数ReadPixels实现。拷贝出来的共享内存可以直接传递给请求者。实际使用哪一种方式取决于请求者。如果请求者没有显式要求将FBO的内容拷贝到共享内存,那么就会将FBO的内容拷贝到纹理上,因为后者的效率更高。
接下来,我们主要关注每一个Render Pass的绘制过程,也就是DirectRenderer类的成员函数DrawRenderPass的实现,如下所示:
void DirectRenderer::DrawRenderPass(DrawingFrame* frame,
const RenderPass* render_pass) {
......
if (!UseRenderPass(frame, render_pass))
return;
const QuadList& quad_list = render_pass->quad_list;
for (QuadList::ConstBackToFrontIterator it = quad_list.BackToFrontBegin();
it != quad_list.BackToFrontEnd();
++it) {
const DrawQuad& quad = *(*it);
bool should_skip_quad = false;
......
if (!should_skip_quad)
DoDrawQuad(frame, *it);
}
FinishDrawingQuadList();
}
这个函数定义在文件external/chromium_org/cc/output/direct_renderer.cc中。
DirectRenderer类的成员函数DrawRenderPass首先是调用成员函数UseRenderPass在当前OpenGL上下文中绑定与参数render_pass描述的一个Render Pass关联的一个FBO,因为接下来要将该Render Pass绘制在与它关联的FBO上。
DirectRenderer类的成员函数UseRenderPass的实现如下所示:
bool DirectRenderer::UseRenderPass(DrawingFrame* frame,
const RenderPass* render_pass) {
......
if (render_pass == frame->root_render_pass) {
BindFramebufferToOutputSurface(frame);
......
return true;
}
ScopedResource* texture = render_pass_textures_.get(render_pass->id);
......
return BindFramebufferToTexture(frame, texture, render_pass->output_rect);
}
这个函数定义在文件external/chromium_org/cc/output/direct_renderer.cc中。
DirectRenderer类的成员函数UseRenderPass首先是判断参数render_pass描述的Render Pass是否是前面记录在参数frame描述的Draw Frame中的Root Render Pass。如果是的话,那么该Render Pass不能绘制在一个FBO上,而是要绘制在Browser端的Output Surface上。因此,这时候需要将Browser端的Output Surface设置为当前OpenGL上下文的绘制表面,这是通过调用由子类GLRenderer实现的成员函数BindFramebufferToOutputSurface实现的。
如果参数render_pass描述的Render Pass不是前面记录在参数frame描述的Draw Frame中的Root Render Pass,那么DirectRenderer类的成员函数UseRenderPass首先会获得与该Render Pass关联的一个纹理,然后通过调用由子类GLRenderer实现的成员函数BindFramebufferToTexture将该纹理与一个FBO关联,并且将该FBO设置为当前OpenGL上下文的绘制表面。
GLRenderer类的成员函数BindFramebufferToOutputSurface的实现如下所示:
void GLRenderer::BindFramebufferToOutputSurface(DrawingFrame* frame) {
......
output_surface_->BindFramebuffer();
......
}
这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
当前正在处理的GLRenderer对象的成员变量output_surface_描述的就是Browser端使用的Output Surface。从前面的分析可以知道,该Output Surface使用一个OutputSurfaceWithoutParent对象描述。调用该OutputSurfaceWithoutParent对象的成员函数BindFramebuffer实际上是一个Surface View设置为当前OpenGL上下文的绘制表面,因为Browser端最终是将自己的UI绘制在一个Surface View上的。这个Surface View的创建过程可以参考前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文。
GLRenderer类的成员函数BindFramebufferToTexture的实现如下所示:
bool GLRenderer::BindFramebufferToTexture(DrawingFrame* frame,
const ScopedResource* texture,
const gfx::Rect& target_rect) {
......
GLC(gl_, gl_->BindFramebuffer(GL_FRAMEBUFFER, offscreen_framebuffer_id_));
current_framebuffer_lock_ =
make_scoped_ptr(new ResourceProvider::ScopedWriteLockGL(
resource_provider_, texture->id()));
unsigned texture_id = current_framebuffer_lock_->texture_id();
GLC(gl_,
gl_->FramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0));
......
return true;
}
这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
GLRenderer类的成员函数BindFramebufferToTexture首先是调用OpenGL接口类GLES2Implementation的成员函数BindFramebuffer将成员变量offscreen_framebuffer_id_描述的一个FBO设置为当前OpenGL上下文的绘图表面,接下来再调用OpenGL接口类GLES2Implementation的成员函数FramebufferTexture2D将参数texture描述的纹理与前面设置的FBO关联起来,这样DirectRenderer类的成员函数DrawRenderPass接下来要绘制的Draw Quad就会作用在参数texture描述的纹理上。
回到DirectRenderer类的成员函数DrawRenderPass中,设置好当前OpenGL上下文的绘制表面之后,接下来就会调用DirectRenderer类的成员函数DoDrawQuad绘制属于参数render_pass描述的Render Pass的每一个Draw Quad。
DirectRenderer类的成员函数DoDrawQuad是由子类GLRenderer实现的,如下所示:
void GLRenderer::DoDrawQuad(DrawingFrame* frame, const DrawQuad* quad) {
......
if (quad->material != DrawQuad::TEXTURE_CONTENT) {
FlushTextureQuadCache();
}
switch (quad->material) {
......
case DrawQuad::TEXTURE_CONTENT:
EnqueueTextureQuad(frame, TextureDrawQuad::MaterialCast(quad));
break;
......
}
}
这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
参数quad描述的一个Draw Quad就是当前要绘制的Draw Quad。对于不同类型的Draw Quad,GLRenderer类的成员函数DoDrawQuad调用不同的成员函数对它进行绘制。例如,对于纹理类型的Draw Quad,GLRenderer类的成员函数DoDrawQuad调用成员函数EnqueueTextureQuad对它进行绘制。
GLRenderer类的成员函数EnqueueTextureQuad的实现如下所示:
void GLRenderer::EnqueueTextureQuad(const DrawingFrame* frame,
const TextureDrawQuad* quad) {
TexCoordPrecision tex_coord_precision = TexCoordPrecisionRequired(
gl_,
&highp_threshold_cache_,
highp_threshold_min_,
quad->shared_quad_state->visible_content_rect.bottom_right());
// Choose the correct texture program binding
TexTransformTextureProgramBinding binding;
if (quad->premultiplied_alpha) {
if (quad->background_color == SK_ColorTRANSPARENT) {
binding.Set(GetTextureProgram(tex_coord_precision));
} else {
binding.Set(GetTextureBackgroundProgram(tex_coord_precision));
}
} else {
if (quad->background_color == SK_ColorTRANSPARENT) {
binding.Set(GetNonPremultipliedTextureProgram(tex_coord_precision));
} else {
binding.Set(
GetNonPremultipliedTextureBackgroundProgram(tex_coord_precision));
}
}
int resource_id = quad->resource_id;
if (draw_cache_.program_id != binding.program_id ||
draw_cache_.resource_id != resource_id ||
draw_cache_.needs_blending != quad->ShouldDrawWithBlending() ||
draw_cache_.background_color != quad->background_color ||
draw_cache_.matrix_data.size() >= 8) {
FlushTextureQuadCache();
draw_cache_.program_id = binding.program_id;
draw_cache_.resource_id = resource_id;
draw_cache_.needs_blending = quad->ShouldDrawWithBlending();
draw_cache_.background_color = quad->background_color;
draw_cache_.uv_xform_location = binding.tex_transform_location;
draw_cache_.background_color_location = binding.background_color_location;
draw_cache_.vertex_opacity_location = binding.vertex_opacity_location;
draw_cache_.matrix_location = binding.matrix_location;
draw_cache_.sampler_location = binding.sampler_location;
}
// Generate the uv-transform
draw_cache_.uv_xform_data.push_back(UVTransform(quad));
// Generate the vertex opacity
const float opacity = quad->opacity();
draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[0] * opacity);
draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[1] * opacity);
draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[2] * opacity);
draw_cache_.vertex_opacity_data.push_back(quad->vertex_opacity[3] * opacity);
// Generate the transform matrix
gfx::Transform quad_rect_matrix;
QuadRectTransform(&quad_rect_matrix, quad->quadTransform(), quad->rect);
quad_rect_matrix = frame->projection_matrix * quad_rect_matrix;
Float16 m;
quad_rect_matrix.matrix().asColMajorf(m.data);
draw_cache_.matrix_data.push_back(m);
}
这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
GLRenderer类的成员函数EnqueueTextureQuad实际上并没有直接绘制参数quad描述的一个Texture Draw Quad,而是将它缓存在一个Draw Cache中。这个Draw Cache由GLRenderer类的成员变量draw_cache_中。
为什么要这样做呢?这是一个渲染优化操作。这是因为有可能前面要绘制的Draw Quad也是Texture Draw Quad,并且这些Texture Draw Quad与当前要绘制的Texture Draw Quad引用了相同的纹理。这时候可以考虑将这些Texture Draw Quad的绘制合并一个纹理绘制。这种合并渲染优化在Android应用程序UI硬件加速渲染机制中也使用到了。这一点可以参考前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章。
对于可以合并为同一个纹理绘制的Texture Draw Quad,它们使用的纹理坐标、转换矩阵等信息会被记录在GLRenderer类的成员变量draw_cache_描述的Draw Cache中,后面在绘制纹理的时候就会使用到。
如果当前要绘制的Texture Draw Quad不能与前面要绘制的Texture Draw Quad合并为同一个纹理绘制,那么GLRenderer类的成员函数EnqueueTextureQuad就会调用成员函数FlushTextureQuadCache对前面要绘制的Texture Draw Quad进行绘制,也就是将之前缓存起来的Texture Draw Quad进行绘制。绘制完成之后,再将当前要绘制的Texture Draw Quad缓存起来,因为有可能接下来要绘制的也是一个Texture Draw Quad,并且该Texture Draw Quad可以与当前要绘制的Texture Draw Quad合并为同一个纹理绘制。
两个Texture Draw Quad可以合并为同一个纹理绘制要同时满足以下条件:
1. 使用同一个Shader进行绘制;
2. 引用相同的纹理;
3. 使用相同的Blending渲染模式;
4. 使用相同的背景颜色;
同时,最多允许八个Texture Draw Quad合并为同一个纹理绘制。
回到GLRenderer类的成员函数DoDrawQuad中,我们注意到,如果当前要绘制的Draw Quad不是一个Texture Draw Quad,那么它也会调用成员函数FlushTextureQuadCache对前面已经缓存起来的Texture Draw Quad进行绘制。
可能存在这样的一种情况。一个Render Pass最后绘制的若干个Draw Quad都是Texture Draw Quad,并且这些Texture Draw Quad可以合并为同一个纹理绘制。那些这个纹理绘制操作是由谁触发的呢?根据前面的分析,GLRenderer类的成员函数DoDrawQuad和EnqueueTextureQuad都不会触发。这时候需要回到DirectRenderer类的成员函数DrawRenderPass中,它处理完成一个Render Pass的所有的Draw Quad之后,会调用由子类GLRenderer实现的成员函数FinishDrawingQuadList触发这个纹理绘制操作。
GLRenderer类的成员函数FinishDrawingQuadList的实现如下所示:
void GLRenderer::FlushTextureQuadCache() {
// Check to see if we have anything to draw.
if (draw_cache_.program_id == 0)
return;
// Set the correct blending mode.
SetBlendEnabled(draw_cache_.needs_blending);
// Bind the program to the GL state.
SetUseProgram(draw_cache_.program_id);
......
// Assume the current active textures is 0.
ResourceProvider::ScopedReadLockGL locked_quad(resource_provider_,
draw_cache_.resource_id);
......
GLC(gl_, gl_->BindTexture(GL_TEXTURE_2D, locked_quad.texture_id()));
......
// Draw the quads!
GLC(gl_,
gl_->DrawElements(GL_TRIANGLES,
6 * draw_cache_.matrix_data.size(),
GL_UNSIGNED_SHORT,
0));
// Clear the cache.
draw_cache_.program_id = 0;
draw_cache_.uv_xform_data.resize(0);
draw_cache_.vertex_opacity_data.resize(0);
draw_cache_.matrix_data.resize(0);
}
这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
GLRenderer类的成员函数FinishDrawingQuadList的主要执行过程如下所示:
1. 调用成员函数SetBlendEnabled启用或者关闭Blending渲染。
2. 调用成员函数SetUseProgram加载要使用的Shader。
3. 调用OpenGL接口类GLES2Implementation的成员函数BindTexture将前面缓存起来的Texture Draw Quad引用的纹理绑定到当前OpenGL上下文来。
4. 调用OpenGL接口类GLES2Implementation的成员函数DrawElements对前面缓存起来的Texture Draw Quad引用的纹理进行绘制。
这里有一个关键点需要注意。前面缓存起来的Texture Draw Quad引用的纹理是从其它OpenGL上下文创建并且通过Mailbox传递过来的。从前面的分析可以知道,这个纹理有可能是从Render端OpenGL上下文传递过来的,也有可能是WebGL端OpenGL上下文传递过来的。当前OpenGL上下文,也就是Browser端OpenGL上下文,不能直接使用这个纹理,需要通过Mailbox机制间接使用。
ResourceProvider::ScopedReadLockGL类提供了在一个OpenGL上下文中使用其它OpenGL上下文通过Mailbox传递过来的纹理的操作。接下来我们通过分析它的构造函数来了解它的实现原理。
ResourceProvider::ScopedReadLockGL类的构造函数的实现如下所示:
ResourceProvider::ScopedReadLockGL::ScopedReadLockGL(
ResourceProvider* resource_provider,
ResourceProvider::ResourceId resource_id)
: resource_provider_(resource_provider),
resource_id_(resource_id),
texture_id_(resource_provider->LockForRead(resource_id)->gl_id) {
DCHECK(texture_id_);
}
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider::ScopedReadLockGL类的构造函数通过调用参数resource_provider描述的一个ResourceProvider对象的成员函数LockForRead根据另外一个参数resource_id描述的一个资源在当前OpenGL上下文中创建一个新的纹理对象,并且将新创建纹理对象的ID保存在成员变量texture_id_中,以便后面可以使用。
ResourceProvider类的成员函数LockForRead的实现如下所示:
const ResourceProvider::Resource* ResourceProvider::LockForRead(ResourceId id) {
Resource* resource = GetResource(id);
......
if (resource->type == GLTexture && !resource->gl_id) {
......
GLES2Interface* gl = ContextGL();
......
if (resource->mailbox.sync_point()) {
GLC(gl, gl->WaitSyncPointCHROMIUM(resource->mailbox.sync_point()));
resource->mailbox.set_sync_point(0);
}
resource->gl_id = texture_id_allocator_->NextId();
GLC(gl, gl->BindTexture(resource->target, resource->gl_id));
GLC(gl,
gl->ConsumeTextureCHROMIUM(resource->mailbox.target(),
resource->mailbox.name()));
}
......
return resource;
}
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数LockForRead首先调用成员函数GetResource获得与参数id对应的一个Resource对象resource,并且假设这个Resource对象resource描述的是一个纹理对象。
由于Resource对象resource描述的纹理对象是通过Mailbox从其它OpenGL上下文传递过来的,这个Mailbox由Resource对象resource的成员变量mailbox描述。ResourceProvider类的成员函数LockForRead接下来要做的事情通过上述Mailbox将Resource对象resource描述的纹理对象变为在当前OpenGL上下文中可访问。根据前面对Mailbox机制的分析可以知道,我们可以通过调用OpenGL接口类GLES2Implementation的成员函数ConsumeTextureCHROMIUM做到这一点。
在调用OpenGL接口类GLES2Implementation的成员函数ConsumeTextureCHROMIUM之前,有两个地方需要注意:
1. 如果用来传递纹理对象的Mailbox设置有Sync Point,那么当前OpenGL上下文需要先调用OpenGL接口类GLES2Implementation的成员函数WaitSyncPointCHROMIUM等待该Sync Point被Retired。因为只有等该Sync Point被Retired之后,通过Maillbox传递过来的纹理对象才在源OpenGL上下文创建和绘制完成。
2. 需要调用ResourceProvider类的成员变量texture_id_allocator_描述的一个IdAllocator对象的成员函数NextId在当前OpenGL上下文中创建一个新的纹理对象,并且将这个新的纹理对象绑定到当前OpenGL上下文中来。这样后面调用OpenGL接口类GLES2Implementation的成员函数ConsumeTextureCHROMIUM时,新创建的纹理对象与通过Mailbox传递过来的纹理对象引用的是相同的纹理数据,也就是使得当前OpenGL上下文可以使用通过Mailbox传递过来的纹理对象。
至此,我们就分析完成Browser端合成Render端和WebGL端UI的过程了。这个过程可以总结为:
1. Render端将自己的UI抽象为一个Render Pass List。Render Pass List中的每一个Render Pass都包含有一个Draw Quad List。典型地,Draw Quad List中的每一个Draw Quad描述的都是一个纹理绘制命令。
2. Render端将自己的Render Pass List传递Browser端进行绘制。其中,Render Pass List引用的纹理对象通过Mailbox传递给Browser端,使得Browser端可以使用这些纹理对象。
3. WebGL端将自己的UI绘制在一个纹理上,并且通过Mailbox将这个纹理传递给Render端。Render端为WebGL端创建了一个Render Pass,作为在传递给Browser端的Render Pass List的一个Render Pass。这个Render Pass的Draw Quad List包含了一个Draw Quad。这个Draw Quad引用了从WebGL端传递过来的纹理对象。
至此,Chromium硬件加速渲染机制基础知识这个系列的文章我们也分析完成了,重新学习可以参考Chromium硬件加速渲染机制基础知识简要介绍和学习计划一文。在接下来的文章中,我们将注意力转向Render端渲染网页的过程。在分析Render端渲染网页的过程之前,我们又要先分析网页的加载和解析过程,这会涉及到WebKit的内容。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。