在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所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
图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的定义非常easy,就是一个名称。例如以下所看到的:
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<const Mailbox*>(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<const Mailbox*>(data);
...... scoped_refptr<TextureRef> 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上下文共享组的情况下就会出现。
上述问题的解决的方法似乎非常easy。仅仅要我们找到负责管理參数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<TextureRef> 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<WebGLContextAttributes*>(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<TextureLayer> 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> TextureLayer::CreateForMailbox(
TextureLayerClient* client) {
return scoped_refptr<TextureLayer>(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<Layer>* occlusion) {
bool updated = Layer::Update(queue, occlusion);
if (client_) {
TextureMailbox mailbox;
scoped_ptr<SingleReleaseCallback> 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<SingleReleaseCallback> 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::MainThreadReference>
TextureLayer::TextureMailboxHolder::Create(
const TextureMailbox& mailbox,
scoped_ptr<SingleReleaseCallback> release_callback) {
return scoped_ptr<MainThreadReference>(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<SingleReleaseCallback> 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<cc::SingleReleaseCallback>* 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<MailboxInfo> 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 <typename LayerType>
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<TextureLayerImpl*>(layer);
...... if (needs_set_mailbox_) {
TextureMailbox texture_mailbox;
scoped_ptr<SingleReleaseCallback> 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<SingleReleaseCallback> 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<LayerImpl>& 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<SingleReleaseCallback> 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<TextureDrawQuad> 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<DrawQuad>());
}
这个函数定义在文件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<cc::OutputSurface> 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<cc::OutputSurface>(
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<ContextProviderCommandBuffer>& context_provider)
: CompositorOutputSurface(routing_id,
output_surface_id,
context_provider,
scoped_ptr<cc::SoftwareOutputDevice>(),
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<cc::OutputSurface> CompositorImpl::CreateOutputSurface(
bool fallback) {
...... scoped_refptr<ContextProviderCommandBuffer> context_provider;
BrowserGpuChannelHostFactory* factory =
BrowserGpuChannelHostFactory::instance();
scoped_refptr<GpuChannelHost> 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<cc::OutputSurface>(
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<ContextProviderCommandBuffer>& context_provider,
scoped_ptr<cc::SoftwareOutputDevice> 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<cc::OutputSurface> RenderWidget::CreateOutputSurface(bool fallback) {
...... if (command_line.HasSwitch(switches::kEnableDelegatedRenderer)) {
......
return scoped_ptr<cc::OutputSurface>(
new DelegatedCompositorOutputSurface(
routing_id(),
output_surface_id,
context_provider));
}
if (!context_provider.get()) {
scoped_ptr<cc::softwareoutputdevice> software_device(
new CompositorSoftwareOutputDevice()); return scoped_ptr<cc::OutputSurface>(new CompositorOutputSurface(
routing_id(),
output_surface_id,
NULL,
software_device.Pass(),
true));
} if (command_line.HasSwitch(cc::switches::kCompositeToMailbox)) {
......
return scoped_ptr<cc::OutputSurface>(
new MailboxOutputSurface(
routing_id(),
output_surface_id,
context_provider,
scoped_ptr<cc::SoftwareOutputDevice>(),
format));
}
bool use_swap_compositor_frame_message = false;
return scoped_ptr<cc::OutputSurface>(
new CompositorOutputSurface(
routing_id(),
output_surface_id,
context_provider,
scoped_ptr<cc::SoftwareOutputDevice>(),
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<cc::CompositorFrame> 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<cc::CompositorFrame> 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<cc::CompositorFrame> 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<cc::DelegatedFrameData> 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<DelegatedRendererLayerImpl*>(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<RenderPass> 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<RenderPass>* render_passes_in_draw_order) {
...... for (size_t i = 0; i < render_passes_in_draw_order->size(); ++i) {
ScopedPtrVector<RenderPass>::iterator to_take =
render_passes_in_draw_order->begin() + i;
......
scoped_ptr<RenderPass> 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<CopyOutputRequest>::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。