标签:
在Chromium中,WebGL端、Render端和Browser端通过命令缓冲区将GPU命令发送给GPU进程执行。GPU命令携带的简单参数也通过命令缓冲区发送给GPU进程,但是复杂参数,例如纹理数据,有可能太大以致命令缓冲区无法容纳,因此需要通过其它机制传递给GPU进程。本文接下来就主要以纹理数据上传为例,分析WebGL端、Render端和Browser端将GPU命令数据传递给GPU进程的机制。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
WebGL端、Render端和Browser端将GPU命令附携带的大数据传递给GPU进程的基本思路通过其它的共享缓冲区进行传递。也就是先将GPU命令携带的大数据写入到共享缓冲区中,然后再将GPU命令携带的大数据参数修改为前面已经写入了数据的共享缓冲区的ID。GPU进程通过这个ID就可以找到对应的共享缓冲区,进而得到真正的GPU命令数据,最后就可以执行对应的OpenGL函数。
有些操作系统对能创建的共享内存的大小有限制。当一个GPU命令携带的数据的大小超过这个限制的时候,那么就不能通过一块共享缓冲区一次性将数据传递给GPU进程。这时候就需要对数据进行分块传输。有些GPU命令的数据本身就支持分块传输,这种情况的处理就比较简单。例如,对于纹理上传命令gles2::cmds::TexImage2D,可以通过gles2::cmds::TexSubImage2D命令对其携带的纹理数据进行分块传输,如图1所示:
图1 纹理数据分块上传机制
在图1中,我们假设一个gles2::cmds::TexImage2D命令要上传的纹理数据可以划分为1、2和3三个子块,每一个子块都可以通过一块共享缓冲区进行传递。这时候一个gles2::cmds::TexImage2D命令就被分拆成三个gles2::cmds::TexSubImage2D子命令,每一个gles2::cmds::TexSubImage2D子命令负责处理一个子数据块。这些gles2::cmds::TexSubImage2D子命令最终在GPU进程中转化为OpenGL函数glTexSubImage2D调用,每一个glTexSubImage2D函数都负责上传一个数据子块到GPU中。
很不幸,并不是所有的GPU命令都像gles2::cmds::TexImage2D命令一样,存在对应的子命令,例如gles2::cmds::ShaderSource命令,它不存在对应的gles2::cmds::ShaderSubSource子命令。这时候就需要使用一种称为Bucket的机制来分块上传GPU命令数据。以gles2::cmds::ShaderSource命令为例,它携带的Shader源代码的分块上传机制如图2所示:
图2 Shader源代码分块上传机制
在图2中,我们同样假设gles2::cmds::ShaderSource命令要上传的数据可以划分为1、2和3三个子块,每一个子都可以通过一块共享缓冲区进行传递。每一个数据子块都是通过一个gles2::cmds::SetBucketData命令保存在GPU进程中的同一个Bucket中的。每一个Bucket都具有一个ID,前面已经准备好了数据的Bucket的ID接下来再通过一个gles2::cmds::ShaderSourceBucket命令传递给GPU进程。GPU进程有了这个Bucket的ID之后,就可以获得它里面的数据,进而可以调用OpenGL函数glShaderSource,从而完成对gles2::cmds::ShaderSource命令的处理。
不难发现,上面描述的两种GPU命令数据分块上传机制都是通过共享缓冲区进行的,这就涉及到这些共享缓冲区的管理问题,也就是分配和释放的问题。为了更好地认识这个问题,我们首先简单介绍一下Chromium的纹理上传机制。Chromium提供了同步和异步纹理上传机制。
我们知道,在Chromium中,所有的GPU命令都是在GPU进程的一个线程中执行的,这个线程称为GPU主线程。将纹理上传命令全部交给GPU主线程执行就称为同步纹理上传,如图3所示:
图3 同步纹理上传
在图3中,我们假设一个命令缓冲区有五个GPU命令需要执行,其中第一个GPU命令是同步纹理上传命令gles2::cmds::TexImage2D,对应的OpenGL函数是glTexImage2D。由于使用的是同步纹理上传方式,因此,在纹理上传命令执行完成之前,后面的四个命令是不能执行的。
与GPU的图形计算和渲染速度相比,将数据从CPU传递到GPU的速度是相当慢的。这就使得纹理上传操作是GPU的一个瓶颈,特别是数据量很大的纹理。对于图3来说,就会造成后面的四个命令需要等待比较长时间才会被执行。
为了解决纹理上传速度慢的问题,Chromium提供了另外一种纹理上传方式——异步纹理上专,如图4所示:
图4 异步纹理上传
在异步纹理上传方式中,GPU进程中使用一个专门的线程用作纹理上传,这个线程称为GPU传输线程。在图4中,我们同样假设一个命令缓冲区有五个GPU命令需要执行,其中第一个GPU命令是异步纹理上传命令gles2::cmds::AsyncTexImage2DCHROMIUM。这个异步纹理上传命令被GPU主线程发送给GPU传输线程处理。GPU传输线程调用OpenGL函数执行异步纹理上传命令。与此同时,GPU主线程也在执行后面的四个命令。
GPU传输线程上传纹理完毕,会将通过EGL函数eglCreateImageKHR将已经上传的纹理封装成一个EGLImageKHR对象。GPU主线程会在空闲的时候检查异步上传的纹理是否已经上传完成。对于已经上传完成的纹理,GPU主线程会通过OpenGL函数glEGLImageTargetTexture2DOES将绑定在当前激活的OpenGL上下文中。这意味着GPU主线程可以访问在GPU传输线程中上传完毕的纹理。这是一种跨线程的纹理共享机制。正是由于这个纹理共享机制,才使得异步纹理上传成为可能。
有时候,GPU进程的Client端需要知道一个异步纹理上传命令什么时候执行完成,这时候它可以向GPU进程发送一个gles2::cmds::WaitAsyncTexImage2DCHROMIUM。GPU主线程在处理gles2::cmds::WaitAsyncTexImage2DCHROMIUM的时候,就会等待GPU传输线程完成纹理上传。当然,如果这时候GPU传输线程已经完成纹理上传,那么GPU主线程就不用等待。GPU主线程结束等待之后,也会检查已经上传完成的纹理是否已经绑定在当前激活的OpenGL上下文中。如果还没有绑定,那么也会通过OpenGL函数glEGLImageTargetTexture2DOES将绑定在当前激活的OpenGL上下文中。
从图4我们就可以看到,通过异步纹理上传方式,后面的四个命令可以可以与前面的纹理上传命令并发执行,从而提高了全部五个命令的执行时间,从而可以在一定程度上解决纹理上传速度慢的问题。
GPU进程的Client端向GPU进程发送的同步纹理上传命令gles2::cmds::TexImage2D和异步纹理上传命令gles2::cmds::AsyncTexImage2DCHROMIUM,都指定了一个共享缓冲区,这个共享缓冲区保存了要上传的纹理数据。GPU进程的Client端不知道这个共享缓冲区什么时候释放,因为它不知道GPU进程什么时候使用完成这个共享缓冲区,也就是不知道纹理上传命令什么时候被执行。但是GPU进程的Client端必须知道上述共享缓冲区什么时候使用完成,以便可以对它进行回收。那么GPU进程的Client端是通过什么方式知道一个共享缓冲区什么时候不再被GPU进程使用的呢?我们分同步纹理上传和异步纹理上传两种情况讨论。
在同步纹理上传方式中,GPU主线程执行完成一个gles2::cmds::TexImage2D命令,就意味着该gles2::cmds::TexImage2D命令引用的共享缓冲区已经使用完毕。GPU进程的Client端往命令缓冲区写入一个gles2::cmds::TexImage2D命令之后,会接着在后面再写入一个gpu::cmd::SetToken命令,如图5所示:
图5 同步Token机制
上述gpu::cmd::SetToken命令关联了一个同步Token值。这个同步Token值由GPU进程的Client端维护,并且与前面被gles2::cmds::TexImage2D命令引用的共享缓冲区对应。GPU进程的Client每往命令缓冲区写入一个gpu::cmd::SetToken命令,都会将其维护的同步Token值增加1,作为下一个gpu::cmd::SetToken命令的同步Token值。
GPU主线程处理完成一个gpu::cmd::SetToken命令之后,会在当前激活的OpenGL上下文中记录该gpu::cmd::SetToken命令关联的同步Token值。这样,GPU进程的Client端通过比较一个共享缓冲区的同步Token值与该Client端在GPU进程中对应的OpenGL上下文记录的当前同步Token值的大小,就可以知道该共享缓冲区是否可以进行回收。
在异步纹理上传方式中,通过上述的同步Token机制,不能确定一个共享缓冲区是否能够进行回收,因为紧跟在异步纹理上传命令后面的gpu::cmd::SetToken命令有可能比异步纹理上传命令本身要提前执行完成。这时候需要使用另外一种异步Token机制,如图6所示:
图6 异步Token机制
异步纹理上传命令不仅gles2::cmds::AsyncTexImage2DCHROMIUM不仅指定了一个共享缓冲区,还指定了一个异步Token值。这个异步Token值同样也是由GPU进程的Client端维护的,并且与异步纹理上传命令指定的共享缓冲区相关联。GPU传输线程执行完成一个异步纹理上传命令之后,会通过函数SetAsyncUploadToken将该异步纹理上传命令指定的异步Token值设置到当前激活的OpenGL上下文中去。这样,GPU进程的Client端通过比较一个共享缓冲区的异步Token值与该Client端在GPU进程中对应的OpenGL上下文记录的当前异步Token值的大小,就可以知道该共享缓冲区是否可以进行回收。
通过上述的同步和异步Token机制,GPU的Client端就可以对那些用来传递数据的共享缓冲区进行管理了。接下来我们就首先分析这些共享缓冲区的管理。
在前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文中提到,GPU进程的Client端在初始化OpenGL上下文的过程中,会创建和初始化一个TransferBuffer对象和一个BufferTracker对象,如下所示:
bool GLES2Implementation::Initialize( unsigned int starting_transfer_buffer_size, unsigned int min_transfer_buffer_size, unsigned int max_transfer_buffer_size, unsigned int mapped_memory_limit) { ...... if (!transfer_buffer_->Initialize( starting_transfer_buffer_size, kStartingOffset, min_transfer_buffer_size, max_transfer_buffer_size, kAlignment, kSizeToFlush)) { return false; } mapped_memory_.reset( new MappedMemoryManager( helper_, base::Bind(&GLES2Implementation::PollAsyncUploads, // The mapped memory manager is owned by |this| here, and // since its destroyed before before we destroy ourselves // we don‘t need extra safety measures for this closure. base::Unretained(this)), mapped_memory_limit)); ...... buffer_tracker_.reset(new BufferTracker(mapped_memory_.get())); ...... return true; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员变量transfer_buffer_指向的是一个TransferBuffer对象,GLES2Implementation类的成员函数Initialize调用它的成员函数Initialize对它进行初始化。这个TransferBuffer对象初始化完成后,GPU进程的Client端就可以通过它分配一些共享缓存区,用来和GPU进程传递数据。
GLES2Implementation类的成员函数Initialize接下来创建了一个MappedMemoryManager对象,并且以这个MappedMemoryManager对象为参数,创建了一个BufferTracker对象,保存在成员变量buffer_trakcer_中。以后GPU进程的Client端也可以通过这个BufferTracker分配共享缓冲区,用来和GPU进程传递数据。
接下来,我们就继续分析TransferBuffer类和BufferTracker类的实现,以便了解它们是如何管理共享缓冲区的。
我们从TransferBuffer类的成员函数Initialize开始分析TransferBuffer类的实现,如下所示:
bool TransferBuffer::Initialize( unsigned int default_buffer_size, unsigned int result_size, unsigned int min_buffer_size, unsigned int max_buffer_size, unsigned int alignment, unsigned int size_to_flush) { result_size_ = result_size; default_buffer_size_ = default_buffer_size; min_buffer_size_ = min_buffer_size; max_buffer_size_ = max_buffer_size; alignment_ = alignment; size_to_flush_ = size_to_flush; ReallocateRingBuffer(default_buffer_size_ - result_size); return HaveBuffer(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
各个参数的含义如下所示:
default_buffer_size:表示默认分配的共享缓冲区的大小。
result_size:当TransferBuffer用来将数据从GPU进程返回给GPU进程的Client端时,分配出来的缓冲区的头部用来填写返回值。这个头部的大小就由参数result_size描述。
min_buffer_size:表示允许分配的共享缓冲区的最小值。
max_buffer_size:表示允许分配的共享缓冲区的最大值。
alignment:分配出来的共享缓冲区被划分为一个个的子缓冲区进行使用,这些子缓冲区的大小要对齐到参数alignment描述的值。
size_to_flush:当从共享缓冲区分配出去的子缓冲区的大小达到参数size_to_flush描述的值后,GPU进程的Client端就会请求GPU进程执行命令缓冲区的命令,以便可以回收这些命令引用的子缓冲区。
TransferBuffer类的成员函数Initialize将上述参数分别保存在对应的成员变量中后,就调用另外一个成员函数ReallocateRingBuffer分配共享缓冲区,如下所示:
void TransferBuffer::ReallocateRingBuffer(unsigned int size) { // What size buffer would we ask for if we needed a new one? unsigned int needed_buffer_size = ComputePOTSize(size + result_size_); needed_buffer_size = std::max(needed_buffer_size, min_buffer_size_); needed_buffer_size = std::max(needed_buffer_size, default_buffer_size_); needed_buffer_size = std::min(needed_buffer_size, max_buffer_size_); if (usable_ && (!HaveBuffer() || needed_buffer_size > buffer_->size())) { if (HaveBuffer()) { Free(); } AllocateRingBuffer(needed_buffer_size); } }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
TransferBuffer类的成员函数ReallocateRingBuffer首先根据参数size和成员变量result_size_、min_buffer_size_和max_buffer_size_计算出应该分配的共享缓冲区的大小needed_buffer_size。
TransferBuffer类的成员变量usable_ 的值初始化为true,接下来如果因为大小限制不能成功分配到共享缓冲区,那么该成员变量的值就会被设置为false,
当TransferBuffer类的成员变量buffer_的值不等于NULL时,它指向的是一个Buffer对象,该Buffer对象描述的就是一个共享缓冲区,这时候调用TransferBuffer类的成员函数HaveBuffer得到的返回值就为true,并且调用该Buffer对象的成员函数size可以获得的它描述的共享缓冲区的大小。
因此,TransferBuffer类的成员函数ReallocateRingBuffer所做的事情就是判断要求分配的共享缓冲区的大小是否合适。如果合适,并且之前还没有分配过共享缓冲区,或者之前已经分配过,但是分配的大小小于前面计算出来的应该分配的大小,那么就需要重新分配一块大小等于needed_buffer_size的共享缓冲区。当然,如果之前已经分配过共享缓冲区,那么这块共享缓冲区会首先被释放掉,这是通过调用TransferBuffer类的成员函数Free实现的。
最后,TransferBuffer类的成员函数ReallocateRingBuffer通过调用另外一个成员函数AllocateRingBuffer分配一块大小等于needed_buffer_size的共享缓冲区,如下所示:
void TransferBuffer::AllocateRingBuffer(unsigned int size) { for (;size >= min_buffer_size_; size /= 2) { int32 id = -1; scoped_refptr<gpu::Buffer> buffer = helper_->command_buffer()->CreateTransferBuffer(size, &id); if (id != -1) { DCHECK(buffer); buffer_ = buffer; ring_buffer_.reset(new RingBuffer( alignment_, result_size_, buffer_->size() - result_size_, helper_, static_cast<char*>(buffer_->memory()) + result_size_)); buffer_id_ = id; result_buffer_ = buffer_->memory(); result_shm_offset_ = 0; return; } // we failed so don‘t try larger than this. max_buffer_size_ = size / 2; } usable_ = false; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
TransferBuffer类的成员函数AllocateRingBuffer在保证要分配的共享缓冲区的大小size不小于允许的最小值min_buffer_size_的前提下,分配一个大小等于size的共享缓冲区。
如果能成功分配,那么分配出来的共享缓冲区使用一个Buffer对象来描述。这个Buffer对象保存在TransferBuffer类的成员变量buffer_中。并且分配出的共享缓冲区的ID保存在TransferBuffer类的成员变量buffer_id_中。前面提到,分配出来的共享缓冲区的头部用来保存从GPU进程读取数据时的结果,因此这个头部的地址就等于分配出来的共享缓冲区的起始地址,保存在TransferBuffer类的成员变量result_buffer_中。同时,TransferBuffer类的成员变量result_shm_offset_表示上述用来保存结果的头部位于分配出来的共享缓冲区的偏移位置,它的值被设置为0。
如果不能成功分配,那么就可能是请求分配的大小太大了,于就是尝试减少一半的大小,即以(size / 2)的大小,再次尝试分配。这时候也需要相应地调整允许分配的共享缓冲区的最大值max_buffer_size_,也就是允许分配的共享缓冲区的最大值max_buffer_size_就等于(size / 2)。这个过程一直持续下去,直到分配成功,或者请求分配的大小小于允许的最小值min_buffer_size_为止。如果是后一种情况,那么TransferBuffer类的成员变量usable_的值就会被设置为false,表示请求分配的共享缓冲区大小不合适,导致不能成功到一块共享缓冲区。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,TransferBuffer类的成员变量helper_指向的是一个GLES2CmdHelper对象,调用这个GLES2CmdHelper对象的成员函数command_buffer可以获得一个CommandBufferProxyImpl对象。有了这个CommandBufferProxyImpl对象之后,就可以调用它的成员函数CreateTransferBuffer创建一个能够与GPU进程进行共享的缓冲区,并且这个缓冲区会注册在GPU进程中。
通过CommandBufferProxyImpl类的成员函数CreateTransferBuffer分配的共享缓冲区都有一个相应的ID,以后GPU进程的Client端通过一个ID值就可以告诉GPU进程它是通过哪一个共享缓冲区来传递数据的。关于CommandBufferProxyImpl类的成员函数CreateTransferBuffer的实现,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。
前面提到,分配出来的共享缓冲区是划分成一个个子缓冲区使用的。这些子缓冲区通过一个RingBuffer对象来管理。这个RingBuffer对象保存在TransferBuffer类的成员变量ring_buffer_中。也就是说,以后我们想在上述共享缓冲区拿出一小块来传递数据给GPU进程或者从GPU进程读回数据时,就可以通过TransferBuffer类的成员变量ring_buffer_描述的RingBuffer对象分配这一小块缓冲区,并且在使用完毕时,将这一小块缓冲区重新交给它管理。
GPU进程的Client端可以通过调用TransferBuffer类的成员函数AllocUpTo或者Alloc从它的成员变量ring_buffer_描述的共享缓冲区中分配一小块指定大小的缓冲区,接下来我们就分别分析它们的实现。
TransferBuffer类的成员函数AllocUpTo的实现如下所示:
void* TransferBuffer::AllocUpTo( unsigned int size, unsigned int* size_allocated) { ...... unsigned int max_size = ring_buffer_->GetLargestFreeOrPendingSize(); *size_allocated = std::min(max_size, size); bytes_since_last_flush_ += *size_allocated; return ring_buffer_->Alloc(*size_allocated); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
TransferBuffer类的成员函数AllocUpTo调用成员变量ring_buffer_指向的一个RingBuffer对象的成员函数GetLargestFreeOrPendingSize可以获得该RingBuffer对象描述的共享缓冲区可分配的子缓冲区的最大值max_size,即空闲部分的大小。这时候如果请求分配的大小size大于可分配的最大值max_size,那么实际分配的大小就会调整为可分配的最大值max_size,并且记录在输出参数size_allocated中。
确定了实际要分配的子缓冲区的大小之后,就会调用TransferBuffer类的成员函数AllocUpTo就会调用ring_buffer_指向的RingBuffer对象的成员函数Alloc进行分配。在分配之前,也会相应地增加TransferBuffer类的成员变量bytes_since_last_flush_的值,这个成员变量描述的是自从上次向GPU进程提交新的GPU命令以来,从成员变量ring_buffer_指向的RingBuffer对象描述的共享缓冲区分配出去的子缓冲区的大小。以后会通过这个值来决定是否要向GPU进程提交新的GPU命令。
TransferBuffer类的成员函数Alloc的实现如下所示:
void* TransferBuffer::Alloc(unsigned int size) { ...... unsigned int max_size = ring_buffer_->GetLargestFreeOrPendingSize(); if (size > max_size) { return NULL; } bytes_since_last_flush_ += size; return ring_buffer_->Alloc(size); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
与前面分析的TransferBuffer类的成员函数AllocUpTo不同,TransferBuffer类的成员函数Alloc如果发现请求分配的子缓冲区的大小size大于可分配的最大值max_size,那么就会导致分配失败。
另一方面,如果请求分配的子缓冲区的大小size小于等于可分配的最大值max_size,那么TransferBuffer类的成员函数Alloc也是通过调用ring_buffer_指向的RingBuffer对象的成员函数Alloc来分配子缓冲区。
RingBuffer类的成员函数Alloc的实现如下所示:
void* RingBuffer::Alloc(unsigned int size) { ...... // Similarly to malloc, an allocation of 0 allocates at least 1 byte, to // return different pointers every time. if (size == 0) size = 1; // Allocate rounded to alignment size so that the offsets are always // memory-aligned. size = RoundToAlignment(size); // Wait until there is enough room. while (size > GetLargestFreeSizeNoWaiting()) { FreeOldestBlock(); } if (size + free_offset_ > size_) { // Add padding to fill space before wrapping around blocks_.push_back(Block(free_offset_, size_ - free_offset_, PADDING)); free_offset_ = 0; } Offset offset = free_offset_; blocks_.push_back(Block(offset, size, IN_USE)); free_offset_ += size; if (free_offset_ == size_) { free_offset_ = 0; } return GetPointer(offset + base_offset_); }
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/ring_buffer.cc中。
我们通过图7来理解RingBuffer类的成员函数Alloc的实现,如下所示:
图7 子缓冲区分配过程
整个共享缓冲区的大小为size_,请求分配的子缓冲区的大小为size。当前正在使用的所有子缓冲在地址空间上是连续的。这块连续的地址空间的起始地址为in_use_offset_中。当前空闲的缓冲区可能分为两部分。一部分位于共享缓冲区的头部,另一部分位于尾部。其中,位于尾部的空闲缓冲区紧跟在正在使用的缓冲区的结束位置上。这个位置记录在free_offset_中。
当前正在使用的每一个子缓冲区,也就是分配出去的子缓冲区,都关联有一个Token值,并且它们处于两种状态之一。一种状态是FREE_PENDING_TOKEN,另一种状态是IN_USE。IN_USE是指一个子缓冲区正在被GPU进程的Client端使用,同时也被GPU命令缓冲区引用。FREE_PENDING_TOKEN是指一个子缓冲区不被GPU进程的Client端使用,但是被GPU命令缓冲区引用。
例如,GPU进程的Client端请求GPU进程执行一个纹理上传操作,上传的纹理数据要拷贝到一个子缓冲区去。在拷贝的过程中,这个子缓冲区的状态为IN_USE。拷贝完毕,GPU进程的Client端往GPU命令缓冲区写入到一个gles2::cmds::TexImage2D命令,该命令引用了上述子缓冲区。这时候GPU进程的Client端就将保存了纹理数据的子缓冲区的状态设置为FREE_PENDING_TOKEN,并且在命令缓冲区中写入一个gpu::cmd::SetToken命令,表示虽然GPU进程的Client端不需要使用该子缓冲区了,但是GPU命令缓冲区仍然引用着它。
接下来,GPU进程先后从GPU命令缓冲区将前面写入的gles2::cmds::TexImage2D命令和gpu::cmd::SetToken命令读取出来处理。处理完成gles2::cmds::TexImage2D命令的时候,就意味着该gles2::cmds::TexImage2D命令引用的子缓冲区使用完毕,但是GPU进程的Client端并不知道。处理完成gpu::cmd::SetToken命令的时候,GPU进程将该gpu::cmd::SetToken命令关联的Token值记录为当前激活的OpenGL上下文的Token值。
GPU进程的Client端发现空闲缓冲区大小不足时,就会获得GPU进程为它创建的OpenGL上下文的当前Token值,保存在last_read_token中。如果一个处于FREE_PENDING_TOKEN状态的子缓冲关联的token值小于等于这个last_read_token值,那么就说明这个子缓冲也不再被GPU命令缓冲区引用了,这时候它的状态就可以修改为FREE。这
有了上面的背景的知识之后,我们就通过图7来分析RingBuffer类的成员函数Alloc。假设这时候共享缓冲区的状态如A所示。在A中,共享缓冲区连续的可分配的空闲缓冲区大小小于请求分配的子缓冲区的大小size。这时候RingBuffer类的成员函数Alloc就调用成员函数GetLargestFreeSizeNoWaiting回收那些处于FREE_PENDING_TOKEN状态的、并须Token值小于等于last_read_token的子缓冲区。注意,这些子缓冲区是可以马上回收的,不需要等待。
回收了处于FREE_PENDING_TOKEN状态的、并须Token值小于等于last_read_token的子缓冲区之后,假设共享缓冲区的状态如B所示。这时候共享缓冲区连续的可分配的空闲缓冲区大小仍然小于请求分配的子缓冲区的大小size。于是RingBuffer类的成员函数Alloc就调用成员函数FreeOldestBlock回收最早分配出去的子缓冲区。注意,这个子缓冲区的状态有可能是处于FREE_PENDING_TOKEN状态的,但是它的Token值大于于last_read_token,也有可能是处于IN_USE状态。无论是哪一种,回收它都需要进行等待,也就是要等待GPU进程处理完毕GPU命令缓冲区中引了它的命令。
上述过程持续进行,直到共享缓冲区的状态如C所示。这时候共享缓冲区连续的可分配的空闲缓冲区大小大于等于请求分配的子缓冲区的大小size。 但是这部分空闲缓冲区可能是位于共享缓冲区头部的。这意味着尾部的空闲缓冲区由于大小不足,必须要跳过。在跳过之前,它的状态被设置为PADDING。状态为PADDING的子缓冲被当作是已经被分配出去的,但是没有实际使用。等到它前面处于IN_USE状态的子缓冲区被回收后,它们就可以合在一起重新被使用。
跳过尾部大小不足的空闲缓冲区之后,共享缓冲区的状态如D所示。这时候free_offset_被设置为0,表示要从共享缓冲区的起始位置开始分配子缓冲区。
以上就是RingBuffer类的成员函数Alloc的实现逻辑。还有两点需要注意:
1. 请求分配的子缓冲区的大小至少为1个字节,并且需要对齐到前面分析TransferBuffer类的成员函数Initialize时提到的参数alignment的值。
2. 每一个分配出去的子缓冲区都使用一个Block对象描述,并且保存在RingBuffer类的成员变量blocks_描述的一个std::deque中。
从上面的分析我们就可以知道,通过RingBuffer类分配的子缓冲区的主要状态变迁路径为:FREE=>IN_USE=>FREE_PENDING_TOKEN=>FREE。
接下来,我们继续分析RingBuffer类的成员函数FreeOldestBlock的实现,以便了解它是如何回收一个分配出去的子缓冲区的,如下所示:
void RingBuffer::FreeOldestBlock() { DCHECK(!blocks_.empty()) << "no free blocks"; Block& block = blocks_.front(); DCHECK(block.state != IN_USE) << "attempt to allocate more than maximum memory"; if (block.state == FREE_PENDING_TOKEN) { helper_->WaitForToken(block.token); } in_use_offset_ += block.size; if (in_use_offset_ == size_) { in_use_offset_ = 0; } // If they match then the entire buffer is free. if (in_use_offset_ == free_offset_) { in_use_offset_ = 0; free_offset_ = 0; } blocks_.pop_front(); }
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/ring_buffer.cc中。
前面提到,所有已经分配出去的子缓冲区保存在RingBuffer类的成员变量blocks_描述的一个std::deque中。其中,最早分配出去的子缓冲区保存这个std::deque的头部。在调用RingBuffer类的成员函数FreeOldestBlock的时候,必须要保证头部的子缓冲区不是处于IN_USE状态的。如果不是处于IN_USE状态,那么根据前面的分析,就是处于PADDING或者FREE_PENDING_TOKEN状态。处于PADDING状态的子缓冲区没有实际使用,因此可以直接回收。但是处于FREE_PENDING_TOKEN状态的子缓冲区,正在被GPU命令缓冲区引用,因此需要进行等待。等待结束后,就重新设置共享缓冲区的状态,即相应地调整in_use_offset_、free_offset_的值,以及将位于头部的子缓冲区从成员变量blocks_描述的一个std::deque移除。
等待GPU命令缓冲区使用完毕一个子缓冲区是通过调用RingBuffer类的成员变量helper_描述的一个GLES2CmdHelper对象的成员函数WaitForToken实现的。GLES2CmdHelper类的成员函数WaitForToken是从父类CommandBufferHelper继承下来的,因此接下来我们分析CommandBufferHelper类的成员函数WaitForToken的实现,如下所示:
void CommandBufferHelper::WaitForToken(int32 token) { ...... if (token > token_) return; // we wrapped if (last_token_read() >= token) return; Flush(); command_buffer_->WaitForTokenInRange(token, token_); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/cmd_buffer_helper.cc中。
后面我们会看到,参数token描述的Token值是通过CommandBufferHelper类的成员函数InsertToken分配出来的。CommandBufferHelper类的成员函数InsertToken每次被调用时,都会将成员变量token_的值加1,然后将得到的结果返回给调用者。CommandBufferHelper类的成员变量token_是一个int32值,它不能无限增加。当增加到最大值0x7FFFFFFF时,就需要重置为0,然后开始新一轮的递增。当CommandBufferHelper类的成员变量token_被重置为0的时候,CommandBufferHelper类的成员函数InsertToken会请求GPU进程处理GPU命令缓冲区的所有新写入的命令,以便保证前面写入的所有gpu::cmd::SetToken命令都已经处理完毕。这样,当参数token的值大于CommandBufferHelper类的成员变量token_的时候,就意味着CommandBufferHelper类的成员函数WaitForToken不需要等待,因为这时候可以确保参数token关联的子缓冲区已经不再被GPU命令缓冲区引用了。
当参数token的值小于等于CommandBufferHelper类的成员变量token_的时候,CommandBufferHelper类的成员函数WaitForToken调用成员函数last_token_read获得GPU进程为当前OpenGL上下文记录的Token值。如果这个Token值大于等于参数token的值,那么就说明CommandBufferHelper类的成员函数WaitForToken不需要等待,因为条件已经满足。否则的话,接下来就会调用另外一个成员函数Flush请求GPU进程执行GPU命令缓冲区的命令。关于CommandBufferHelper类的成员函数Flush的实现,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。
请求了GPU进程执行执行GPU命令缓冲区的命令之后,CommandBufferHelper类的成员函数WaitForToken调用成员变量command_buffer_描述的一个CommandBufferProxyImpl对象的成员函数WaitForTokenInRange等待GPU进程处理GPU命令缓冲区的命令,直到处理到一个Token值设置为token的gpu::cmd::SetToken命令。
CommandBufferProxyImpl类的成员函数WaitForTokenInRange的实现如下所示:
void CommandBufferProxyImpl::WaitForTokenInRange(int32 start, int32 end) { ...... TryUpdateState(); if (!InRange(start, end, last_state_.token) && last_state_.error == gpu::error::kNoError) { gpu::CommandBuffer::State state; if (Send(new GpuCommandBufferMsg_WaitForTokenInRange( route_id_, start, end, &state))) OnUpdateState(state); } ...... }这个函数定义在文件external/chromium_org/content/common/gpu/client/command_buffer_proxy_impl.cc中。
CommandBufferProxyImpl类的成员函数WaitForTokenInRange首先调用另外一个成员函数TryUpdateState获得GPU进程的状态。获得的状态信息记录在CommandBufferProxyImpl类的成员变量last_state_描述的一个State对象。这个State对象的成员变量token记录了GPU进程为当前OpenGL上下文记录的Token值。如果这个Token值处于参数start和end描述的范围中,那么CommandBufferProxyImpl类的成员函数WaitForTokenInRange就不用等待了。否则的话,CommandBufferProxyImpl类的成员函数WaitForTokenInRange就会向GPU进程发送一个类型为GpuCommandBufferMsg_WaitForTokenInRange的同步IPC消息,等待GPU进程为当前OpenGL上下文记录的Token值处于参数start和end描述的范围中。
类型为GpuCommandBufferMsg_WaitForTokenInRange的IPC消息是由与当前正处理的CommandBufferProxyImpl对象对应的一个运行在GPU进程中的GpuCommandBufferStub对象的成员函数OnMessageReceived接收的,如下所示:
bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) { ...... bool handled = true; IPC_BEGIN_MESSAGE_MAP(GpuCommandBufferStub, message) ...... IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_WaitForTokenInRange, OnWaitForTokenInRange); ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() CheckCompleteWaits(); ...... return handled; }这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
从这里可以看到,GpuCommandBufferStub类的成员函数OnMessageReceived将类型为GpuCommandBufferMsg_WaitForTokenInRange的IPC消息分发给成员函数OnWaitForTokenInRange处理。
GpuCommandBufferStub类的成员函数OnWaitForTokenInRange的实现如下所示:
void GpuCommandBufferStub::OnWaitForTokenInRange(int32 start, int32 end, IPC::Message* reply_message) { ...... wait_for_token_ = make_scoped_ptr(new WaitForCommandState(start, end, reply_message)); CheckCompleteWaits(); }这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
GpuCommandBufferStub类的成员函数OnWaitForTokenInRange首先将参数start、end和reply_message封装在一个WaitForCommandState对象中,并且将该WaitForCommandState对象保存在成员变量wait_for_token_中,接着调用另外一个成员函数CheckCompleteWaits检查GPU进程是否已经处理了GPU命令缓冲区中的一个Token值介于start和end之间的gpu::cmd::SetToken命令。
GpuCommandBufferStub类的成员函数CheckCompleteWaits的实现如下所示:
void GpuCommandBufferStub::CheckCompleteWaits() { if (wait_for_token_ || wait_for_get_offset_) { gpu::CommandBuffer::State state = command_buffer_->GetLastState(); if (wait_for_token_ && (gpu::CommandBuffer::InRange( wait_for_token_->start, wait_for_token_->end, state.token) || state.error != gpu::error::kNoError)) { ReportState(); GpuCommandBufferMsg_WaitForTokenInRange::WriteReplyParams( wait_for_token_->reply.get(), state); Send(wait_for_token_->reply.release()); wait_for_token_.reset(); } ...... } }这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
在前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文中,我们分析过GpuCommandBufferStub类的成员函数CheckCompleteWaits是如何处理成员变量wait_for_get_offset_不等于NULL的情况的。当GpuCommandBufferStub类的成员变量wait_for_get_offset_不等于NULL的时候,它指向的也是一个WaitForCommandState对象,表示GPU进程的一个Client端正在等待GPU进程处理新提交的GPU命令,以便在GPU命令缓冲区中腾出更多的空闲空间来。
GpuCommandBufferStub类的成员函数CheckCompleteWaits处理成员变量wait_for_token_不等于NULL的情况也是类似的,它首先调用成员变量command_buffer_指向的一个CommandBufferService对象的成员函数GetLastState获得GPU命令缓冲区的处理状态信息。获得的状态信息封装在一个State对象中,这个State对象的成员变量token记录的就是最后一个处理的gpu::cmd::SetToken命令设置的Token值。如果这个Token值介于成员变量wait_for_token_描述的WaitForCommandState对象指定的范围,那么就可以向正在等待的Client端发送一个IPC消息,作为该Client端之前发送过来的类型为GpuCommandBufferMsg_WaitForTokenInRange的IPC消息的回复。Client端收到这个回复消息之后,就可以结束等待了。
以上我们分析的就是通过TransferBuffer类的成员函数AllocUpTo和Alloc从一个共享缓冲区中分配子缓冲区的过程,接下来我们继续分析通过TransferBuffer类的成员函数FreePendingToken释放子缓冲区的过程,它的实现如下所示:
void TransferBuffer::FreePendingToken(void* p, unsigned int token) { ring_buffer_->FreePendingToken(p, token); if (bytes_since_last_flush_ >= size_to_flush_ && size_to_flush_ > 0) { helper_->Flush(); bytes_since_last_flush_ = 0; } }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
TransferBuffer类的成员函数FreePendingToken首先调用成员变量ring_buffer_指向的一个RingBuffer对象的成员函数FreePendingToken释放参数p描述的子缓冲区,并且给该子缓冲区关联一个Token值。
TransferBuffer类的成员函数FreePendingToken接下来判断从上次请求GPU进程处理GPU命令缓冲区的命令以来,又分配出去的子缓冲区的字节数bytes_since_last_flush_是否已经超出预先设定的阀值size_to_flush_。如果超出的话,就再次调用成员变量helper_指向的一个GLES2CmdHelper对象的成员函数Flush请求GPU进程处理GPU命令缓冲区的新命令,并且将成员变量tes_since_last_flush_的值重置为0。
接下来,我们继续分析RingBuffer类的成员函数FreePendingToken的实现,以便可以了解子缓冲区释放的过程,如下所示:
void RingBuffer::FreePendingToken(void* pointer, unsigned int token) { Offset offset = GetOffset(pointer); offset -= base_offset_; ...... for (Container::reverse_iterator it = blocks_.rbegin(); it != blocks_.rend(); ++it) { Block& block = *it; if (block.offset == offset) { ...... block.token = token; block.state = FREE_PENDING_TOKEN; return; } } ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/ring_buffer.cc中。
RingBuffer类的成员函数FreePendingToken首先找到参数pointer描述的子缓冲区在成员变量base_offset_描述的共享缓冲区中的偏移位置,然后根据这个偏移位置从成员变量blocks_描述的一个std::deque中找到一个对应的Block对象,最后将参数token描述的Token值设置给前面找到的Block对象,并且将该Block对象的状态设置为FREE_PENDING_TOKEN。这相当于是将一个子缓冲区从状态IN_USE修改为FREE_PENDING_TOKEN,这也意味着该子缓冲区还没有真正释放掉,因为这时候它可能还被GPU命令缓冲区引用。
以上我们分析的就是通过TransferBuffer类的成员函数FreePendingToken释放一个从共享缓冲区中分配出来的子缓冲区的过程。为了方便GPU进程的Client端释放一个子缓冲区时,自动往GPU命令缓冲区插入一个gpu::cmd::SetToken命令,以及关联一个Token值,Chromium提供了一个工具类ScopedTransferBufferPtr,用来分配和释放子缓冲区。
当我们创建一个ScopedTransferBufferPtr对象时,会通过它的构造函数自动从一个共享缓冲区中分配一个子缓冲区,如下所示:
class GPU_EXPORT ScopedTransferBufferPtr { public: ScopedTransferBufferPtr( unsigned int size, CommandBufferHelper* helper, TransferBufferInterface* transfer_buffer) : buffer_(NULL), size_(0), helper_(helper), transfer_buffer_(transfer_buffer) { Reset(size); } ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.h中。
参数transfer_buffer指向的是一个TransferBuffer对象,该TransferBuffer对象内部创建有一块共享缓冲区,参数size表示要从上述共享缓冲区中分配的子缓冲区的大小,另外一个参数helper指向的是一个GLES2CmdHelper对象,用来往GPU命令缓冲区插入gpu::cmd::SetToken命令。
ScopedTransferBufferPtr类的构造函数调用另外一个成员函数Reset从参数transfer_buffer指向的TransferBuffer对象中分配一个子缓冲区,它的实现如下所示:
void ScopedTransferBufferPtr::Reset(unsigned int new_size) { Release(); buffer_ = transfer_buffer_->AllocUpTo(new_size, &size_); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
ScopedTransferBufferPtr类的成员变量Reset调用成员变量transfer_buffer描述的一个TransferBuffer对象的成员函数AllocUpTo分配一块大小为new_size的子缓冲区,分配出来的子缓冲区用一个Buffer对象描述,该Buffer对象保存在成员变量buffer_中。
注意,当前正在处理的ScopedTransferBufferPtr有可能之前已经分配过子缓冲区,这时候在分配新的子缓冲区之前,需要调用另外一个成员函数Release释放旧的子缓冲区。后面我们再分析ScopedTransferBufferPtr类的成员函数Release的实现。
当一个ScopedTransferBufferPtr对象超出其生命周期范围时,会通过它的析构函数自动释放之前分配的子缓冲区,如下所示:
class GPU_EXPORT ScopedTransferBufferPtr { public: ...... ~ScopedTransferBufferPtr() { Release(); } ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.h中。
ScopedTransferBufferPtr类的析构函数调用另外一个成员函数Release释放之前通过构造函数分配的子缓冲区,如下所示:
void ScopedTransferBufferPtr::Release() { if (buffer_) { transfer_buffer_->FreePendingToken(buffer_, helper_->InsertToken()); buffer_ = NULL; size_ = 0; } }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/transfer_buffer.cc中。
当成员变量buffer_不等于NULL的时候,就说明当前正在处理的ScopedTransferBufferPtr对象之前从成员变量transfer_buffer_描述的一个TransferBuffer对象中分配过子缓冲区,因此这时候就需要调用该TransferBuffer对象的成员函数FreePendingToken释放该子缓冲区。在释放之前,还会调用成员变量helper_指向的一个GLES2CmdHelper对象的成员函数InsertToken往GPU命令缓冲区写入一个gpu::cmd::SetToken命令。等到该gpu::cmd::SetToken命令被GPU进程处理后,ScopedTransferBufferPtr类的成员变量buffer_描述的子缓冲区才可以真正释放。
接下来我们继续分析GLES2CmdHelper类的成员函数InsertToken的实现,以便可以了解gpu::cmd::SetToken命令的处理过程。
GLES2CmdHelper类的成员函数InsertToken是从父类CommandBufferHelper继承下来的,因此接下来我们分析CommandBufferHelper类的成员函数InsertToken的实现,如下所示:
int32 CommandBufferHelper::InsertToken() { ...... token_ = (token_ + 1) & 0x7FFFFFFF; cmd::SetToken* cmd = GetCmdSpace<cmd::SetToken>(); if (cmd) { cmd->Init(token_); if (token_ == 0) { TRACE_EVENT0("gpu", "CommandBufferHelper::InsertToken(wrapped)"); // we wrapped Finish(); DCHECK_EQ(token_, last_token_read()); } } return token_; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/cmd_buffer_helper.cc中。
前面分析CommandBufferHelper类的成员函数WaitForToken时提到,GLES2CmdHelper类的成员函数InsertToken是通过递增成员变量token_的值来分配Token的。分配出来的Token值封装在一个gpu::cmd::SetToken命令中。
前面分析CommandBufferHelper类的成员函数WaitForToken时也提到,GLES2CmdHelper类的成员变量token_递增后如果变成0,那么就意味着需要马上请求GPU进程处理GPU命令缓冲区中的命令,这是为了确保以后调用CommandBufferHelper类的成员函数WaitForToken时,当指定的Token值小于等于GPU进程为当前OpenGL上下文记录的Token值时,它无需要进入等待状态。
注意,GLES2CmdHelper类的成员函数InsertToken是调用另外一个成员函数Finish请求GPU进程处理GPU命令缓冲区中的命令的。我们知道,我们也可以调用GLES2CmdHelper类的成员函数Flush请求GPU进程处理GPU命令缓冲区中的命令。这两个成员函数的区别在于,前者会同步等待GPU进程处理完成GPU命令缓冲区中的命令,而后者只是发出了请求,但是不会同步等待。
接下来我们继续分析GPU进程处理gpu::cmd::SetToken命令的过程。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,gpu::cmd::SetToken命令由CommonDecoder类的成员函数HandleSetToken处理的,如下所示:
error::Error CommonDecoder::HandleSetToken( uint32 immediate_data_size, const cmd::SetToken& args) { engine_->set_token(args.token); return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,CommonDecoder类的成员变量engine_指向的是一个GpuScheduler对象,这里调用这个GpuScheduler对象的成员函数set_token将gpu::cmd::SetToken命令封装的Token值设置到它内部去。
GpuScheduler类的成员函数set_token的实现如下所示:
void GpuScheduler::set_token(int32 token) { command_buffer_->SetToken(token); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。
GpuScheduler类的成员函数set_token调用成员变量command_buffer_指向的一个CommandBufferService对象的成员函数SetToken将参数token描述的Token设置到它的内部去。
CommandBufferService类的成员函数SetToken的实现如下所示:
void CommandBufferService::SetToken(int32 token) { token_ = token; ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/command_buffer_service.cc中。
CommandBufferService类的成员函数SetToken将参数token描述的Token保存在成员变量token_中。这样当我们前面分析的GpuCommandBufferStub类的成员函数CheckCompleteWaits调用其成员变量command_buffer_指向的一个CommandBufferService对象的成员函数GetLastState时,就可以通过返回的State对象获得其成员变量token_的值,从而知道当前激活的OpenGL上下文记录的最新Token值。
这样,我们就分析完析TransferBuffer类是如何通过Token机制管理与GPU进程共享的缓冲区了。接下来我们再分析另外一个类BufferTracker,它用另一种方管理与GPU进程共享的缓冲区。我们从BufferTracker类的构造函数开始分析它是如何管理与GPU进程共享的缓冲区的。
前面分析GLES2Implementation类的成员函数Initialize时提到,创建一个BufferTracker对象需要一个MappedMemoryManager对象,该MappedMemoryManager对象负责分配和释放共享缓冲区中,并且保存在BufferTracker类的成员变量mapped_memory_中,如下所示:
BufferTracker::BufferTracker(MappedMemoryManager* manager) : mapped_memory_(manager) { }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/buffer_tracker.cc中。
接下来我们就可以调用BufferTracker类的成员函数CreateBuffer分配一个共享缓冲区,如下所示:
BufferTracker::Buffer* BufferTracker::CreateBuffer( GLuint id, GLsizeiptr size) { ...... int32 shm_id = -1; uint32 shm_offset = 0; void* address = NULL; if (size) address = mapped_memory_->Alloc(size, &shm_id, &shm_offset); Buffer* buffer = new Buffer(id, size, shm_id, shm_offset, address); std::pair<BufferMap::iterator, bool> result = buffers_.insert(std::make_pair(id, buffer)); ...... return buffer; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/buffer_tracker.cc中。
BufferTracker类的成员函数CreateBuffer调用成员变量mapped_memory_指向的一个MappedMemoryManager对象的成员函数Alloc从其内部的一块共享缓冲区中分配一个大小为size的子缓冲区。分配成功后,会得到共享缓冲区的ID、子缓冲区的地址及其在共享缓冲区的偏移位置。这些信息会封装在一个BufferTracker::Buffer对象中。这个BufferTracker::Buffer对象同时会被设置一个ID,这个ID值由参数id指定。最后得到的BufferTracker::Buffer对象会以它的ID值作为键值保存在BufferTracker类的成员变量buffers_描述的一个Map中,以及返回给调用者。
接下来我们继续分析MappedMemoryManager类的成员函数Alloc的实现,如下所示:
void* MappedMemoryManager::Alloc( unsigned int size, int32* shm_id, unsigned int* shm_offset) { DCHECK(shm_id); DCHECK(shm_offset); if (size <= allocated_memory_) { size_t total_bytes_in_use = 0; // See if any of the chunks can satisfy this request. for (size_t ii = 0; ii < chunks_.size(); ++ii) { MemoryChunk* chunk = chunks_[ii]; chunk->FreeUnused(); total_bytes_in_use += chunk->bytes_in_use(); if (chunk->GetLargestFreeSizeWithoutWaiting() >= size) { void* mem = chunk->Alloc(size); DCHECK(mem); *shm_id = chunk->shm_id(); *shm_offset = chunk->GetOffset(mem); return mem; } } // If there is a memory limit being enforced and total free // memory (allocated_memory_ - total_bytes_in_use) is larger than // the limit try waiting. if (max_free_bytes_ != kNoLimit && (allocated_memory_ - total_bytes_in_use) >= max_free_bytes_) { TRACE_EVENT0("gpu", "MappedMemoryManager::Alloc::wait"); for (size_t ii = 0; ii < chunks_.size(); ++ii) { MemoryChunk* chunk = chunks_[ii]; if (chunk->GetLargestFreeSizeWithWaiting() >= size) { void* mem = chunk->Alloc(size); DCHECK(mem); *shm_id = chunk->shm_id(); *shm_offset = chunk->GetOffset(mem); return mem; } } } } // Make a new chunk to satisfy the request. CommandBuffer* cmd_buf = helper_->command_buffer(); unsigned int chunk_size = ((size + chunk_size_multiple_ - 1) / chunk_size_multiple_) * chunk_size_multiple_; int32 id = -1; scoped_refptr<gpu::Buffer> shm = cmd_buf->CreateTransferBuffer(chunk_size, &id); if (id < 0) return NULL; DCHECK(shm); MemoryChunk* mc = new MemoryChunk(id, shm, helper_, poll_callback_); allocated_memory_ += mc->GetSize(); chunks_.push_back(mc); void* mem = mc->Alloc(size); DCHECK(mem); *shm_id = mc->shm_id(); *shm_offset = mc->GetOffset(mem); return mem; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/mapped_memory.cc中。
在分析MappedMemoryManager类的成员函数Alloc的实现之前,我们首先描述MappedMemoryManager类是如何管理共享内存的。我们前面分析的TransferBuffer类只管理一块共享内存,所有向它请求的子缓冲区都是从这块共享内存分配出去的。MappedMemoryManager类与TransferBuffer类不同,它管理的是一系列的共享内存块。每一块共享内存都通过一个MemoryChunk对象来描述,并且这些MemoryChunk对象都保存在MappedMemoryManager类的成员变量chunks_描述的一个Vector中。
MemoryChunk类管理共享内存的方式与TransferBuffer类相似,也是按照子缓冲区的形式来管理它内部维护的共享内存块。每一个子缓冲区也有FREE、PENDING_FREE_TOKEN和IN_USE三种状态。处于FREE状态的子缓冲被分配后,状态就变为IN_USE。处于IN_USE状态的子缓冲同时被GPU进程的Client端和GPU命令缓冲区引用。当GPU进程的Client端不引用一个子缓冲区的时候,该子缓冲区的状态就变为PENDING_FREE_TOKEN。当GPU命令缓冲区也不引用一个子缓冲区的时候,该子缓冲区的状态就可以变为FREE了。
当我们调用MappedMemoryManager类的成员函数Alloc请求分配一块大小为size的子缓冲区时,MappedMemoryManager类的成员函数Alloc首先尝试在已有的共享内存块中进行分配,方法如下所示:
1. 依次调用保存在成员变量chunks_中的MemoryChunk对象的成员函数GetLargestFreeSizeWithoutWaiting,询问它内部维护的共享内存块处于FREE状态的在地址上连续的空间是否能满足分配请求,直到碰到一个能满足分配请求的MemoryChunk对象。这时候就继续调用该MemoryChunk对象的成员函数Alloc进行分配,并且将获得的子缓冲区地址、子缓冲所在的共享内存块的ID以及子缓冲在共享内存块的偏移位置返回给调用者,
2. 如果按照第1步的方法不能找到一个能满足分配请求的MemoryChunk对象,那么再检查在已有的共享内存块中,处于FREE和PENDING_FREE_TOKEN状态的空间的大小(allocated_memory_ - total_bytes_in_use)是否大于一个预先设定的阀值(max_free_bytes_)。如果预先设定的阀值不等于kNoLimit,并且处于FREE和PENDING_FREE_TOKEN状态的空间的大小超过该阀值,那么就再次依次调用保存在成员变量chunks_中的MemoryChunk对象的成员函数GetLargestFreeSizeWithWaiting,等待它们先回收处于PENDING_FREE_TOKEN状态的子缓冲区,直到其中的一个MemoryChunk对象回收完处于PENDING_FREE_TOKEN状态的子缓冲区之后,得到处于FREE状态的在地址上连续的空间能满足分配请求。这时候就也会继续调用该MemoryChunk对象的成员函数Alloc进行分配,并且将获得的子缓冲区地址、子缓冲所在的共享内存块的ID以及子缓冲在共享内存块的偏移位置返回给调用者。
如果按照第2步的方法也不能找到一个能满足分配请求的MemoryChunk对象,那么MappedMemoryManager类的成员函数Alloc接下来就会调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数command_buffer获得一个CommandBufferProxyImpl对象,并且调用这个CommandBufferProxyImpl对象的成员函数CreateTransferBuffer分配一块新的共享内存,并且将这块新的共享内存封装在一个MemoryChunk对象保存在成员变量chunks_描述的一个Vector中。与此同时,也会通过该MemoryChunk对象的成员函数Alloc请求在新的共享内存块中分配一块大上为size的子缓冲区,并且将获得的子缓冲区地址、新的共享内存块的ID以及子缓冲在新的共享内存块的偏移位置返回给调用者。
从这里我们就可以看到,MemoryChunk类内部维护的共享内存块与TransferBuffer类内部维护的共享内存块一样,都是通过CommandBufferProxyImpl类的成员函数CreateTransferBuffer进行分配的。关于CommandBufferProxyImpl类的成员函数CreateTransferBuffer的实现,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。
由于MemoryChunk类与前面分析的TransferBuffer类相似,因此我们就不对MemoryChunk类进行详细的分析了。接下来我们继续分析通过BufferTracker类释放子缓冲区的过程。BufferTracker类提供了两个成员函数用于释放子缓冲区,一个是Free,另一个是FreePendingToken。
当一个子缓冲区不被GPU进程的Client端引用时,就可以调用BufferTracker类的成员函数FreePendingToken对其进行释放,如下所示:
void BufferTracker::FreePendingToken(Buffer* buffer, int32 token) { if (buffer->address_) mapped_memory_->FreePendingToken(buffer->address_, token); buffer->size_ = 0; buffer->shm_id_ = 0; buffer->shm_offset_ = 0; buffer->address_ = NULL; buffer->last_usage_token_ = 0; buffer->last_async_upload_token_ = 0; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/buffer_tracker.cc中。
参数buffer指向的是一个Buffer对象,该Buffer对象描述的就是一个子缓冲区,参数token是与该子缓冲区最新关联的Token值。这意味着当GPU进程处理完成一个Token值等于token的gpu::cmd::SetToken命令时,GPU命令缓冲区才停止引用参数buffer描述的子缓冲区。
当参数buffer指向的Buffer对象的成员变量address_的值不等于NULL时,表示该Buffer对象描述的是一个有效的子缓冲区。这时候BufferTracker类的成员函数FreePendingToken调用成员变量mapped_memory_指向的一个MappedMemoryManager对象的成员函数FreePendingToken对该子缓冲区进行回收,也就是将该子缓冲区的状态从IN_USE修改为PENDING_FREE_TOKEN。
BufferTracker类的成员函数FreePendingToken最后还会清空参数buffer指向的Buffer对象的信息,表示它描述的子缓冲区已经被处理过了,不能重复被处理。
接下来我们继续分析MappedMemoryManager类的成员函数FreePendingToken的实现,如下所示:
void MappedMemoryManager::FreePendingToken(void* pointer, int32 token) { for (size_t ii = 0; ii < chunks_.size(); ++ii) { MemoryChunk* chunk = chunks_[ii]; if (chunk->IsInChunk(pointer)) { chunk->FreePendingToken(pointer, token); return; } } NOTREACHED(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/mapped_memory.cc中。
MappedMemoryManager类的成员函数FreePendingToken首先找到参数pointer描述的子缓冲区所属的共享内存块,然后再通过用来描述该享内存块的一个MemoryChunk对象的成员函数FreePendingToken将它的状态设置为PENDING_FREE_TOKEN。
当一个子缓冲区不被GPU命令缓冲区引用时,就可以调用BufferTracker类的成员函数Free对其进行释放,如下所示:
void BufferTracker::Free(Buffer* buffer) { if (buffer->address_) mapped_memory_->Free(buffer->address_); buffer->size_ = 0; buffer->shm_id_ = 0; buffer->shm_offset_ = 0; buffer->address_ = NULL; buffer->last_usage_token_ = 0; buffer->last_async_upload_token_ = 0; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/buffer_tracker.cc中。
BufferTracker类的成员函数Free的实现与前面分析的成员函数FreePendingToken类似,不过它是通过调用mapped_memory_指向的一个MappedMemoryManager对象的成员函数Free将参数buffer描述的子缓冲区的状态从PENDING_FREE_TOKEN修改为FREE。
MappedMemoryManager类的成员函数Free的实现如下所示:
void MappedMemoryManager::Free(void* pointer) { for (size_t ii = 0; ii < chunks_.size(); ++ii) { MemoryChunk* chunk = chunks_[ii]; if (chunk->IsInChunk(pointer)) { chunk->Free(pointer); return; } } NOTREACHED(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/buffer_tracker.cc中。
MappedMemoryManager类的成员函数Free首先找到参数pointer描述的子缓冲区所属的共享内存块,然后再通过用来描述该享内存块的一个MemoryChunk对象的成员函数FreePendingToken将它的状态设置为FREE。
当一个子缓冲区的状态变为PENDING_FREE_TOKEN或者FREE的时候,我们需要通过BufferTracker类的成员函数RemoveBuffer将其从内部移除,如下所示:
void BufferTracker::RemoveBuffer(GLuint client_id) { BufferMap::iterator it = buffers_.find(client_id); if (it != buffers_.end()) { Buffer* buffer = it->second; buffers_.erase(it); if (buffer->address_) mapped_memory_->Free(buffer->address_); delete buffer; } }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/buffer_tracker.cc中。
前面分析BufferTracker类的成员函数CreateBuffer时提到,所有分配的子缓冲区都具有一个ID,并且以这个ID为键值保存在成员变量buffers_描述的一个Map中。这里的参数client_id描述的就是一个要移除的子缓冲区的ID值。BufferTracker类的成员函数RemoveBuffer通过这个ID值就可以在成员变量buffers_描述的Map中找到对应的Buffer对象,并且将其从Map中移除。
从成员变量buffers_描述的Map中移除一个Buffer对象之后,BufferTracker类的成员函数RemoveBuffer还会检查该Buffer对象是否描述的是一个有效的子缓冲区。如果是的话,还会调用成员变量mapped_memory_指向的一个MappedMemoryManager对象的成员函数Free回收它占用的内存。
BufferTracker类还提供有一个成员函数Unmanage,用来清空一个Buffer对象的信息,如下所示:
void BufferTracker::Unmanage(Buffer* buffer) { buffer->size_ = 0; buffer->shm_id_ = 0; buffer->shm_offset_ = 0; buffer->address_ = NULL; buffer->last_usage_token_ = 0; buffer->last_async_upload_token_ = 0; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/buffer_tracker.cc中。
后面我们会看到,当一个Buffer对象描述的子缓冲区被一个异步纹理上传命令引用时,就需要用到这个函数。
这样,我们就分析完析了TransferBuffer类和BufferTracker类是如何为GPU进程的Client端管理与GPU进程共享的内存的,接下来我们就会看到GPU进程的Client端如何使用这些共享内存传递数据给GPU进程的。
我们首先分析的是gles2::cmds::ShaderSource命令附带的Shader Source是如何传递给GPU进程的。GPU进程的Client端是通过GLES2Implementation类的成员函数ShaderSource请求GPU进程执行一个gles2::cmds::ShaderSource命令的,如下所示:
void GLES2Implementation::ShaderSource( GLuint shader, GLsizei count, const GLchar* const* source, const GLint* length) { ...... // Compute the total size. uint32 total_size = 1; for (GLsizei ii = 0; ii < count; ++ii) { if (source[ii]) { total_size += (length && length[ii] >= 0) ? static_cast<size_t>(length[ii]) : strlen(source[ii]); } } // Concatenate all the strings in to a bucket on the service. helper_->SetBucketSize(kResultBucketId, total_size); uint32 offset = 0; for (GLsizei ii = 0; ii <= count; ++ii) { const char* src = ii < count ? source[ii] : ""; if (src) { uint32 size = ii < count ? (length ? static_cast<size_t>(length[ii]) : strlen(src)) : 1; while (size) { ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_); ...... memcpy(buffer.address(), src, buffer.size()); helper_->SetBucketData(kResultBucketId, offset, buffer.size(), buffer.shm_id(), buffer.offset()); offset += buffer.size(); src += buffer.size(); size -= buffer.size(); } } } ...... helper_->ShaderSourceBucket(shader, kResultBucketId); ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
参数source是一个字符串数组,它描述的就是一个Shader Source。GLES2Implementation类的成员函数ShaderSource首先计算这个Shader Source的长度,结果保存在变量total_size中。这个长度可能很长,长到可能不能用一块共享内存来传递给GPU进程,并且gles2::cmds::ShaderSource命令不像gles2::cmds::TexImage2D命令一样,可以通过子命令gles2::cmds::TexSubImage2D一部分一部分地传递数据给GPU进程。这时候就要通过Bucket来传递数据给GPU进程了。
GPU进程的Client端通过Bucket传递数据给GPU进程,首先要做的一件事情是约定好用哪个Bucket来传递。这个直接由GPU进程的Client端指定即可。GLES2Implementation类统一使用一个ID值为kResultBucketId的Bucket传递给GPU进程。
确定好要使用的Bucket之后,GPU进程的Client端接下来就按照以下三个步骤将Shader Source传递给GPU进程。
1. 通知GPU进程需要使用的Bucket的大小,这是通过向GPU进程发送一个gpu::cmd::SetBucketSize命令进行的,该命令通过GLES2Implementation类的成员变量helper_描述的一个GLES2CmdHelper对象的成员函数SetBucketSize写入到GPU命令缓冲区中。
2. 将要传递给GPU进程的Shader Source分别拷贝到一系列的共享子缓冲区中去。每一个共享子缓冲区都通过一个gpu::cmd::SetBucketData命令告知GPU进程。需要告知的信息包括子缓冲区所属的共享内存块的ID、子缓冲区在所属的共享内存的偏移位置以及子缓冲区的大小。保存这些子缓冲区的数据将会被GPU进程拷贝到指定的Bucket中去。每一个子缓冲区都是通过前面分析的ScopedTransferBufferPtr类在GLES2Implementation类的成员变量transfer_buffer_描述的一个TransferBuffer对象进行分配的。每一个子缓冲区对应的gpu::cmd::SetBucketData命令都是GLES2Implementation类的成员变量helper_描述的一个GLES2CmdHelper对象的成员函数SetBucketData写入到GPU命令缓冲区中。
3. 向GPU进程发送一个gpu::cmd::ShaderSourceBucket命令,该命令通过GLES2Implementation类的成员变量helper_描述的一个GLES2CmdHelper对象的成员函数ShaderSourceBucket写入到GPU命令缓冲区中。
通过GLES2CmdHelper类向GPU命令缓冲区写入GPU命令,以及GPU进程从GPU命令缓冲区读出GPU命令的过程可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。接下来我们分析GPU进程处理gpu::cmd::SetBucketSize、gpu::cmd::SetBucketData和gles2::cmds::ShaderSourceBucket三个命令的过程。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gpu::cmd::SetBucketSize命令分发给CommonDecoder类的成员函数HandleSetBucketSize处理,处理过程如下所示:
error::Error CommonDecoder::HandleSetBucketSize( uint32 immediate_data_size, const cmd::SetBucketSize& args) { uint32 bucket_id = args.bucket_id; uint32 size = args.size; Bucket* bucket = CreateBucket(bucket_id); bucket->SetSize(size); return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
CommonDecoder类的成员函数HandleSetBucketSize首先从gpu::cmd::SetBucketSize命令中获得Bucket ID和Bucket Size,然后调用另外一个成员函数CreateBucket创建一个Bucket,并且设置这个Bucket的Size。
CommonDecoder类的成员函数CreateBucket的实现如下所示:
CommonDecoder::Bucket* CommonDecoder::CreateBucket(uint32 bucket_id) { Bucket* bucket = GetBucket(bucket_id); if (!bucket) { bucket = new Bucket(); buckets_[bucket_id] = linked_ptr<Bucket>(bucket); } return bucket; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
CommonDecoder类的成员函数CreateBucket首先调用成员函数GetBucket检查是否为参数bucket_id描述的ID创建过一个Bucket了。如果已经创建过,那么就直接将这个Bucket返回给调用者。否则的话,就新创建一个Bucket,并且将这个Bucket以参数bucket_id为键值保存在成员变量buckets_描述的一个std::map中,最后将该Bucket返回给调用者。
返回到CommonDecoder类的成员函数HandleSetBucketSize中,它获得了一个Bucket之后,就调用该Bucket的成员函数SetSize设置其大小,如下所示:
void CommonDecoder::Bucket::SetSize(size_t size) { if (size != size_) { data_.reset(size ? new int8[size] : NULL); size_ = size; memset(data_.get(), 0, size); } }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
当请求设置的Bucket大小size与Bucket原大小size_不一致时,Bucket类的成员函数SetSize就会重新分配一块大小为size的缓冲区。这块缓冲区会被初始化0,并且它的地址会保在成员变量data_中。
以上就是gpu::cmd::SetBucketSize命令的处理过程,接下来我们继续分析gpu::cmd::SetBucketData命令的处理过程。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gpu::cmd::SetBucketData命令分发给CommonDecoder类的成员函数HandleSetBucketData处理,处理过程如下所示:
error::Error CommonDecoder::HandleSetBucketData( uint32 immediate_data_size, const cmd::SetBucketData& args) { uint32 bucket_id = args.bucket_id; uint32 offset = args.offset; uint32 size = args.size; const void* data = GetSharedMemoryAs<const void*>( args.shared_memory_id, args.shared_memory_offset, size); if (!data) { return error::kInvalidArguments; } Bucket* bucket = GetBucket(bucket_id); if (!bucket) { return error::kInvalidArguments; } if (!bucket->SetData(data, offset, size)) { return error::kInvalidArguments; } return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
CommonDecoder类的成员函数HandleSetBucketData首先从gpu::cmd::SetBucketData命令获得以下参数:
1. bukcet_id,描述的是一个Bucket ID,以它为参数调用成员函数GetBucket就可以获得一个Bucket。
2. offset,描述的是Bucket偏移位置,通过共享内存传递过来的数据将会从这个偏移位置写入到前面获得的Bucket中。
3. size,通过共享内存传递过来的数据的大小。
4. shared_memory_id,用来传递数据的共享内存的ID。
5. shared_memory_offset,传递过来的数据在共享内存的偏移位置。
有了后面三个参数之后,就可以调用成员函数GetSharedMemoryAs获得通过共享内存传递过来的数据,然后将这些数据拷贝到前面获得的Bucket里面去,这时通过调用Bucket类的成员函数SetData实现的,如下所示:
bool CommonDecoder::Bucket::SetData( const void* src, size_t offset, size_t size) { if (OffsetSizeValid(offset, size)) { memcpy(data_.get() + offset, src, size); return true; } return false; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
前面提到,Bucket类的成员变量data_指向了一块缓冲区,这块缓冲区即为Bucket的底层存储,Bucket类的成员函数SetData将参数src描述的缓冲区数据拷贝到这块存储的偏移位置offset中去。
回到CommonDecoder类的成员函数HandleSetBucketData中,我们主要分析它通过调用成员函数GetSharedMemoryAs获得通过共享内存传递过来的数据的过程,如下所示:
class GPU_EXPORT CommonDecoder : NON_EXPORTED_BASE(public AsyncAPIInterface) { public: ...... template <typename T> T GetSharedMemoryAs(unsigned int shm_id, unsigned int offset, unsigned int size) { return static_cast<T>(GetAddressAndCheckSize(shm_id, offset, size)); } ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.h中。
CommonDecoder类的成员函数GetSharedMemoryAs调用另外一个成员函数GetAddressAndCheckSize获得参数shm_id、offset和size描述的共享内存地址,如下所示:
void* CommonDecoder::GetAddressAndCheckSize(unsigned int shm_id, unsigned int data_offset, unsigned int data_size) { CHECK(engine_); scoped_refptr<gpu::Buffer> buffer = engine_->GetSharedMemoryBuffer(shm_id); if (!buffer) return NULL; return buffer->GetDataAddress(data_offset, data_size); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
CommonDecoder类的成员函数GetAddressAndCheckSize首先调用成员变量engine_指向的一个GpuScheduler对象的成员函数GetSharedMemoryBuffer获得与参数shm_id对应的一块共享内存。获得的共享内存侃用一个gpu::Buffer对象描述。有了这个gpu::Buffer对象之后,就可以调用它的成员函数GetDataAddress获得偏移位置data_offset处的地址。
接下来我们主要分析GpuScheduler类的成员函数GetSharedMemoryBuffer的实现,如下所示:
scoped_refptr<Buffer> GpuScheduler::GetSharedMemoryBuffer(int32 shm_id) { return command_buffer_->GetTransferBuffer(shm_id); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gpu_scheduler.cc中。
GpuScheduler类的成员函数GetSharedMemoryBuffer调用成员变量command_buffer_描述的一个CommandBufferService对象的成员函数GetTransferBuffer获得与参数shm_id对应的一块共享内存,如下所示:
scoped_refptr<Buffer> CommandBufferService::GetTransferBuffer(int32 id) { return transfer_buffer_manager_->GetTransferBuffer(id); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/command_buffer_service.cc中。
从前面的分析过程可以知道,参数id描述的共享内存是GPU进程的Client端通过TransferBuffer类创建的,并且是通过调用CommandBufferProxyImpl类的成员函数CreateTransferBuffer进行创建的。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文双可以知道,通过CommandBufferProxyImpl类的成员函数CreateTransferBuffer创建的共享内存会传递给GPU进程,而GPU进程将这块共享内存交给CommandBufferService类处理。CommandBufferService类又会将这块共享内存注册在其成员变量transfer_buffer_manager_描述的一个TransferBufferManager中。
因此,CommandBufferService类的成员函数GetTransferBuffer通过调用成员变量transfer_buffer_manager_描述的TransferBufferManager对象的成员函数GetTransferBuffer就可以获得与参数id对应的、之前已经注册在成员变量transfer_buffer_manager_描述的TransferBufferManager对象中的共享内存。
以上就是gpu::cmd::SetBucketData命令的处理过程,接下来我们继续分析gles2::cmds::ShaderSourceBucket的处理过程。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gles2::cmds::ShaderSourceBucket命令分发给GLES2DecoderImpl类的成员函数HandleShaderSourceBucket处理,处理过程如下所示:
error::Error GLES2DecoderImpl::HandleShaderSourceBucket( uint32 immediate_data_size, const cmds::ShaderSourceBucket& c) { Bucket* bucket = GetBucket(c.data_bucket_id); if (!bucket || bucket->size() == 0) { return error::kInvalidArguments; } return ShaderSourceHelper( c.shader, bucket->GetDataAs<const char*>(0, bucket->size() - 1), bucket->size() - 1); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数HandleShaderSourceBucket首先通过调用成员函数获得gles2::cmds::ShaderSourceBucket命令的Bucket ID对应的Bucket。这个Bucket保存了gles2::cmds::ShaderSourceBucket命令所需要的Shader Source。通过调用这个Bucket的成员函数GetDataAs即可获得保存了Shader Source的缓冲区的地址,如下所示:
class GPU_EXPORT CommonDecoder : NON_EXPORTED_BASE(public AsyncAPIInterface) { public: ...... class GPU_EXPORT Bucket { public: ...... template <typename T> T GetDataAs(size_t offset, size_t size) const { return reinterpret_cast<T>(GetData(offset, size)); } ...... }; ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.h中。
Bucket类的成员函数GetDataAs调用另外一个成员函数GetData获得内部一块用来保存数据的缓冲区地址,如下所示:
void* CommonDecoder::Bucket::GetData(size_t offset, size_t size) const { if (OffsetSizeValid(offset, size)) { return data_.get() + offset; } return NULL; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/common_decoder.cc中。
从前面的分析可以知道,Bucket类内部用来保存数据的缓冲区地址保存在成员变量data_中,因此Bucket类的成员函数GetData将它的值加上参数offset描述的偏移值,就可以得到所请求的数据所在的地址,并且将这个地址返回给调用者。
回到GLES2DecoderImpl类的成员函数HandleShaderSourceBucket中,它得到gles2::cmds::ShaderSourceBucket命令所需要的Shader Source所保存在的缓冲区的地址后,就调用另外一个成员函数ShaderSourceHelper继承处理gles2::cmds::ShaderSourceBucket命令,如下所示:
error::Error GLES2DecoderImpl::ShaderSourceHelper( GLuint client_id, const char* data, uint32 data_size) { std::string str(data, data + data_size); Shader* shader = GetShaderInfoNotProgram(client_id, "glShaderSource"); if (!shader) { return error::kNoError; } // Note: We don‘t actually call glShaderSource here. We wait until // the call to glCompileShader. shader->UpdateSource(str.c_str()); return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
参数client_id描述的是一个Shader ID,以这个Shader ID为参数,GLES2DecoderImpl类的成员函数ShaderSourceHelper以它为参数,调用另外一个成员函数GetShaderInfoNotProgram可以获得一个Shader对象。有了这个Shader对象之后,就调用它的成员函数UpdateSource将参数str描述的Shader Source保存在它的内部,如下所示:
class GPU_EXPORT Shader : public base::RefCounted<Shader> { public: ...... void UpdateSource(const char* source) { source_.reset(source ? new std::string(source) : NULL); } ...... private: ...... scoped_ptr<std::string> source_; ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/shader_manager.h中。
Shader类的成员函数UpdateSource将参数source描述的Shader Source保存在成员变量source_描述的一个字符串中。以后GPU进程的Client端就可以通过gles2::cmds::CompileShader命令编译保存在Shader类的成员变量source_中的Shader Source。
GPU进程的Client端是通过GLES2Implementation类的成员函数CompileShader请求GPU进程执行gles2::cmds::CompileShader命令的,如下所示:
void GLES2Implementation::CompileShader(GLuint shader) { ...... helper_->CompileShader(shader); CheckGLError(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation_impl_autogen.h中。
GLES2Implementation类的成员函数CompileShader通过调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数ComplieShader往GPU命令缓冲区写入一个gles2::cmds::CompileShader命令。GPU进程将这个gles2::cmds::CompileShader命令分给GLES2DecoderImpl类的成员函数HandleCompileShader处理,如下所示:
error::Error GLES2DecoderImpl::HandleCompileShader( uint32_t immediate_data_size, const gles2::cmds::CompileShader& c) { GLuint shader = c.shader; DoCompileShader(shader); return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h中。
GLES2DecoderImpl类的成员函数HandleCompileShader首先从gles2::cmds::CompileShader命令中获得一个ID,然后调用另外一个成员函数DoCompileShader对该ID描述的Shader进行编译,如下所示:
void GLES2DecoderImpl::DoCompileShader(GLuint client_id) { ...... Shader* shader = GetShaderInfoNotProgram(client_id, "glCompileShader"); ...... program_manager()->DoCompileShader( shader, translator, feature_info_->feature_flags().angle_translated_shader_source ? ProgramManager::kANGLE : ProgramManager::kGL); };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数DoCompileShader首先通过调用成员函数GetShaderInfoNotProgram获得与参数client_id对应的一个Shader对象。这个Shader对象保存了要编译的Shader Source。
GLES2DecoderImpl类的成员函数DoCompileShader接下来调用成员函数program_manager获得一个ProgramManager对象,以便可以调用它的成员函数DoComplieShader编译保存在前面获得的Shader对象里面的Shader Source,如下所示:
void ProgramManager::DoCompileShader( Shader* shader, ShaderTranslator* translator, ProgramManager::TranslatedShaderSourceType translated_shader_source_type) { ...... const std::string* source = shader->source(); const char* shader_src = source ? source->c_str() : ""; ...... glShaderSource(shader->service_id(), 1, &shader_src, NULL); glCompileShader(shader->service_id()); ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/program_manager.cc中。
ProgramManager类的成员函数DoComplieShader首先调用参数shader指向的一个Shader对象的成员函数source获得保存在其内部的一个Shader Source字符串。有了这个Shader Source字符串之后,就可以调用OpenGL函数glShaderSource将其加载到GPU中去了。加载到GPU之后,就可以调用另外一个OpenGL函数glCompileShader对其进行编译了。
这样,我们就分析完成gles2::cmds::ShaderSource命令附带的Shader Source传递给GPU进程处理的过程了。接下来我们分析gles2::cmds::BufferData命令是如何将数据传递给GPU进程的。
GPU进程的Client端是通过GLES2Implementation类的成员函数BufferData请求GPU进程执行一个gles2::cmds::BufferData命令的,如下所示:
void GLES2Implementation::BufferData( GLenum target, GLsizeiptr size, const void* data, GLenum usage) { ...... BufferDataHelper(target, size, data, usage); ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数BufferData调用另外一个成员函数BufferDataHelper请求GPU进程执行一个gles2::cmds::BufferData命令,如下所示:
void GLES2Implementation::BufferDataHelper( GLenum target, GLsizeiptr size, const void* data, GLenum usage) { ...... GLuint buffer_id; if (GetBoundPixelTransferBuffer(target, "glBufferData", &buffer_id)) { if (!buffer_id) { return; } BufferTracker::Buffer* buffer = buffer_tracker_->GetBuffer(buffer_id); if (buffer) RemoveTransferBuffer(buffer); // Create new buffer. buffer = buffer_tracker_->CreateBuffer(buffer_id, size); DCHECK(buffer); if (buffer->address() && data) memcpy(buffer->address(), data, size); return; } ...... // See if we can send all at once. ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_); ...... if (buffer.size() >= static_cast<unsigned int>(size)) { memcpy(buffer.address(), data, size); helper_->BufferData( target, size, buffer.shm_id(), buffer.offset(), usage); return; } // Make the buffer with BufferData then send via BufferSubData helper_->BufferData(target, size, 0, 0, usage); BufferSubDataHelperImpl(target, 0, size, data, &buffer); ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数BufferDataHelper首先是调用成员函数GetBoundPixelTransferBuffer检查参数target的值是否等于GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM或者GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,如下所示:
bool GLES2Implementation::GetBoundPixelTransferBuffer( GLenum target, const char* function_name, GLuint* buffer_id) { *buffer_id = 0; switch (target) { case GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM: *buffer_id = bound_pixel_pack_transfer_buffer_id_; break; case GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM: *buffer_id = bound_pixel_unpack_transfer_buffer_id_; break; default: // Unknown target return false; } if (!*buffer_id) { SetGLError(GL_INVALID_OPERATION, function_name, "no buffer bound"); } return true; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
如果参数target的值等于GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM或者GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,那么GLES2Implementation类的成员函数GetBoundPixelTransferBuffer返回成员变量bound_pixel_pack_transfer_buffer_id_或者bound_pixel_unpack_transfer_buffer_id_的值。
GLES2Implementation类的成员变量bound_pixel_pack_transfer_buffer_id_和bound_pixel_unpack_transfer_buffer_id_的值是什么设置的呢?从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,当GPU进程的Client端调用GLES2Implementation类的成员函数BindBuffer请求GPU进程执行一个gles2::cmds::BindBuffer命令的时候,会调用成员函数BindBufferHelper。
GLES2Implementation类的成员函数BindBufferHelper的实现如下所示:
bool GLES2Implementation::BindBufferHelper( GLenum target, GLuint buffer_id) { // TODO(gman): See note #1 above. bool changed = false; switch (target) { ...... case GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM: bound_pixel_pack_transfer_buffer_id_ = buffer_id; break; case GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM: bound_pixel_unpack_transfer_buffer_id_ = buffer_id; break; default: changed = true; break; } ..... return changed; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
当参数target的值等于GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM或者L_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM的时候,另外一个参数buffer_id的值将会分别记录在GLES2Implementation类的成员变量bound_pixel_pack_transfer_buffer_id_或者bound_pixel_unpack_transfer_buffer_id_中。
GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM和GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM是Chromium模仿OpenGL的GL_PIXEL_PACK_BUFFER和GL_PIXEL_UNPACK_BUFFER定义的两个扩展。我们知道,GL_PIXEL_PACK_BUFFER用来将GPU的帧缓冲数据读回到一个PBO中去的,而GL_PIXEL_UNPACK_BUFFER用来将一个PBO的内容作为一个纹理的数据上传到GPU中。类似地,GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM用来将GPU的帧缓冲数据读回到一个共享缓冲区去,而GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM用来将一个共享缓冲区的内容作为一个纹理的数据上传到GPU中去。
例如,当GPU进程的Client端调用GLES2Implementation类的成员函数ReadPixels读取GPU的帧缓冲区数据时,如果之前调用了GLES2Implementation类的成员函数BindBuffer绑定了一个类型为GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM的Buffer ID,那么GLES2Implementation类的成员函数ReadPixels就会将GPU的帧缓冲区读回到该Buffer ID对应的一个共享缓冲区中去,如下所示:
void GLES2Implementation::ReadPixels( GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { ...... if (bound_pixel_pack_transfer_buffer_id_) { GLuint offset = ToGLuint(pixels); BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid( bound_pixel_pack_transfer_buffer_id_, "glReadPixels", offset, padded_row_size * height); if (buffer && buffer->shm_id() != -1) { helper_->ReadPixels(xoffset, yoffset, width, height, format, type, buffer->shm_id(), buffer->shm_offset(), 0, 0, true); CheckGLError(); } return; } ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
当GLES2Implementation类的成员变量bound_pixel_pack_transfer_buffer_id_的值不等于0的时候,就说明GPU进程的Client端调用了GLES2Implementation类的成员函数BindBuffer绑定了一个类型为GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM的Buffer ID,这时候GLES2Implementation类的成员函数ReadPixels向GPU进程发送一个gles::cmds::ReadPixels命令,请求将GPU的帧缓冲区数据读回到成员变量bound_pixel_pack_transfer_buffer_id_描述的共享缓冲区中去。
后面我们分析纹理上传过程时,将会看到GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM是如何使用的。现在回到GLES2Implementation类的成员函数BufferDataHelper中,如果它调用成员函数GetBoundPixelTransferBuffer得到的返回值为true,那么本地变量buffer_id的值就等于成员变量bound_pixel_pack_transfer_buffer_id_或者bound_pixel_unpack_transfer_buffer_id_的值。这时候GLES2Implementation类的成员函数BufferDataHelper所做的事情都是重新为buffer_id创建一个共享缓冲区,并且将参数data描述的数据拷贝到新创建的共享缓冲区去。
在参数target等于GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM的情况下,GLES2Implementation类的成员函数BufferDataHelper主要是为buffer_id创建一个共享缓冲区,而在参数target等于GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM的情况下,GLES2Implementation类的成员函数BufferDataHelper主要是为buffer_id创建一个共享缓冲区,并且将参数data描述的数据拷贝到新创建的共享缓冲区去。这是因为对于前一种情况来说,目的是为了向新创建的共享缓冲区写入数据,而不是为了读取它的数据。
为什么需要为buffer_id重新创建一个共享缓冲区呢?考虑这样一个情景。GPU进程的Client端调用了两次GLES2Implementation类的成员函数ReadPixels读取GPU的帧缓冲区数据。如果第二次调用GLES2Implementation类的成员函数ReadPixels之前的那次调用GLES2Implementation类的成员函数BufferData,没有重新为成员变量bound_pixel_pack_transfer_buffer_id_创建一个新的共享缓冲区,那么就会造成前后两次读取的GPU帧缓冲区数据都写在同一个共享缓冲区中,这样就会造成后一次读取的数据会覆盖前一次读取的数据。为了解决这个问题,就需要为成员变量bound_pixel_pack_transfer_buffer_id_创建一个新的共享缓冲区。
在为buffer_id创建一个新的共享缓冲区之前,需要删除它之前对应的旧共享缓冲区,这是通过调用GLES2Implementation类的成员函数RemoveTransferBuffer实现的,如下所示:
void GLES2Implementation::RemoveTransferBuffer(BufferTracker::Buffer* buffer) { int32 token = buffer->last_usage_token(); uint32 async_token = buffer->last_async_upload_token(); if (async_token) { if (HasAsyncUploadTokenPassed(async_token)) { buffer_tracker_->Free(buffer); } else { detached_async_upload_memory_.push_back( std::make_pair(buffer->address(), async_token)); buffer_tracker_->Unmanage(buffer); } } else if (token) { if (helper_->HasTokenPassed(token)) buffer_tracker_->Free(buffer); else buffer_tracker_->FreePendingToken(buffer, token); } else { buffer_tracker_->Free(buffer); } buffer_tracker_->RemoveBuffer(buffer->id()); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
前面提到,当一个共享缓冲区被GPU命令缓冲区的命令引用时,它会关联有一个Token。这可能是一个同步Token,也可能是一个异步Token。这时候可能不能马上删除这个共享缓冲区,也就是不能马上回收这个共享缓冲区。只有当该Token关联的GPU命令被GPU进程处理过后,才可以回收关联的共享缓冲区。
如果参数buffer指向的BufferTracker对象描述的共享缓冲区关联有异步Token,即调用这个BufferTracker对象的成员函数last_async_upload_token获得的Token值async_token不等于0,那么GLES2Implementation类的成员函数RemoveTransferBuffer首先调用另外一个成员函数HasAsyncUploadTokenPassed判断该Token关联的GPU命令是否已经被GPU进程处理。如果已经被GPU进程处理,那么调用成员函数HasAsyncUploadTokenPassed得到的返回值为true,这时候就可以调用成员变量buffer_tracker_指向的一个BufferTracker对象的成员函数Free回收参数buffer描述的共享缓冲区。否则的话,参数buffer描述的共享缓冲区就暂时不能回收,需要保存在成员变量detached_async_upload_memory_描述的一个std::list中等待回收。后面我们分析异步纹理上传时,再详细分析异步Token机制及其关联的共享缓冲区的回收过程。
如果参数buffer指向的BufferTracker对象描述的共享缓冲区关联有同步Token,即调用这个BufferTracker对象的成员函数last_usage_token获得的Token值token不等于0,那么GLES2Implementation类的成员函数RemoveTransferBuffer首先调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数HasTokenPassed判断该Token关联的GPU命令是否已经被GPU进程处理。如果已经被GPU进程处理,那么调用成员变量helper_描述的GLES2CmdHelper对象的成员函数HasTokenPassed得到的返回值为true,这时候同样可以调用成员变量buffer_tracker_指向的一个BufferTracker对象的成员函数Free回收参数buffer描述的共享缓冲区。否则的话,参数buffer描述的共享缓冲区就暂时不能回收,而是需要设置为FREE_PENDING_TOKEN状态,这是通过调用成员变量buffer_tracker_指向的一个BufferTracker对象的成员函数FreePendingToken实现的。这一点可以前面分析的Token机制。
如果参数buffer指向的BufferTracker对象描述的共享缓冲区既没有关联异步Token,也没有关联同步Token,那么GLES2Implementation类的成员函数RemoveTransferBuffer就可以马上调用成员变量buffer_tracker_指向的一个BufferTracker对象的成员函数Free回收该共享缓冲区。
无论是哪一种情况,最后参数buffer描述的共享缓冲区都需要从成员变量buffer_tracker_描述的BufferTracker对象中删除,这是通过调用这个BufferTracker对象的成员函数RemoveBuffer实现的,这是由于这个BufferTracker对象维护的共享缓冲区均是处于IN_USE状态的,而不是FREE或者FREE_PENDING_TOKEN状态。
LES2Implementation类的成员函数RemoveTransferBuffer需要调用的BufferTracker类的成员函数Free、FreePendingToken和RemoveBuffer,我们在前面都已经分析过了,因此这里就不再复述。
回到GLES2Implementation类的成员函数BufferDataHelper中,如果参数target的值不等于GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM和GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,那么它接下来通过前面分析过的ScopedTransferBufferPtr类创建了一个共享缓冲区。如果这个共享缓冲区的实际大小大于等于参数data描述的缓冲区的大小,这就意味着可以一次性将参数data描述的缓冲区拷贝到获得的共享缓冲区中去。在这种情况下,直接调用成员变量helper_指向的一个GLES2CmdHelper对象向GPU命令缓冲区写入一个gles2::cmds::BufferData命令即可。
如果通过ScopedTransferBufferPtr类创建的共享缓冲区的大小小于参数data描述的缓冲区的大小,这就意味着不可以一次性将参数data描述的缓冲区拷贝到获得的共享缓冲区中去。在这种情况下,可以用两种方式请求GPU进程执行一个gles2::cmds::BufferData命令。
第一种方式与前面分析的gles2::cmds::ShaderSource命令一样,首先将参数data描述的缓冲区数据写入到一个Bucket中,然后再通过一个类似的gles2::cmds::BufferDataBucket命令将该Bucket的ID传递给GPU进程。GPU进程获得这个ID之后,就可以将参数data描述的缓冲区数据读取出来,作为OpenGL函数glBufferData的参数。
第二种方式是用若干个gles2::cmds::BufferSubData命令来完成一个gles2::cmds::BufferData,这得益于OpenGL支持通过函数glBufferSubData部分更新Buffer对象的数据。
GLES2Implementation类的成员函数BufferDataHelper使用的是第二种方式,是通过调用另外一个成员函数BufferSubDataHelperImpl实现的,如下所示:
void GLES2Implementation::BufferSubDataHelperImpl( GLenum target, GLintptr offset, GLsizeiptr size, const void* data, ScopedTransferBufferPtr* buffer) { ...... const int8* source = static_cast<const int8*>(data); while (size) { if (!buffer->valid() || buffer->size() == 0) { buffer->Reset(size); if (!buffer->valid()) { return; } } memcpy(buffer->address(), source, buffer->size()); helper_->BufferSubData( target, offset, buffer->size(), buffer->shm_id(), buffer->offset()); offset += buffer->size(); source += buffer->size(); size -= buffer->size(); buffer->Release(); } }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数BufferSubDataHelperImpl通过一个while循环将参数data描述的缓冲区数据传递GPU进程。每一次循环都会通过参数buffer指向的一个ScopedTransferBufferPtr对象的成员函数Reset创建一个共享缓冲区,然后将依次将参数data描述的缓冲区数据写入到这个共享缓冲区中,最后通过调用成员变量helper_指向的一个GLES2CmdHelper对象的成员函数BufferSubData向GPU命令缓冲区写入一个gles2::cmds::BufferSubData命令,以便可以将共享缓冲区交给GPU进程调用OpenGL函数glBufferSubData处理。
接下来我们继续分析GPU进程处理gles2::cmds::BufferData命令和gles2::cmds::BufferSubData命令的过程。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gles2::cmds::BufferData命令分发给GLES2DecoderImpl类的成员函数HandleBufferData处理,处理过程如下所示:
error::Error GLES2DecoderImpl::HandleBufferData( uint32 immediate_data_size, const cmds::BufferData& c) { GLenum target = static_cast<GLenum>(c.target); GLsizeiptr size = static_cast<GLsizeiptr>(c.size); uint32 data_shm_id = static_cast<uint32>(c.data_shm_id); uint32 data_shm_offset = static_cast<uint32>(c.data_shm_offset); GLenum usage = static_cast<GLenum>(c.usage); const void* data = NULL; if (data_shm_id != 0 || data_shm_offset != 0) { data = GetSharedMemoryAs<const void*>(data_shm_id, data_shm_offset, size); if (!data) { return error::kOutOfBounds; } } buffer_manager()->ValidateAndDoBufferData(&state_, target, size, data, usage); return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数HandleBufferData首先从gles2::cmds::BufferData命令中获得Buffer Data的大小,以及Bufer Data所保存在的共享内存的ID、Bufer Data相对共享内存的偏移位置等信息。有了上述三个信息之后,就可以调用另外一个成员函数GetSharedMemoryAs获得Buffer Data所保存在的共享缓冲区地址。有了这个地址后,再调用成员函数buffer_manager获得一个BufferManager对象,最后调用该BufferManager对象的成员函数ValidateAndDoBufferData继续处理gles2::cmds::BufferData命令。
BufferManager类的成员函数ValidateAndDoBufferData的实现如下所示:
void BufferManager::ValidateAndDoBufferData( ContextState* context_state, GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage) { ...... Buffer* buffer = GetBufferInfoForTarget(context_state, target); ...... DoBufferData(error_state, buffer, size, usage, data); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/buffer_manager.cc中。
BufferManager类的成员函数ValidateAndDoBufferData对参数data描述的Buffer Data进行一系列的检查之后,就调用成员函数GetBufferInfoForTarget根据参数target获得要更新数据的Buffer对象,最后调用成员函数DoBufferData将参数data描述的Buffer Data更新为参数前面获得的的Buffer对象描述的Buffer的数据。
BufferManager类的成员函数DoBufferData的实现如下所示:
void BufferManager::DoBufferData( ErrorState* error_state, Buffer* buffer, GLsizeiptr size, GLenum usage, const GLvoid* data) { ...... if (IsUsageClientSideArray(usage)) { GLsizei empty_size = UseNonZeroSizeForClientSideArrayBuffer() ? 1 : 0; glBufferData(buffer->target(), empty_size, NULL, usage); } else { glBufferData(buffer->target(), size, data, usage); } ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/buffer_manager.cc中。
OpenGL的Buffer对象有Client端和Server端。对于Client端的Buffer对象,直接更新它的数据即可,不必通过OpenGL函数glBufferData进行更新,不过仍然需要通过通过调用OpenGL函数glBufferData通知OpenGL。这时候调用OpenGL函数glBufferData时,第三个参数指定为NULL即可。对于Server端的Buffer对象,则需要调用OpenGL函数glBufferData更新它的数据,也就是在调用OpenGL函数glBufferData时,第三个参数需要指定为数据所在的地址。
以上就是gles2::cmds::BufferData命令的处理过程,接下来我们继续分析gles2::cmds::BufferSubData命令的处理过程。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gles2::cmds::BufferSubData命令分发给GLES2DecoderImpl类的成员函数HandleBufferSubData处理,处理过程如下所示:
error::Error GLES2DecoderImpl::HandleBufferSubData( uint32_t immediate_data_size, const gles2::cmds::BufferSubData& c) { GLenum target = static_cast<GLenum>(c.target); GLintptr offset = static_cast<GLintptr>(c.offset); GLsizeiptr size = static_cast<GLsizeiptr>(c.size); uint32_t data_size = size; const void* data = GetSharedMemoryAs<const void*>( c.data_shm_id, c.data_shm_offset, data_size); ...... DoBufferSubData(target, offset, size, data); return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h中。
GLES2DecoderImpl类的成员函数HandleBufferSubData与前面分析的成员函数HandleBufferData类似,都是通过成员函数GetSharedMemoryAs根据gles2::cmds::BufferSubData命令封装的信息获得用来更新Buffer对象数据的共享缓冲区的地址,不过GLES2DecoderImpl类的成员函数HandleBufferSubData调用另外一个成员函数DoBufferSubData继续处理gles2::cmds::BufferSubData命令。
GLES2DecoderImpl类的成员函数DoBufferSubData的实现如下所示:
void GLES2DecoderImpl::DoBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data) { // Just delegate it. Some validation is actually done before this. buffer_manager()->ValidateAndDoBufferSubData( &state_, target, offset, size, data); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数DoBufferSubData首先调用成员函数buffer_manager获得一个BufferManager对象,接着调用这个BufferManager对象的成员函数ValidateAndDoBufferSubData更新参数target描述的Buffer对象在参数offset描述的偏移处的数据。
BufferManager类的成员函数ValidateAndDoBufferSubData的实现如下所示:
void BufferManager::ValidateAndDoBufferSubData( ContextState* context_state, GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data) { ...... Buffer* buffer = GetBufferInfoForTarget(context_state, target); ...... DoBufferSubData(error_state, buffer, offset, size, data); }
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/buffer_manager.cc中。
BufferManager类的成员函数ValidateAndDoBufferSubData与前面分析的成员函数ValidateAndDoBufferData一样,都是先调用成员函数GetBufferInfoForTarget根据参数target获得要更新数据的Buffer对象,然后调用成员函数DoBufferSubData将参数data描述的Buffer Data更新为参数前面获得的的Buffer对象描述的Buffer的数据。
BufferManager类的成员函数DoBufferSubData的实现如下所示:
void BufferManager::DoBufferSubData( ErrorState* error_state, Buffer* buffer, GLintptr offset, GLsizeiptr size, const GLvoid* data) { ...... if (!buffer->IsClientSideArray()) { glBufferSubData(buffer->target(), offset, size, data); } }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/buffer_manager.cc中。
BufferManager类的成员函数DoBufferSubData调用OpenGL函数更新参数buffer描述的的Buffer对象在偏移处offset的数据。不过对于Server端的Buffer对象,才需要这样做。对于Client端的Buffer对象,直接在GPU进程的Client端更新即可。
以上就是gles2::cmds::BufferSubData命令的处理过程。这样,我们就分析完成gles2::cmds::BufferData命令附带的Buffer Data传递给GPU进程处理的过程了。接下来我们就开始分析GPU进程的Client端传递纹理数据给GPU进程处理的过程。我们首先分析同步纹理上传方式,然后再分析异步纹理上传方式。
GPU进程的Client端通过是通过GLES2Implementation类的成员函数TexImage2D请求GPU进程执行一个同步纹理上传命令的,如下所示:
void GLES2Implementation::TexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) { ...... // If there‘s a pixel unpack buffer bound use it when issuing TexImage2D. if (bound_pixel_unpack_transfer_buffer_id_) { GLuint offset = ToGLuint(pixels); BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid( bound_pixel_unpack_transfer_buffer_id_, "glTexImage2D", offset, size); if (buffer && buffer->shm_id() != -1) { helper_->TexImage2D( target, level, internalformat, width, height, format, type, buffer->shm_id(), buffer->shm_offset() + offset); buffer->set_last_usage_token(helper_->InsertToken()); CheckGLError(); } return; } ...... // Check if we can send it all at once. ScopedTransferBufferPtr buffer(size, helper_, transfer_buffer_); ...... if (buffer.size() >= size) { CopyRectToBuffer( pixels, height, unpadded_row_size, src_padded_row_size, unpack_flip_y_, buffer.address(), padded_row_size); helper_->TexImage2D( target, level, internalformat, width, height, format, type, buffer.shm_id(), buffer.offset()); CheckGLError(); return; } // No, so send it using TexSubImage2D. helper_->TexImage2D( target, level, internalformat, width, height, format, type, 0, 0); TexSubImage2DImpl( target, level, 0, 0, width, height, format, type, unpadded_row_size, pixels, src_padded_row_size, GL_TRUE, &buffer, padded_row_size); CheckGLError(); }
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
当GLES2Implementation类的成员变量bound_pixel_unpack_transfer_buffer_id_的值不等于0的时候,如前所述,说明GPU进程的Client端要将这个成员变量描述的共享缓冲区的内容作为纹理数据上传。因此,这时候GLES2Implementation类的成员函数TexImage2D就先调用成员函数GetBoundPixelUnpackTransferBufferIfValid获得成员变量bound_pixel_unpack_transfer_buffer_id_描述的共享缓冲区,然后再调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数TexImage2D往GPU命令缓冲区写入一个gles2::cmds::TexImage2D命令,这个gles2::cmds::TexImage2D命令指定要上传的纹理数据由前面获得的共享共缓冲区提供。
当GLES2Implementation类的成员变量bound_pixel_unpack_transfer_buffer_id_的值不等于0的时候,我们看到,GLES2Implementation类的成员函数TexImage2D往GPU命令缓冲区写入一个gles2::cmds::TexImage2D命令之后,还会继续调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数InsertToken往GPU命令缓冲区写入一个gpu::cmd::SetToken命令,并且将这个gpu::cmd::SetToken命令关联的Token值设置给成员变量bound_pixel_unpack_transfer_buffer_id_描述的共享缓冲区,意味着这个共享缓冲区要等到前面写入的gpu::cmd::SetToken命令被GPU进程处理之后,才可以回收。
当GLES2Implementation类的成员变量bound_pixel_unpack_transfer_buffer_id_的值等于0的时候,GLES2Implementation类的成员函数TexImage2D要将上传的纹理数据由参数pixels提供。这时候需要将参数pixels指向的缓冲区内容拷贝到一个共享缓冲区中传递给GPU进程,因此,GLES2Implementation类的成员函数TexImage2D首先通过ScopedTransferBufferPtr类从成员变量transfer_buffer_描述的一个TransferBuffer对象中分配一个共享缓冲区。
如果前面分配得到的共享缓冲区大小大于等于参数pixels指向的缓冲区,那么就可以调用另外一个成员函数CopyRectToBuffer一次性地将参数pixels指向的缓冲区的内容拷贝到共享缓冲区中,然后再通过一个gles2::cmds::TexImage2D命令将这个共享缓冲区传递GPU进程作为纹理数据上传到GPU去。这个gles2::cmds::TexImage2D命令是通过调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数TexImage2D写入到GPU命令缓冲区的。
如果前面分配得到的共享缓冲区大小小于参数pixels指向的缓冲区,那么需要分多次将参数pixels指向的缓冲区的数据传递给GPU进程。每一次通过一个gles2::cmds::TexSubImage2D命令指定要传递的部分数据。这是通过调用GLES2Implementation类的成员函数TexSubImage2DImpl实现的,如下所示:
void GLES2Implementation::TexSubImage2DImpl( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, uint32 unpadded_row_size, const void* pixels, uint32 pixels_padded_row_size, GLboolean internal, ScopedTransferBufferPtr* buffer, uint32 buffer_padded_row_size) { ...... const int8* source = reinterpret_cast<const int8*>(pixels); GLint original_yoffset = yoffset; // Transfer by rows. while (height) { unsigned int desired_size = buffer_padded_row_size * (height - 1) + unpadded_row_size; if (!buffer->valid() || buffer->size() == 0) { buffer->Reset(desired_size); if (!buffer->valid()) { return; } } GLint num_rows = ComputeNumRowsThatFitInBuffer( buffer_padded_row_size, unpadded_row_size, buffer->size()); num_rows = std::min(num_rows, height); CopyRectToBuffer( source, num_rows, unpadded_row_size, pixels_padded_row_size, unpack_flip_y_, buffer->address(), buffer_padded_row_size); GLint y = unpack_flip_y_ ? original_yoffset + height - num_rows : yoffset; helper_->TexSubImage2D( target, level, xoffset, y, width, num_rows, format, type, buffer->shm_id(), buffer->offset(), internal); buffer->Release(); yoffset += num_rows; source += num_rows * pixels_padded_row_size; height -= num_rows; } }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数TexSubImage2DImpl的实现与前面分析的成员函数BufferSubDataHelperImpl是类似的,都是通过一个while循环将参数pixels描述的缓冲区数据传递GPU进程。每一次循环都会通过参数buffer指向的一个ScopedTransferBufferPtr对象的成员函数Reset创建一个共享缓冲区,然后将依次将参数pixels描述的缓冲区数据写入到这个共享缓冲区中,最后通过调用成员变量helper_指向的一个GLES2CmdHelper对象的成员函数TexSubImage2D向GPU命令缓冲区写入一个gles2::cmds::TexSubImage2D命令,以便可以将共享缓冲区交给GPU进程调用OpenGL函数glTexSubImage2D处理。
接下来我们继续分析GPU进程处理gles2::cmds::TexImage2D命令和gles2::cmds::TexSubImage2D命令的过程。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gles2::cmds::TexImage2D命令分发给GLES2DecoderImpl类的成员函数HandleTexImage2D处理,处理过程如下所示:
error::Error GLES2DecoderImpl::HandleTexImage2D( uint32 immediate_data_size, const cmds::TexImage2D& c) { ...... // Set as failed for now, but if it successed, this will be set to not failed. texture_state_.tex_image_2d_failed = true; GLenum target = static_cast<GLenum>(c.target); GLint level = static_cast<GLint>(c.level); // TODO(kloveless): Change TexImage2D command to use unsigned integer // for internalformat. GLenum internal_format = static_cast<GLenum>(c.internalformat); GLsizei width = static_cast<GLsizei>(c.width); GLsizei height = static_cast<GLsizei>(c.height); GLint border = static_cast<GLint>(c.border); GLenum format = static_cast<GLenum>(c.format); GLenum type = static_cast<GLenum>(c.type); uint32 pixels_shm_id = static_cast<uint32>(c.pixels_shm_id); uint32 pixels_shm_offset = static_cast<uint32>(c.pixels_shm_offset); uint32 pixels_size; if (!GLES2Util::ComputeImageDataSizes( width, height, format, type, state_.unpack_alignment, &pixels_size, NULL, NULL)) { return error::kOutOfBounds; } const void* pixels = NULL; if (pixels_shm_id != 0 || pixels_shm_offset != 0) { pixels = GetSharedMemoryAs<const void*>( pixels_shm_id, pixels_shm_offset, pixels_size); ...... } TextureManager::DoTextImage2DArguments args = { target, level, internal_format, width, height, border, format, type, pixels, pixels_size}; texture_manager()->ValidateAndDoTexImage2D( &texture_state_, &state_, &framebuffer_state_, args); return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数HandleTexImage2D首先从传递过来的gles2::cmds::TexImage2D命令获得上传纹理所需要的各种参数。其中,参数pixel_shm_id描述的是纹理数据缓冲区所在的共享内存块的ID,参数pixels_shm_offset是纹理数据缓冲区在共享内存块的偏移位置,参数pixels_size是纹理数据缓冲区的大小。有了这三个参数,就可以调用成员函数GetSharedMemoryAs获得纹理数据缓冲区的地址pixels。
GLES2DecoderImpl类的成员函数HandleTexImage2D接下来就将前面获得的参数和纹理数据缓冲区地址封装在一个DoTextImage2DArguments对象,再接下来又调用成员函数texture_manager获得一个TextureManager对象,最后将前面封装的DoTextImage2DArguments对象传递给这个TextureManager对象的成员函数ValidateAndDoTexImage2D,以便后者可以继续处理gles2::cmds::TexImage2D命令。
TextureManager类的成员函数ValidateAndDoTexImage2D的实现如下所示:
void TextureManager::ValidateAndDoTexImage2D( DecoderTextureState* texture_state, ContextState* state, DecoderFramebufferState* framebuffer_state, const DoTextImage2DArguments& args) { TextureRef* texture_ref; if (!ValidateTexImage2D(state, "glTexImage2D", args, &texture_ref)) { return; } DoTexImage2D(texture_state, state->GetErrorState(), framebuffer_state, texture_ref, args); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/texture_manager.cc中。
TextureManager类的成员函数ValidateAndDoTexImage2D首先调用另外一个成员函数ValidateTexImage检查通过参数args的合法性。通过了合法性检查之后 ,TextureManager类的成员函数ValidateTexImage会根据传递进来的参数args.target返回一个TextureRef对象。这意味GPU进程会为每一个纹理Target创建一个TextureRef对象对象。有了这个TextureRef对象之后,就可以调用另外一个成员函数DoTexImage2D上传纹理数据给GPU进程了。
TextureManager类的成员函数DoTexImage2D的实现如下所示:
void TextureManager::DoTexImage2D( DecoderTextureState* texture_state, ErrorState* error_state, DecoderFramebufferState* framebuffer_state, TextureRef* texture_ref, const DoTextImage2DArguments& args) { ...... { ScopedTextureUploadTimer timer(texture_state); glTexImage2D( args.target, args.level, args.internal_format, args.width, args.height, args.border, args.format, args.type, args.pixels); } ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/texture_manager.cc中。
TextureManager类的成员函数DoTexImage2D主要就是调用OpenGL函数glTexImage2D上传参数args.pixels描述的纹理数据给GPU。
以上就是gles2::cmds::TexImage2D命令的处理过程,接下来我们继续分析gles2::cmds::TexSubImage2D命令的处理过程。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gles2::cmds::TexSubImage2D命令分发给GLES2DecoderImpl类的成员函数HandleTexSubImage2D处理,处理过程如下所示:
error::Error GLES2DecoderImpl::HandleTexSubImage2D( uint32 immediate_data_size, const cmds::TexSubImage2D& c) { ...... GLenum target = static_cast<GLenum>(c.target); GLint level = static_cast<GLint>(c.level); GLint xoffset = static_cast<GLint>(c.xoffset); GLint yoffset = static_cast<GLint>(c.yoffset); GLsizei width = static_cast<GLsizei>(c.width); GLsizei height = static_cast<GLsizei>(c.height); GLenum format = static_cast<GLenum>(c.format); GLenum type = static_cast<GLenum>(c.type); uint32 data_size; if (!GLES2Util::ComputeImageDataSizes( width, height, format, type, state_.unpack_alignment, &data_size, NULL, NULL)) { return error::kOutOfBounds; } const void* pixels = GetSharedMemoryAs<const void*>( c.pixels_shm_id, c.pixels_shm_offset, data_size); return DoTexSubImage2D( target, level, xoffset, yoffset, width, height, format, type, pixels); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数HandleTexSubImage2D首先从传递过来的gles2::cmds::TexSubImage2D命令获得上传纹理所需要的各种参数。其中,参数c.pixels_shm_id描述的是纹理数据缓冲区所在的共享内存块的ID,参数c.pixels_shm_offset是纹理数据缓冲区在共享内存块的偏移位置,参数data_size是纹理数据缓冲区的大小。有了这三个参数,就可以调用成员函数GetSharedMemoryAs获得纹理数据缓冲区的地址pixels。
获得了纹理数据缓冲区的地址pixels之后,GLES2DecoderImpl类的成员函数HandleTexSubImage2D就调用另外一个成员函数DoTexSubImage2D执行纹理上传操作,如下所示:
error::Error GLES2DecoderImpl::DoTexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * data) { ...... if (xoffset != 0 || yoffset != 0 || width != tex_width || height != tex_height) { ...... ScopedTextureUploadTimer timer(&texture_state_); glTexSubImage2D( target, level, xoffset, yoffset, width, height, format, type, data); return error::kNoError; } ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数DoTexSubImage2D主要就是调用OpenGL函数glTexSubImage2D上传参数data描述的纹理数据给GPU。
以上就是gles2::cmds::TexSubImage2D命令的处理过程。这样,我们就分析完成gles2::cmds::TexImage2D命令附带的纹理数据传递给GPU进程处理的过程了。获得了纹理数据后,GPU进程是在GPU主线程中调用OpenGL函数glTexImage2D或者glTexSubImage2D上传给GPU。这意味着在纹理上传的过程中,GPU主线程将会被阻塞。这种纹理上传方式就称为同步纹理上传。接下来我们分析另外一种纹理方式——异步纹理。
GPU进程的Client端通过是通过GLES2Implementation类的成员函数AsyncTexImage2DCHROMIUM请求GPU进程执行一个异步纹理上传命令的,如下所示:
void GLES2Implementation::AsyncTexImage2DCHROMIUM( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) { ...... if (!EnsureAsyncUploadSync()) { SetGLError(GL_OUT_OF_MEMORY, "glTexImage2D", "out of memory"); return; } ...... GLuint offset = ToGLuint(pixels); BufferTracker::Buffer* buffer = GetBoundPixelUnpackTransferBufferIfValid( bound_pixel_unpack_transfer_buffer_id_, "glAsyncTexImage2DCHROMIUM", offset, size); if (buffer && buffer->shm_id() != -1) { uint32 async_token = NextAsyncUploadToken(); buffer->set_last_async_upload_token(async_token); helper_->AsyncTexImage2DCHROMIUM( target, level, internalformat, width, height, format, type, buffer->shm_id(), buffer->shm_offset() + offset, async_token, async_upload_sync_shm_id_, async_upload_sync_shm_offset_); } }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
异步纹理上传要求GPU进程的Client端将纹理数据保存在一个类型为GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM的Buffer对象中,也就是要求之前调用过GLES2Implementation类的成员函数BindBuffer和BufferData将纹理数据保存在成员变量bound_pixel_unpack_transfer_buffer_id_描述的一个共享缓冲区中。这个共享缓冲区可以通过调用GLES2Implementation类的成员函数GetBoundPixelUnpackTransferBufferIfValid获得。
获得了成员变量bound_pixel_unpack_transfer_buffer_id_描述的共享缓冲区之后,就可以通过一个gles2::cmds::AsyncTexImage2DCHROMIUM命令将它传递给GPU进程,以便GPU进程将保存在它里面的纹理数据传递给GPU。这个gles2::cmds::AsyncTexImage2DCHROMIUM命令是通过调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数AsyncTexImage2DCHROMIUM写入到GPU命令缓冲区的。
通过les2::cmds::AsyncTexImage2DCHROMIUM命令传递GPU进程的还有两个重要的参数。一个是异步Token,另一个是一块用来查询纹理异步上传状态的共享内存。
异步Token是通过调用GLES2Implementation类的成员函数NextAsyncUploadToken生成的,如下所示:
uint32 GLES2Implementation::NextAsyncUploadToken() { async_upload_token_++; if (async_upload_token_ == 0) async_upload_token_++; return async_upload_token_; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类有一个类型为uint32的成员变量async_upload_token_,它的初始值为0。GLES2Implementation类的成员函数NextAsyncUploadToken每次被调用时,都会递增成员变量async_upload_token_的值,作为下一个使用的异步Token值。递增后的值如果等于uint32类型的最大值,那么自动绕回0。这时候需要再次递增成员变量async_upload_token_的值,以保证生成的异步Token值始终是大于0的。
返回到GLES2Implementation类的成员函数AsyncTexImage2DCHROMIUM中,调用成员函数NextAsyncUploadToken获得了一个异步Token值之后,它会将这个Token值设置给成员变量bound_pixel_unpack_transfer_buffer_id_描述的共享缓冲区,用来保证该共享缓冲区在纹理数据被GPU进程异步上传完成之前不会被回收。以此同时,这个异步Token也会传递给GPU进程。GPU进程异步上传完成一个纹理数据之后,就会将与该纹理数据关联的异步Token值设置为当前激活的OpenGL上下文的异步Token值。这样,GPU进程的Client端通过记录在OpenGL上下文中的异步Token值,就可以知道哪些纹理已经异步上传完成了,哪些纹理还没有异步上传完成。
GPU进程的Client是通过一块与GPU进程共享的缓冲区来查询当前激活的OpenGL上下文的异步Token值的。这块共享内存是通过调用GLES2Implementation类的成员函数EnsureAsyncUploadSync创建的,如下所示:
bool GLES2Implementation::EnsureAsyncUploadSync() { if (async_upload_sync_) return true; int32 shm_id; unsigned int shm_offset; void* mem = mapped_memory_->Alloc(sizeof(AsyncUploadSync), &shm_id, &shm_offset); if (!mem) return false; async_upload_sync_shm_id_ = shm_id; async_upload_sync_shm_offset_ = shm_offset; async_upload_sync_ = static_cast<AsyncUploadSync*>(mem); async_upload_sync_->Reset(); return true; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数EnsureAsyncUploadSync是通过调用成员变量mapped_memory_指向的一个MappedMemoryManager对象的成员函数Alloc来创建用来查询当前激活的OpenGL上下文的异步Token值的共享缓冲区的。这个共享缓冲区所在的共享内存块的ID和这个共享缓冲区相对所在共享内存块的偏移位置分别保存在GLES2Implementation类的成员变量async_upload_sync_shm_id_和async_upload_sync_shm_offset_中。
最后,创建出来的共享缓冲区还会被当作一个AsyncUploadSync对象保存GLES2Implementation类的成员变量async_upload_sync_中。这样当这个成员变量的值不等于NULL的时候,就表示用来查询当前激活的OpenGL上下文的异步Token值的共享缓冲区已经创建过了。在这种情况下,GLES2Implementation类的成员函数EnsureAsyncUploadSync就不用重复创建了。
AsyncUploadSync类的定义如下所示:
struct AsyncUploadSync { void Reset() { base::subtle::Release_Store(&async_upload_token, 0); } void SetAsyncUploadToken(uint32_t token) { DCHECK_NE(token, 0u); base::subtle::Release_Store(&async_upload_token, token); } bool HasAsyncUploadTokenPassed(uint32_t token) { DCHECK_NE(token, 0u); uint32_t current_token = base::subtle::Acquire_Load(&async_upload_token); return (current_token - token < 0x80000000); } base::subtle::Atomic32 async_upload_token; };这个类定义在文件external/chromium_org/gpu/command_buffer/common/gles2_cmd_format.h中。
AsyncUploadSync类有一个原子类型的成员变量async_upload_token,它就是用来描述一个OpenGL上下文当前的异步Token值的,它的值通过AsyncUploadSync类的成员函数Reset初始化为0。
GPU进程每异步上传完成一个纹理数据,都会将该纹理数据关联的异步Token值设置到AsyncUploadSync类的成员变量async_upload_token_中去,这是通过调用AsyncUploadSync类的成员函数SetAsyncUploadToken完成的。
GPU进程的Client需要检查一个纹理数据是否异步完成时,需要获得设置给该纹理数据的异步Token值,并且以该异步Token值为参数,调用AsyncUploadSync类的成员函数HasAsyncUploadTokenPassed。如果GPU进程已经上传完成该纹理数据,那么传递给AsyncUploadSync类的成员函数HasAsyncUploadTokenPassed的token值,就会小于等于成员变量async_upload_token_的值。
注意AsyncUploadSync类的成员函数HasAsyncUploadTokenPassed是如何比较两个uint32值大小的。如果参数token的值小于等于本地变量current_token的值,那么(curren_token - token)的值就会大于等于0。当(curren_token - token)的值等于0的时候,(0 < 0x80000000)成立,因此AsyncUploadSync类的成员函数HasAsyncUploadTokenPassed的返回值为true。当(curren_token - token)的值大于0的时候,(curren_token - token)是一个正数,它的最高位等于0,而0x80000000的最高位为1,它们作为无符号数比较时,(curren_token - token < 0x80000000)也成立,因此AsyncUploadSync类的成员函数HasAsyncUploadTokenPassed的返回值为也为true。
接下来,我们继续分析gles2::cmds::AsyncTexImage2DCHROMIUM命令的处理过程。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gles2::cmds::AsyncTexImage2DCHROMIUM命令分发给GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM处理,处理过程如下所示:
error::Error GLES2DecoderImpl::HandleAsyncTexImage2DCHROMIUM( uint32 immediate_data_size, const cmds::AsyncTexImage2DCHROMIUM& c) { ...... GLenum target = static_cast<GLenum>(c.target); GLint level = static_cast<GLint>(c.level); GLenum internal_format = static_cast<GLenum>(c.internalformat); GLsizei width = static_cast<GLsizei>(c.width); GLsizei height = static_cast<GLsizei>(c.height); GLint border = static_cast<GLint>(c.border); GLenum format = static_cast<GLenum>(c.format); GLenum type = static_cast<GLenum>(c.type); uint32 pixels_shm_id = static_cast<uint32>(c.pixels_shm_id); uint32 pixels_shm_offset = static_cast<uint32>(c.pixels_shm_offset); uint32 pixels_size; uint32 async_upload_token = static_cast<uint32>(c.async_upload_token); uint32 sync_data_shm_id = static_cast<uint32>(c.sync_data_shm_id); uint32 sync_data_shm_offset = static_cast<uint32>(c.sync_data_shm_offset); base::ScopedClosureRunner scoped_completion_callback; if (async_upload_token) { base::Closure completion_closure = AsyncUploadTokenCompletionClosure(async_upload_token, sync_data_shm_id, sync_data_shm_offset); ...... scoped_completion_callback.Reset(completion_closure); } if (!GLES2Util::ComputeImageDataSizes( width, height, format, type, state_.unpack_alignment, &pixels_size, NULL, NULL)) { return error::kOutOfBounds; } const void* pixels = NULL; if (pixels_shm_id != 0 || pixels_shm_offset != 0) { pixels = GetSharedMemoryAs<const void*>( pixels_shm_id, pixels_shm_offset, pixels_size); ...... } TextureManager::DoTextImage2DArguments args = { target, level, internal_format, width, height, border, format, type, pixels, pixels_size}; TextureRef* texture_ref; // All the normal glTexSubImage2D validation. if (!texture_manager()->ValidateTexImage2D( &state_, "glAsyncTexImage2DCHROMIUM", args, &texture_ref)) { return error::kNoError; } ...... AsyncTexImage2DParams tex_params = { target, level, static_cast<GLenum>(internal_format), width, height, border, format, type}; AsyncMemoryParams mem_params( GetSharedMemoryBuffer(c.pixels_shm_id), c.pixels_shm_offset, pixels_size); ...... AsyncPixelTransferDelegate* delegate = async_pixel_transfer_manager_->CreatePixelTransferDelegate(texture_ref, tex_params); ...... delegate->AsyncTexImage2D( tex_params, mem_params, base::Bind(&TextureManager::SetLevelInfoFromParams, // The callback is only invoked if the transfer delegate still // exists, which implies through manager->texture_ref->state // ownership that both of these pointers are valid. base::Unretained(texture_manager()), base::Unretained(texture_ref), tex_params)); return error::kNoError; }
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM首先检查正在处理的gles2::cmds::AsyncTexImage2DCHROMIUM命令是否带有一个异步Token。如果带有的话,这个异步Token就保存在本地变量async_upload_token中,并且它的值不等于0。这时候GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM会调用另外一个成员函数AsyncUploadTokenCompletionClosure创建一个Closure,如下所示:
base::Closure GLES2DecoderImpl::AsyncUploadTokenCompletionClosure( uint32 async_upload_token, uint32 sync_data_shm_id, uint32 sync_data_shm_offset) { scoped_refptr<gpu::Buffer> buffer = GetSharedMemoryBuffer(sync_data_shm_id); ...... AsyncMemoryParams mem_params(buffer, sync_data_shm_offset, sizeof(AsyncUploadSync)); scoped_refptr<AsyncUploadTokenCompletionObserver> observer( new AsyncUploadTokenCompletionObserver(async_upload_token)); return base::Bind( &AsyncPixelTransferManager::AsyncNotifyCompletion, base::Unretained(GetAsyncPixelTransferManager()), mem_params, observer); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
参数sync_data_shm_id和sync_data_shm_offset也是从gles2::cmds::AsyncTexImage2DCHROMIUM命令传递过来的。从前面的分析可以知道,参数sync_data_shm_id描述的是一个共享内存块ID,而参数sync_data_shm_offset描述的是上述共享内存块的一个偏移值。位于这个偏移位置的那部分地址空间被当作一个AsyncUploadSync对象使用。GPU进程的Client端正是通过这个AsyncUploadSync对象检查GPU进程是否已经异步上传完成某一个纹理的。为了完成这个任务,GPU进程需要在异步上传完一个纹理后,将与该纹理关联的异步Token值写入到上述AsyncUploadSync对象中去。
GLES2DecoderImpl类的成员函数AsyncUploadTokenCompletionClosure首先是调用成员函数GetSharedMemoryBuffer获得参数sync_data_shm_id描述的共享内存,然后将这个共享内存与参数sync_data_shm_offset封装在一个AsyncMemoryParams对象。以后通过这个AsyncMemoryParams对象就可以构造出一个AsyncUploadSync对象,进而将参数async_upload_token描述的异步Token值设置到它里面去。
GLES2DecoderImpl类的成员函数AsyncUploadTokenCompletionClosure接下来又将参数async_upload_token描述的异步Token值封装成一个AsyncUploadTokenCompletionObserver对象中。
GLES2DecoderImpl类的成员函数AsyncUploadTokenCompletionClosure再接下来又会调用成员函数GetAsyncPixelTransferManager获得一个AsyncPixelTransferManager对象,如下所示:
AsyncPixelTransferManager* GLES2DecoderImpl::GetAsyncPixelTransferManager() { return async_pixel_transfer_manager_.get(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数GetAsyncPixelTransferManager返回的是成员变量async_pixel_transfer_manager_描述的一个AsyncPixelTransferManager对象,这个AsyncPixelTransferManager是在GLES2DecoderImpl类的成员函数Initialize中创建的,如下所示:
bool GLES2DecoderImpl::Initialize( const scoped_refptr<gfx::GLSurface>& surface, const scoped_refptr<gfx::GLContext>& context, bool offscreen, const gfx::Size& size, const DisallowedFeatures& disallowed_features, const std::vector<int32>& attribs) { ...... async_pixel_transfer_manager_.reset( AsyncPixelTransferManager::Create(context.get())); ...... return true; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
从这里可以看到,GLES2DecoderImpl类的成员函数Initialize又是通过调用AsyncPixelTransferManager类的静态成员函数Create创建成员变量async_pixel_transfer_manager_描述的AsyncPixelTransferManager对象的,如下所示:
AsyncPixelTransferManager* AsyncPixelTransferManager::Create( gfx::GLContext* context) { TRACE_EVENT0("gpu", "AsyncPixelTransferManager::Create"); switch (gfx::GetGLImplementation()) { case gfx::kGLImplementationEGLGLES2: DCHECK(context); if (context->HasExtension("EGL_KHR_fence_sync") && context->HasExtension("EGL_KHR_image") && context->HasExtension("EGL_KHR_image_base") && context->HasExtension("EGL_KHR_gl_texture_2D_image") && context->HasExtension("GL_OES_EGL_image") && !IsBroadcom() && !IsImagination() && !IsNvidia31() && !base::android::SysUtils::IsLowEndDevice()) { return new AsyncPixelTransferManagerEGL; } return new AsyncPixelTransferManagerIdle; case gfx::kGLImplementationOSMesaGL: return new AsyncPixelTransferManagerIdle; case gfx::kGLImplementationMockGL: return new AsyncPixelTransferManagerStub; default: NOTREACHED(); return NULL; } }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_android.cc。
在前面Chromium的GPU进程启动过程分析一文中,我们提到,在Android平台上,GPU进程加载的OpenGL实现是gfx::kGLImplementationEGLGLES2。这时候AsyncPixelTransferManager类的静态成员函数Create返回的要么是一个AsyncPixelTransferManagerEGL对象,要么是一个AsyncPixelTransferManagerIdle对象。如果参数描述的一个OpenGL上下文支持EGL_KHR_fence_sync、EGL_KHR_image、HR_image_base、EGL_KHR_gl_texture_2D_image和GL_OES_EGL_image五种扩展,并且设备配置的GPU不是Broadcom、Imagination或者Nvidia生成的,以及当前设备不是一个低端设备,那么返回的就是一个AsyncPixelTransferManagerEGL对象。否是的话,返回的就是一个AsyncPixelTransferManagerIdle对象。
我们假设AsyncPixelTransferManager类的静态成员函数Create返回的是一个AsyncPixelTransferManagerEGL对象,这意味着GLES2DecoderImpl类的成员变量async_pixel_transfer_manager_指向的是一个AsyncPixelTransferManagerEGL对象。
返回到GLES2DecoderImpl类的成员函数AsyncUploadTokenCompletionClosure中,我们就可以知道。它返回的一个Closure对象绑定了成员变量async_pixel_transfer_manager_指向的AsyncPixelTransferManagerEGL对象的成员函数AsyncNotifyCompletion。当这个AsyncPixelTransferManagerEGL对象的成员函数AsyncNotifyCompletion被调用时,会得到两个参数。一个参数是一个AsyncMemoryParams对象,通过这个AsyncMemoryParams对象可以构造一个AsyncUploadSync对象。另一个参数是一个AsyncUploadTokenCompletionObserver对象,这个AsyncUploadTokenCompletionObserver对象封装了一个异步Token值。
返回到GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM中,通过成员函数AsyncUploadTokenCompletionClosure得到的Closure对象赋值给了一个ScopedClosureRunner对象。当这个ScopedClosureRunner对象超出其生命周期范围之后,也就是在GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM调用结束并且在返回之前,就会自动执行成员函数AsyncUploadTokenCompletionClosure得到的Closure对象,也就是执行GLES2DecoderImpl类的成员变量async_pixel_transfer_manager_指向的AsyncPixelTransferManagerEGL对象的成员函数AsyncNotifyCompletion。
GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM接下来调用成员函数GetSharedMemoryAs,根据从gles2::cmds::AsyncTexImage2DCHROMIUM命令传递过来的三个参数pixels_shm_id、pixels_shm_offset和pixels_size获得一个共享缓冲区。从前面的分析可以知道,这个共享缓冲区保存的就是即将要异步上传的纹理数据。
GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM接下来通过调用成员函数texture_manager获得一个TextureManager对象,并且调用这个TextureManager对象的成员函数ValidateTexImage2D检查从gles2::cmds::AsyncTexImage2DCHROMIUM命令传递过来的纹理数据缓冲区和其它参数的合法性。通过合法性检查之后,TextureManager类的成员函数ValidateTexImage2D就会根据从gles2::cmds::AsyncTexImage2DCHROMIUM命令传递过来的target参数获得一个TextureRef对象。这个TextureRef对象描述的是一个纹理,GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM接下来要做的事情就是为它上传纹理数据到GPU去。
GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM接下来调用成员变量async_pixel_transfer_manager_描述的一个AsyncPixelTransferManagerEGL对象的成员函数CreatePixelTransferDelegate创建了一个AsyncPixelTransferDelegate对象。
AsyncPixelTransferManagerEGL类的成员函数CreatePixelTransferDelegate是从父类AsyncPixelTransferManager继承下来的,它的实现如下所示:
AsyncPixelTransferDelegate* AsyncPixelTransferManager::CreatePixelTransferDelegate( gles2::TextureRef* ref, const AsyncTexImage2DParams& define_params) { ...... AsyncPixelTransferDelegate* delegate = CreatePixelTransferDelegateImpl(ref, define_params); delegate_map_[ref] = make_linked_ptr(delegate); ...... return delegate; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager.cc中。
AsyncPixelTransferManager类的成员函数CreatePixelTransferDelegate又调用了由子类AsyncPixelTransferManagerEGL实现的成员函数CreatePixelTransferDelegateImpl创建了一个AsyncPixelTransferDelegate对象。创建出来的AsyncPixelTransferDelegate对象在返回给调用者之前,会以参数ref描述的TextureRef对象地址为键值,保存在AsyncPixelTransferManager类的成员变量delegate_map_描述的一个Hash Map中,以便以后可以进行查询。
AsyncPixelTransferManagerEGL类的成员函数CreatePixelTransferDelegateImpl的实现如下所示:
AsyncPixelTransferDelegate* AsyncPixelTransferManagerEGL::CreatePixelTransferDelegateImpl( gles2::TextureRef* ref, const AsyncTexImage2DParams& define_params) { return new AsyncPixelTransferDelegateEGL( &shared_state_, ref->service_id(), define_params); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
AsyncPixelTransferManagerEGL类的成员函数CreatePixelTransferDelegateImpl返回的是一个AsyncPixelTransferDelegateEGL对象。注意,在创建这个AsyncPixelTransferDelegateEGL对象的时候,使用的第二个参数是一个纹理ID。这个纹理ID是通过调用参数ref描述的一个TextureRef对象的成员函数service_id获得的。后面上传纹理数据时需要用到这个纹理ID。
返回到GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM中,这意味着它通过调用成员变量async_pixel_transfer_manager_描述的一个AsyncPixelTransferManagerEGL对象的成员函数CreatePixelTransferDelegate获得的是一个AsyncPixelTransferDelegateEGL对象。有了这个AsyncPixelTransferDelegateEGL对象之后,GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM就调用它的成员函数AsyncTexImage2D执行异步纹理操作。
在调用上述AsyncPixelTransferDelegateEGL对象的成员函数AsyncTexImage2D的时候,传递了两个重要参数。第一个参数是一个AsyncTexImage2DParams对象。这个AsyncTexImage2DParams对象封装了一些纹理信息,例如纹理Target、高度、宽度和像素格式等信息。第二个参数是一个AsyncMemoryParams对象。这个AsyncMemoryParams对象封装了保存了纹理数据的一个共享缓冲区的信息。
AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D的实现如下所示:
void AsyncPixelTransferDelegateEGL::AsyncTexImage2D( const AsyncTexImage2DParams& tex_params, const AsyncMemoryParams& mem_params, const base::Closure& bind_callback) { ...... shared_state_->pending_allocations.push_back(AsWeakPtr()); ...... transfer_message_loop_proxy()->PostTask(FROM_HERE, base::Bind( &TransferStateInternal::PerformAsyncTexImage2D, state_, tex_params, mem_params, shared_state_->texture_upload_stats)); ...... }
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D首先将正在处理的AsyncPixelTransferDelegateEGL对象保存成员变量shared_state_指向的一个AsyncPixelTransferManagerEGL::SharedState对象的成员变量pending_allocations描述的一个std::list中。
AsyncPixelTransferDelegateEGL类的成员变量shared_state_是在构造函数中初始化的,如下所示:
AsyncPixelTransferDelegateEGL::AsyncPixelTransferDelegateEGL( AsyncPixelTransferManagerEGL::SharedState* shared_state, GLuint texture_id, const AsyncTexImage2DParams& define_params) : shared_state_(shared_state) { ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
从前面的分析可以知道,AsyncPixelTransferDelegateEGL对象是在AsyncPixelTransferManagerEGL类的成员函数CreatePixelTransferDelegateImpl中创建的。在创建的时候,传递进来的参数shared_state指向的AsyncPixelTransferManagerEGL::SharedState对象与AsyncPixelTransferManagerEGL类的成员变量shared_state_指向的AsyncPixelTransferManagerEGL::SharedState对象是相同的。这意味着AsyncPixelTransferDelegateEGL类的成员变量shared_state_和AsyncPixelTransferManagerEGL类的成员变量shared_state_指向的是相同的AsyncPixelTransferManagerEGL::SharedState对象。
回到AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D中,我们就可以知道,所有正在执行异步纹理数据上传的AsyncPixelTransferDelegateEGL对象实际上是保存在AsyncPixelTransferManagerEGL类的成员变量shared_state_指向的一个AsyncPixelTransferManagerEGL::SharedState对象的成员变量pending_allocations描述的一个std::list中。
AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D中接下来调用全局函数transfer_message_loop_proxy获得一个MessageLoopProxy对象,如下所示:
base::LazyInstance<TransferThread> g_transfer_thread = LAZY_INSTANCE_INITIALIZER; base::MessageLoopProxy* transfer_message_loop_proxy() { return g_transfer_thread.Pointer()->message_loop_proxy().get(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
函数transfer_message_loop_proxy返回的MessageLoopProxy对象是通过调用全局变量g_tranfser_thread描述的一个TransferThread对象的成员函数message_loop_proxy获得的。TransferThread类是从base::Thread类继承下来的,它描述的是一个专门用来上传纹理的GPU线程,即图4所示的GPU传输线程,它的成员函数message_loop_proxy也是从类base::Thread继承下来的。
结合前面Chromium多线程模型设计和实现分析一文,我们可以知道,通过函数transfer_message_loop_proxy返回的MessageLoopProxy对象可以向上述GPU传输线程的消息队列发送消息,该消息最后会在GPU传输线程中执行。因此,AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D中最后所做的事情就是向GPU传输线程的消息队列发送一个消息,该消息绑定了成员变量state_描述的一个TransferStateInternal对象的成员函数PerformAsyncTexImage2D。这意味着接下来TransferStateInternal类的成员函数PerformAsyncTexImage2D会被调用。
AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D执行结束后,返回到GLES2DecoderImpl类的成员函数HandleAsyncTexImage2DCHROMIUM中,这时候我们在前面提到的ScopedClosureRunner对象就会超出其生命周期范围,这时候它所封装的一个Closure对象就会被执行。由于这个Closure对象绑定的是GLES2DecoderImpl类的成员变量async_pixel_transfer_manager_Closure描述的一个AsyncPixelTransferManagerEGL对象的成员函数AsyncNotifyCompletion,因此接下来AsyncPixelTransferManagerEGL类的成员函数AsyncNotifyCompletion就会被调用,如下所示:
void AsyncPixelTransferManagerEGL::AsyncNotifyCompletion( const AsyncMemoryParams& mem_params, AsyncPixelTransferCompletionObserver* observer) { // Post a PerformNotifyCompletion task to the upload thread. This task // will run after all async transfers are complete. transfer_message_loop_proxy()->PostTask( FROM_HERE, base::Bind(&PerformNotifyCompletion, mem_params, make_scoped_refptr(observer))); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
AsyncPixelTransferManagerEGL类的成员函数AsyncNotifyCompletion与前面分析的AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D一样,都是先通过调用全局函数transfer_message_loop_proxy获得一个MessageLoopProxy对象,然后通过这个MessageLoopProxy对象向一个线程的消息队列发送一个消息,也就是向全局变量g_transfer_thread描述的一个GPU传输线程的消息队列发送一个消息,该消息绑定的函数为全局函数PerformNotifyCompletion。
这意味着此时在GPU传输线程的消息队列中有两个待处理消息。其中,排在前面的消息绑定的函数是TransferStateInternal类的成员函数PerformAsyncTexImage2D,排在后面的消息绑定的函数是全局函数PerformNotifyCompletion。因此,接下来TransferStateInternal类的成员函数PerformAsyncTexImage2D和全局函数PerformNotifyCompletion就会在GPU传输线程中先后被执行。
在分析TransferStateInternal类的成员函数PerformAsyncTexImage2D和全局函数PerformNotifyCompletion的执行过程之前,我们先分析全局变量g_transfer_thread描述的GPU传输线程的初始化过程。从前面Chromium多线程模型设计和实现分析一文可以知道,全局变量g_transfer_thread描述的GPU传输线程在创建之后,会调用TransferThread类的成员函数Init执行初始化工作,如下所示:
class TransferThread : public base::Thread { public: ...... virtual void Init() OVERRIDE { gfx::GLShareGroup* share_group = NULL; surface_ = new gfx::PbufferGLSurfaceEGL(gfx::Size(1, 1)); surface_->Initialize(); context_ = gfx::GLContext::CreateGLContext( share_group, surface_.get(), gfx::PreferDiscreteGpu); bool is_current = context_->MakeCurrent(surface_.get()); DCHECK(is_current); } ...... private: scoped_refptr<gfx::GLContext> context_; scoped_refptr<gfx::GLSurface> surface_; ...... };
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
TransferThread类的成员函数Init所做的事情就是初始化一个OpenGL环境:先是调用函数gfx::PbufferGLSurfaceEGL创建一个基于Pbuffer的Surface,接着调用函数gfx::GLContext类的静态成员函数CreateGLContext基于上述Surface创建一个OpenGL上下文,最将将上述OpenGL上下文设置为当前线程(即GPU传输线程)的OpenGL上下文。
设置了OpenGL上下文之后,GPU传输线程就可以在TransferStateInternal类的成员函数PerformAsyncTexImage2D中执行上传纹理的操作了,如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... void PerformAsyncTexImage2D( AsyncTexImage2DParams tex_params, AsyncMemoryParams mem_params, scoped_refptr<AsyncPixelTransferUploadStats> texture_upload_stats) { ...... void* data = mem_params.GetDataAddress(); ...... { ...... glGenTextures(1, &thread_texture_id_); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, thread_texture_id_); SetGlParametersForEglImageTexture(); // If we need to use image_preserved, we pass the data with // the allocation. Otherwise we use a NULL allocation to // try to avoid any costs associated with creating the EGLImage. if (use_image_preserved_) DoTexImage2D(tex_params, data); else DoTexImage2D(tex_params, NULL); } CreateEglImageOnUploadThread(); { ...... // If we didn‘t use image_preserved, we haven‘t uploaded // the data yet, so we do this with a full texSubImage. if (!use_image_preserved_) DoFullTexSubImage2D(tex_params, data); } WaitForLastUpload(); MarkAsCompleted(); ...... } ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
TransferStateInternal类的成员函数PerformAsyncTexImage2D首先调用参数mem_params描述的一个AsyncMemoryParams对象的成员函数GetDataAddress获得保存了纹理数据的共享缓冲区的地址,保存在本地变量data中。
TransferStateInternal类的成员函数PerformAsyncTexImage2D接着创建了一个纹理对象,该纹理对象的ID保存在成员变量thread_texture_id_中,并且调用另外一个成员函数SetGlParametersForEglImageTexture设置该纹理对象的参数。
再接下来,TransferStateInternal类的成员函数PerformAsyncTexImage2D根据成员变量use_image_preserved_的值使用两种不同的方式将本地变量data描述的共享缓冲区作为前面创建的纹理的数据上传到GPU中,以及根据该纹理创建一个EGLImageKHR对象。
当成员变量use_image_preserved_等于true的时候,TransferStateInternal类的成员函数PerformAsyncTexImage2D首先调用函数DoTexImage2D上传纹理数据,接着再调用成员函数CreateEglImageOnUploadThread根据该纹理创建一个EGL_IMAGE_PRESERVED_KHR属性为EGL_TRUE的EGLImageKHR对象。
函数DoTexImage2D的实现如下所示:
void DoTexImage2D(const AsyncTexImage2DParams& tex_params, void* data) { glTexImage2D( GL_TEXTURE_2D, tex_params.level, tex_params.internal_format, tex_params.width, tex_params.height, tex_params.border, tex_params.format, tex_params.type, data); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
从这里可以看到,函数DoTexImage2D是通过调用OpenGL函数glTexImage2D将纹理数据上传到GPU的。
TransferStateInternal类的成员函数CreateEglImageOnUploadThread的实现如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... void CreateEglImageOnUploadThread() { CreateEglImage(thread_texture_id_); } ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
TransferStateInternal类的成员函数CreateEglImageOnUploadThread调用另外一个成员函数CreateEglImage根据成员变量thread_texture_id_描述的纹理创建一个EGLImageKHR对象,如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... void CreateEglImage(GLuint texture_id) { ...... EGLDisplay egl_display = eglGetCurrentDisplay(); EGLContext egl_context = eglGetCurrentContext(); EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR; EGLClientBuffer egl_buffer = reinterpret_cast<EGLClientBuffer>(texture_id); EGLint image_preserved = use_image_preserved_ ? EGL_TRUE : EGL_FALSE; EGLint egl_attrib_list[] = { EGL_GL_TEXTURE_LEVEL_KHR, 0, // mip-level. EGL_IMAGE_PRESERVED_KHR, image_preserved, EGL_NONE }; egl_image_ = eglCreateImageKHR( egl_display, egl_context, egl_target, egl_buffer, egl_attrib_list); ...... } ...... protected: ...... EGLImageKHR egl_image_; ...... };
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
TransferStateInternal类的成员函数CreateEglImage是通过调用EGL函数eglCreateImageKHR以参数texture_id描述的纹理为Source,创建一个EGLImageKHR对象,并且保存在成员变量gel_image_中。这时候由于TransferStateInternal类的成员变量use_image_preserved_的值是等于true的,因此在调用EGL函数eglCreateImageKHR创建EGLImageKHR对象时,将该EGLImageKHR对象的EGL_IMAGE_PRESERVED_KHR属性值指定为EGL_TRUE,表示在EGL函数eglCreateImageKHR调用结束后,参数texture_id描述的纹理的数据将会被保留,即它的数据不会被修改。
另一方面,如果TransferStateInternal类的成员变量use_image_preserved_的值等于false,那么调用EGL函数eglCreateImageKHR创建出来的EGLImageKHR对象的GL_IMAGE_PRESERVED_KHR属性值指定为EGL_FALSE,表示在EGL函数eglCreateImageKHR调用结束后,参数texture_id描述的纹理的数据将会是未定义的,即它的数据可能会被修改。
根据官方文档,在调用EGL函数eglCreateImageKHR创建EGLImageKHR对象时,将EGL_IMAGE_PRESERVED_KHR属性值指定为EGL_FALSE,会使得EGLImageKHR对象的创建效率更高,因为这会使得EGL函数eglCreateImageKHR的实现以更灵活的方式来创建EGLImageKHR对象,例如就地使用参数texture_id描述的纹理的数据缓冲区来创建EGLImageKHR对象,避免重新分配一块新的缓冲区,并且将源数据拷贝过去。将EGL_IMAGE_PRESERVED_KHR属性值指定为EGL_TRUE,就无法做到这一点,因为这时候需要保证源数据的不变性。出于性能考虑,在调用EGL函数eglCreateImageKHR创建EGLImageKHR对象时,如果没有指定EGL_IMAGE_PRESERVED_KHR属性的值,那么它的默认值就为EGL_FALSE。
EGLImageKHR对象与一般的OpenGL对象的不同之处在于它可以在不同的OpenGL上下文共享。也就是说,在一个OpenGL上下文中创建的EGLImageKHR对象,可以在另外一个OpenGL上下文中使用。我们最常见的用法就是,在一个OpenGL上下文中创建一个OpenGL纹理,并且以这个OpenGL纹理为Source,创建一个EGLImageKHR对象,然后再将这个EGLImageKHR对象交给另外一个OpenGL上下文,另外一个OpenGL上下文再将该EGLImageKHR对象封装成一个OpenGL纹理来使用。这两个OpenGL上下文可以是在不同的线程中创建的。正是这种机制,使用Chromium可以实现异步纹理上传。事实上,通过EGLImageKHR对象,不仅可以在不同线程的OpenGL上下文之间实现纹理共享,还可以在不在进程的OpenGL上下文之间实现纹理共享。我们在前面Android应用程序UI硬件加速渲染的预加载资源地图集服务(Asset Atlas Service)分析一文中分析的地图集纹理就是通过EGLImageKHR对象实现在不同进程之间的、GPU级别的资源共享的。同时,Android系统的SurfaceFlinger进程在合成应用程序进程的窗口UI时,也就是通过EGLImageKHR对象来访问代表了应用程序窗口UI的图形缓冲区数据的。
尽管在调用EGL函数eglCreateImageKHR创建EGLImageKHR对象时,将EGL_IMAGE_PRESERVED_KHR属性值指定为EGL_FALSE,会使得创建EGLImageKHR对象的效率更高,但是某些GPU,例如Qualcomm和Imagination公司生产的GPU,通过这种方式创建出来的EGLImageKHR对象在不同OpenGL上下文之间进行共享时,会出现竞争条件,使得根据该EGLImageKHR对象创建出来的纹理是黑的。作为work-around,针对Qualcomm和Imagination公司生产的GPU,Chromium在调用EGL函数eglCreateImageKHR创建EGLImageKHR对象时,就会将EGL_IMAGE_PRESERVED_KHR属性值指定为EGL_TRUE,而对于其余公司的GPU,则会将EGL_IMAGE_PRESERVED_KHR属性值指定为EGL_FALSE。
回到TransferStateInternal类的成员函数PerformAsyncTexImage2D中,当成员变量use_image_preserved_的值等于false时,在调用EGL函数eglCreateImageKHR根据成员变量thread_texture_id_描述的纹理创建EGLImageKHR对象时,会将该EGLImageKHR对象的GL_IMAGE_PRESERVED_KHR属性值指定为EGL_FALSE,因此效率就会更高。
在成员变量use_image_preserved_的值等于false的情况下,为了进一步提高创建EGLImageKHR对象的效率,TransferStateInternal类的成员函数PerformAsyncTexImage2D在调用EGL函数eglCreateImageKHR之前,不会为成员变量thread_texture_id_描述的纹理上传数据,这样可以进一步减少在创建EGLImageKHR对象时的需要的数据操作,因为这时候作为Source的纹理还没有上传数据。不过当创建完成EGLImageKHR对象之后,我们仍然是需要为成员变量thread_texture_id_描述的纹理上传数据的,这是通过调用另外一个函数DoFullTexSubImage2D实现的,如下所示:
void DoFullTexSubImage2D(const AsyncTexImage2DParams& tex_params, void* data) { glTexSubImage2D( GL_TEXTURE_2D, tex_params.level, 0, 0, tex_params.width, tex_params.height, tex_params.format, tex_params.type, data); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
更确切地说,我们是通过调用OpenGL函数glTexSubImage2D来上传纹理数据的,而不是像之前一样,通过调用OpenGL函数glTexImage2D来上传纹理数据。根据官方文档,如果我们之前根据一个纹理调用EGL函数eglCreateImageKHR创建了一个EGLImageKHR对象,那么以后当我们调用OpenGL函数glTexSubImage2D更新纹理的数据时,更新后的数据会同时被之前创建的EGLImageKHR对象看到。这样就与我们前面分析的,先上传纹理的数据,再根据纹理创建EGLImageKHR对象,达到的效果是一样的,但是效率会更高。
回到TransferStateInternal类的成员函数PerformAsyncTexImage2D中,完成了上传纹理数据和根据纹理创建EGLImageKHR对象的操作之后,接下来还要完成两个操作。第一个操作是在当前线程中,即在GPU传输线程中,确保纹理上传操作已经完成。第二个操作是将一个WaitableEvent对象设置为Signaled,以便通知正在等待异步纹理上传完成的Client,异步纹理上传操作已完成。
第一个操作是通过调用TransferStateInternal类的成员函数WaitForLastUpload实现的,如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... void WaitForLastUpload() { // This glFinish is just a safe-guard for if uploads have some // GPU action that needs to occur. We could use fences and try // to do this less often. However, on older drivers fences are // not always reliable (eg. Mali-400 just blocks forever). if (wait_for_uploads_) { TRACE_EVENT0("gpu", "glFinish"); glFinish(); } } ...... };
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
一般来说,从OpenGL函数glTexImage2D或者glTexSubImage2D返回后,纹理数据就会上传完成。但是也有可能GPU在执行glTexImage2D或者glTexSubImage2D命令之后,还需要其它的一些命令,才能确保纹理数据上传完成。这时候就需要调用OpenGL函数glFinish等待这些命令执行完成。但是有些GPU调用OpenGL函数glFinish需要等待的时间实在是太长,例如Imagination公司的GPU,这时候TransferStateInternal类的成员变量wait_for_uploads_就会被设置为false,因此就不会调用OpenGL函数glFinish进行等待。
第二个操作是通过调用TransferStateInternal类的成员函数MarkAsCompleted实现的,如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... void MarkAsCompleted() { ...... transfer_completion_.Signal(); } protected: ...... base::WaitableEvent transfer_completion_; ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
TransferStateInternal类的成员函数MarkAsCompleted将成员变量transfer_completion_描述的一个WaitableEvent的状态设置为Signaled。这时候如果有Client正在调用TransferStateInternal类的成员函数WaitForTransferCompletion等待纹理异步上传完成,那么这个Client就会被唤醒,如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... void WaitForTransferCompletion() { ...... transfer_completion_.Wait(); } ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
如果Client在纹理异步上传完成之后调用TransferStateInternal类的成员函数WaitForTransferCompletion,那么就不必进行等待,而直接从TransferStateInternal类的成员函数WaitForTransferCompletion返回。
前面提到,GPU传输线程的消息队列中有两个待处理消息。一个是上传纹理的消息,现在已经处理完毕。因此,GPU传输线程接下来就开始处理第二个消息。这个消息绑定的函数是一个全局函数PerformNotifyCompletion,它的实现如下所示:
void PerformNotifyCompletion( AsyncMemoryParams mem_params, scoped_refptr<AsyncPixelTransferCompletionObserver> observer) { TRACE_EVENT0("gpu", "PerformNotifyCompletion"); observer->DidComplete(mem_params); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
从前面分析的GLES2DecoderImpl类的成员函数AsyncUploadTokenCompletionClosure可以知道,参数observer描述的是一个AsyncUploadTokenCompletionObserver对象,函数PerformNotifyCompletion接下来调用它的成员函数DidComplete通知它纹理上传完成了。
AsyncUploadTokenCompletionObserver类的成员函数DidComplete的实现如下所示:
class AsyncUploadTokenCompletionObserver : public AsyncPixelTransferCompletionObserver { public: explicit AsyncUploadTokenCompletionObserver(uint32 async_upload_token) : async_upload_token_(async_upload_token) { } virtual void DidComplete(const AsyncMemoryParams& mem_params) OVERRIDE { ...... void* data = mem_params.GetDataAddress(); AsyncUploadSync* sync = static_cast<AsyncUploadSync*>(data); sync->SetAsyncUploadToken(async_upload_token_); } private: ...... uint32 async_upload_token_; ...... };
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
参数mem_params描述的AsyncMemoryParams对象是在前面分析的GLES2DecoderImpl类的成员函数AsyncUploadTokenCompletionClosure里面创建的,通过它可以获得一个AsyncUploadSync对象。这个AsyncUploadSync对象描述的是一个共享缓冲区,这个共享缓冲区是在GPU进程的Client端创建的,也就是在前面分析的GLES2Implementation类的成员函数EnsureAsyncUploadSync里面创建的。通过调用这个AsyncUploadSync对象的成员函数SetAsyncUploadToken将可以AsyncUploadTokenCompletionObserver类的成员变量async_upload_token_描述的异步Token值设置到上述共享缓冲区去,表示与该异步Token值关联的纹理数据缓冲区已经使用完毕,GPU进程的Client端可以对它进行回收了。
前面分析GLES2Implementation类的成员函数RemoveTransferBuffer时提到,它在回收一个共享缓冲区之前,会首先判断该共享缓冲区是否设置了一个异步Token值。如果设置了,那么就会调用GLES2Implementation类的成员函数HasAsyncUploadTokenPassed判断该当前OpenGL上下文的异步Token值是否大于等于该异步Token值。如果大于等于的话,那么就说明指定的共享缓冲区可以进行回收了。
GLES2Implementation类的成员函数HasAsyncUploadTokenPassed的实现如下所示:
class GLES2_IMPL_EXPORT GLES2Implementation : NON_EXPORTED_BASE(public GLES2Interface), NON_EXPORTED_BASE(public ContextSupport) { ...... private: ...... bool HasAsyncUploadTokenPassed(uint32 token) const { return async_upload_sync_->HasAsyncUploadTokenPassed(token); } ...... };这个函数定义在文件external/chromium_org/gpu/command_bufferclient/gles2_implementation.h中。
GLES2Implementation类的成员变量async_upload_sync_指向的AsyncUploadSync对象与前面分析的AsyncUploadTokenCompletionObserver类的成员函数DidComplete时提到的AsyncUploadSync对象描述的是同一个共享缓冲区,因此,GLES2Implementation类的成员函数HasAsyncUploadTokenPassed通过调用它的成员函数HasAsyncUploadTokenPassed就可以知道与参数token对应的共享缓冲区是否可以进行回收。AsyncUploadSync类的成员函数HasAsyncUploadTokenPassed的实现我们在前面已经分析过了,这里不再复述。
GLES2Implementation类也提供了一个成员函数PollAsyncUploads,MappedMemoryManager类在分配新的共享缓冲区的过程中,就会调用它来检查有哪些用于异步纹理数据上传的共享缓冲区可以进行回收。
GLES2Implementation类的成员函数PollAsyncUploads的实现如下所示:
void GLES2Implementation::PollAsyncUploads() { ...... DetachedAsyncUploadMemoryList::iterator it = detached_async_upload_memory_.begin(); while (it != detached_async_upload_memory_.end()) { if (HasAsyncUploadTokenPassed(it->second)) { mapped_memory_->Free(it->first); it = detached_async_upload_memory_.erase(it); } else { break; } } }这个函数定义在文件external/chromium_org/gpu/command_bufferclient/gles2_implementation.cc中。
前面分析GLES2Implementation类的成员函数RemoveTransferBuffer时提到,那些用于异步纹理数据上传的共享缓冲区在纹理数据还没有异步上传完成之前,会保存在GLES2Implementation类的成员变量detached_async_upload_memory_描述的一个std::list中。GLES2Implementation类的成员函数PollAsyncUploads遍历保存在这个std::list中的共享缓冲区,并且调用前面分析过的GLES2Implementation类的成员函数HasAsyncUploadTokenPassed检查它们关联的异步Token值是否已经大于等于记录在当前OpenGL上下文中的异步Token值。如果大于等于的话,那么就会调用成员变量mapped_memory_描述的一个MappedMemoryManager对象的成员函数Free对它们进行回收。
GPU进程的Client端请求GPU进程执行一个异步纹理上传操作之后,有可能需要等待该纹理上传操作完成,以便可以执行接下来与该纹理相关的操作。GLES2Implementation类的提供了一个成员函数WaitAsyncTexImage2DCHROMIUM,用来等待一个异步纹理上传操作完成,它的实现如下所示:
void GLES2Implementation::WaitAsyncTexImage2DCHROMIUM(GLenum target) { ...... helper_->WaitAsyncTexImage2DCHROMIUM(target); ...... }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
参数target描述的是要等待数据异步上传完成的纹理对象。GLES2Implementation类的成员函数WaitAsyncTexImage2DCHROMIUM通过调用成员变量helper_描述的一个GLES2CmdHelper对象的成员函数WaitAsyncTexImage2DCHROMIUM将这个参数封装在一个gles2:cmds::WaitAsyncTexImage2DCHROMIUM命令中发送给GPU进程处理。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程将gles2:cmds::WaitAsyncTexImage2DCHROMIUM命令分发给GLES2DecoderImpl类的成员函数HandleWaitAsyncTexImage2DCHROMIUM处理,处理过程如下所示:
error::Error GLES2DecoderImpl::HandleWaitAsyncTexImage2DCHROMIUM( uint32 immediate_data_size, const cmds::WaitAsyncTexImage2DCHROMIUM& c) { ...... GLenum target = static_cast<GLenum>(c.target); ...... TextureRef* texture_ref = texture_manager()->GetTextureInfoForTarget( &state_, target); ...... AsyncPixelTransferDelegate* delegate = async_pixel_transfer_manager_->GetPixelTransferDelegate(texture_ref); if (!delegate) { ...... return error::kNoError; } ...... delegate->WaitForTransferCompletion(); ProcessFinishedAsyncTransfers(); return error::kNoError; }
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数HandleWaitAsyncTexImage2DCHROMIUM首先从传递过来的gles2:cmds::WaitAsyncTexImage2DCHROMIUM命令获得一个target值。该target值在GPU进程中对应有一个纹理对象。这个纹理对象通过一个TextureRef对象描述。如果该TextureRef对象描述的纹理有进行过异步数据上传操作,那么通过调用GLES2DecoderImpl类的成员变量async_pixel_transfer_manager_描述的一个AsyncPixelTransferDelegate对象的成员函数GetPixelTransferDelegate就可以获得一个AsyncPixelTransferDelegate对象,如下所示:
AsyncPixelTransferDelegate* AsyncPixelTransferManager::GetPixelTransferDelegate( gles2::TextureRef* ref) { TextureToDelegateMap::iterator it = delegate_map_.find(ref); if (it == delegate_map_.end()) { return NULL; } else { return it->second.get(); } }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager.cc中。
前面分析AsyncPixelTransferManager类的成员函数CreatePixelTransferDelegate时提到,成员变量delegate_map_描述的是一个Hash Map,这个Hash Map保存的是AsyncPixelTransferDelegateEGL对象。每一个AsyncPixelTransferDelegateEGL对象都与一个纹理对象对应,即与一个TextureRef对象对应。这些纹理对象之前都是被请求过异步数据上传操作的。
因此,如果参数ref描述的TextureRef对象之前被请求过执行异步数据上传操作,那么AsyncPixelTransferDelegate类的成员函数GetPixelTransferDelegate就会返回一个AsyncPixelTransferDelegateEGL对象。有了这个AsyncPixelTransferDelegateEGL对象之后,GLES2DecoderImpl类的成员函数HandleWaitAsyncTexImage2DCHROMIUM就可以调用它的成员函数WaitForTransferCompletion等待与它对应的纹理对象异步上传数据完成。前面在分析GPU传输线程上传纹理数据的过程时,我们已经分析过AsyncPixelTransferDelegateEGL类的成员函数WaitForTransferCompletion的实现了,因此这里不再复述。
等待结束之后,GLES2DecoderImpl类的成员函数HandleWaitAsyncTexImage2DCHROMIUM调用另外一个成员函数ProcessFinishedAsyncTransfers对已经异步上传数据完成的纹理进行处理,如下所示:
void GLES2DecoderImpl::ProcessFinishedAsyncTransfers() { ...... async_pixel_transfer_manager_->BindCompletedAsyncTransfers(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数ProcessFinishedAsyncTransfers调用成员变量async_pixel_transfer_manager_描述的一个AsyncPixelTransferManagerEGL对象的成员函数BindCompletedAsyncTransfers对已经异步上传完数据的纹理进行处理。
AsyncPixelTransferManagerEGL类的成员函数BindCompletedAsyncTransfers的实现如下所示:
void AsyncPixelTransferManagerEGL::BindCompletedAsyncTransfers() { ...... while(!shared_state_.pending_allocations.empty()) { ...... AsyncPixelTransferDelegateEGL* delegate = shared_state_.pending_allocations.front().get(); // Terminate early, as all transfers finish in order, currently. if (delegate->TransferIsInProgress()) break; ...... // If the transfer is finished, bind it to the texture // and remove it from pending list. delegate->BindTransfer(); shared_state_.pending_allocations.pop_front(); } }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
前面分析AsyncPixelTransferDelegateEGL类的成员函数AsyncTexImage2D时提到,AsyncPixelTransferManagerEGL类的成员变量shared_state_描述的一个AsyncPixelTransferManagerEGL::SharedState对象的成员变量pending_allocations描述的一个std::list保存了那些正在执行异步纹理数据上传操作的AsyncPixelTransferDelegateEGL对象。
AsyncPixelTransferManagerEGL类的成员函数BindCompletedAsyncTransfers依次调用上述AsyncPixelTransferDelegateEGL对象的成员函数TransferIsInProgress检查它们是否已经执行完成异步纹理数据上传操作了,如下所示:
bool AsyncPixelTransferDelegateEGL::TransferIsInProgress() { return state_->TransferIsInProgress(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
AsyncPixelTransferDelegateEGL类的成员函数TransferIsInProgress调用成员变量state_描述的一个TransferStateInternal对象的成员函数TransferIsInProgress检查它负责的纹理数据上传操作是否已经完成,如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... bool TransferIsInProgress() { return !transfer_completion_.IsSignaled(); } ...... };这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
TransferStateInternal类的成员变量transfer_completion_描述的是一个WaitableEvent。前面分析TransferStateInternal类的成员函数MarkAsCompleted时提到,当一个TransferStateInternal对象完成一个纹理数据上传操作后,会将它的成员变量transfer_completion_描述的WaitableEvent的状态设置Signaled。
因此,TransferStateInternal类的成员函数TransferIsInProgress只需要判断成员变量transfer_completion_描述的WaitableEventt的状态是否为Signaled,就可以知道当前正在处理的TransferStateInternal对象所负责的纹理数据上传操作是否已经完成。
回到AsyncPixelTransferManagerEGL类的成员函数BindCompletedAsyncTransfers中,对于已经上传完成纹理数据操作的TransferStateInternal对象来说,它们的成员函数BindTransfer会被调用,执行过程如下所示:
class TransferStateInternal : public base::RefCountedThreadSafe<TransferStateInternal> { public: ...... void BindTransfer() { ...... glBindTexture(GL_TEXTURE_2D, texture_id_); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image_); ...... } ...... protected: ...... // The ‘real‘ texture. GLuint texture_id_; // The EGLImage sibling on the upload thread. GLuint thread_texture_id_; ...... };
这个函数定义在文件external/chromium_org/gpu/command_buffer/service/async_pixel_transfer_manager_egl.cc中。
TransferStateInternal类的成员变量texture_id_描述的是在GPU主线程中创建的一个纹理对象的ID,这个纹理对象就是当前正在处理的TransferStateInternal要为其上传数据的纹理对象。不过,当前正在处理的TransferStateInternal将要上传的数据上传给在GPU传输线程中创建的另外一个纹理对象。这个另外的纹理对象的ID保存在TransferStateInternal类的成员变量thread_texture_id_中。
前面分析TransferStateInternal类的成员函数PerformAsyncTexImage2D时提到,成员变量thread_texture_id_描述的纹理的数据可以通过另外一个成员变量egl_image_描述的一个EGLImageKHR对象进行访问。这时候TransferStateInternal类的成员BindTransfer可以通过调用OpenGL函数glEGLImageTargetTexture2DOES将成员变量egl_image_描述的EGLImageKHR对象关联的数据设置为成员变量texture_id_描述的纹理的数据,相当于就是共享成员变量thread_texture_id_描述的纹理的数据,从而完成成员变量texture_id_描述的纹理的异步数据上传过程。
通过上面的分析,我们就可以知道,异步纹理上传操作需要GPU进程的Client端主动调用GLES2Implementation类的成员函数WaitAsyncTexImage2DCHROMIUM才能完成。如果GPU进程的Client端不调用GLES2Implementation类的成员函数WaitAsyncTexImage2DCHROMIUM,是不是异步纹理上传操作就完成不了呢?答案是否定的。
GPU进程在调度一个OpenGL上下文的时候,如果发现这个OpenGL上下文的GPU命令缓冲区没有更多的命令需要处理时,就会调用GLES2DecoderImpl类的成员函数PerformIdleWork处理一些不重要的工作。
GLES2DecoderImpl类的成员函数PerformIdleWork的实现如下所示:
void GLES2DecoderImpl::PerformIdleWork() { ...... ProcessFinishedAsyncTransfers(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数PerformIdleWork直接调和我们前面分析过的成员函数ProcessFinishedAsyncTransfers处理那些数据上传操作已经完成了的纹理,即通过EGLImageKHR对象把在GPU传输线程中上传的纹理数据交给在GPU主线程中创建的纹理使用。
以上就是异步纹理上传的过程。至此,我们就分析完成了GPU数据从GPU进程的Client端,即WebGL端、Render端和Browser端,传递到GPU进程,再由GPU进程传递给GPU的过程了。其中,异步纹理上传过程是本文的重点,它可以在一定程度上解决纹理上传速度慢的问题。由于纹理上传是使用GPU硬件加速渲染时的一个频繁操作,因此通过异步方式上传显得尤为必要。
此外,将GPU数据从GPU进程的Client端传递到GPU进程涉及到的一个重要问题是共享缓冲区的管理问题。由于用来传递数据的缓冲区是共享的,并且GPU进程的Client端发出命令与GPU进程处理命令是异步进行的,这就给共享缓冲区的回收带来复杂性。Chromium通过引用Token机制,包括同步Token和异步Token机制,解决共享缓冲区的管理问题。
最后,对于那些由于操作系统对共享缓冲区分配有大小限制而无法一次性将数据从GPU进程的Client端传递给GPU进程的那些GPU命令,Chromium提供了Bucket机制来完成这些数据的传输。
在接下来的一篇文章中,我们继续分析GPU进程调度OpenGL上下文的过程。OpenGL上下文之所以要通过调度的方式执行,是因为在Chromium中,所有的OpenGL上下文,也就是每一个WebGL端、Render端和Browser端实例对应的OpenGL上下文,都是运行在GPU进程中的同一个GPU线程中的,因此这个GPU线程需要决定什么时候执行哪一个OpenGL上下文的命令。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。
标签:
原文地址:http://blog.csdn.net/luoshengyang/article/details/49750961