Chromium除了支持网页分块GPU光栅化,还支持CPU光栅化。GPU光栅化的特点是快,缺点是硬件之间可能会导差异性,以及不是所有的绘图操作硬件都能很好地支持。CPU光栅化的特点是通用,以及能够支持所有的绘图操作,缺点是较慢,特别是在网页使用硬件加速渲染的情况下,CPU的光栅化结果还需要上传到GPU去渲染。本文接下来将详细分析CPU光栅化的原理,着重描述它是如何快速地光栅化结果上传到GPU去的。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
从前面Chromium网页GPU光栅化原理分析一文可以知道,网页分块的光栅化过程,实际上是执行之前所记录的分块绘制命令,最终得到一个包含RGB值的图形缓冲区,如下所示:
图1 网页分块的CPU光栅化过程
在CPU光栅化方式中,CPU将网页分块绘制一个图形缓冲区中。这个图形缓冲区需要进一步交给GPU渲染,才能显示在屏幕中。一般情况下,CPU访问的是系统内存,而GPU访问的是显卡内存。CPU将数据提交给GPU,需要执行的一个操作是将数据从系统内存拷贝到显卡内存。这个操作也称为GPU上传操作。
相比于GPU渲染操作,GPU上传操作是一个非常慢的过程。典型的GPU上传操作是纹理上传。纹理数据通常比较大,因此将它们上传到GPU去就会是一个性能问题。网页分块CPU光栅化完成后得到的图形缓冲区就相当于是一个纹理数据。因此在CPU光栅化过程中,也会碰到影响性能的纹理上传问题。
为了解决纹理上传慢的问题,Android平台提供了一种Native Buffer,也称为Graphic Buffer。Graphic Buffer有一个很好的特性,就是它既可以被CPU访问,也可以被GPU访问。因此,如果我们为每一个网页分块都分配一个Graphic Buffer,并且将每一个网页分块都光栅化在各自的Gaphic Buffer中,那么就可以免去将光栅化结果上传给GPU的步骤,从而大大地提高网页的渲染效率。这种CPU光栅化方式就得名为Zero Copy光栅化方式,也称为Image Raster方式。
通常,好的东西都是有代价的。Graphic Buffer也不例外,它不像系统内存一样,可以大量地使用。为了减少Graphic Buffer的使用,Chromium只申请临时使用的Graphic Buffer,并且为每一个网页分配一个纹理对象。每一个网页分块都光栅化在临时使用的Graphic Buffer中,并且在光栅化完成后,会将临时使用的Graphic Buffer的内容拷贝到各自的纹理对象中去,然后将临时使用的Graphic Buffer释放掉。注意,这个拷贝操作是在GPU内部完成的,因此它的执行过程很快,不像纹理上传操作那样存在性能问题。这种CPU光栅化方式就得名为One Copy光栅化方式,也称为Image Copy Raster方式。
有些Android手机,虽然本身提供了Graphic Buffer,但是只允许系统使用,不允许App使用。在这种情况下,Chromium只能将网页分块光栅化在一个CPU能够访问的Pixel Buffer中。光栅化完成之后,再将这个Pixel Buffer上传到GPU去渲染。这样就避不开纹理上传慢的问题了。这种CPU光栅化方称为Pixel Buffer Raster方式。
从前面的分析就可以知道,CPU光栅化方式有三种,分别是Image Raster、Image Copy Raster和Pixel Buffer Raster。接下来,我们分别对它们的实现原理进行分析。
1. Image Raster
从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,当使用Zero Copy光栅化方式时,CC模块会创建一个ImageRasterWorkerPool对象来执行光栅化任务。从前面Chromium网页光栅化过程分析一文又可以知道,在执行光栅化任务的过程中,ImageRasterWorkerPool类的成员函数AcquireCanvasForRaster会被调用来创建一个画布,如下所示:
SkCanvas* ImageRasterWorkerPool::AcquireCanvasForRaster(RasterTask* task) { return resource_provider_->MapImageRasterBuffer(task->resource()->id()); }这个函数定义在文件external/chromium_org/cc/resources/image_raster_worker_pool.cc中。
参数task指向的是一个RasterTaskImpl对象。这个RasterTaskImpl对象描述的是当前要执行的光栅化任务。调用这个RasterTaskImpl对象的成员函数resource可以获得一个Resource对象。这个Resource对象描述的是一个纹理资源。这个纹理资源就是分配给当前要执行光栅化操作的网页分块的。注意,这个纹理资源此时只是分配了一个纹理ID,还没有为其分配纹理储存。
ImageRasterWorkerPool类的成员变量resource_provider_指向的是一个ResourceProvider对象。ImageRasterWorkerPool类的成员函数AcquireCanvasForRaster通过调用这个ResourceProvider对象的成员函数MapImageRasterBuffer根据前面获得的纹理资源创建一个画布返回给调用者。
ResourceProvider类的成员函数MapImageRasterBuffer的实现如下所示:
SkCanvas* ResourceProvider::MapImageRasterBuffer(ResourceId id) { Resource* resource = GetResource(id); AcquireImage(resource); if (!resource->image_raster_buffer.get()) resource->image_raster_buffer.reset(new ImageRasterBuffer(resource, this)); return resource->image_raster_buffer->LockForWrite(); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数MapImageRasterBuffer首先通过调用成员函数GetResource获得参数id描述的纹理资源,然后调用另外一个成员函数AcquireImage为该纹理资源创建一个Graphic Buffer。
为参数id描述的纹理资源创建了Graphic Buffer之后,ResourceProvider类的成员函数MapImageRasterBuffer再将该Graphic Buffer封装在一个ImageRasterBuffer对象中,然后再调用这个ImageRasterBuffer对象的成员函数LockForWrite根据它所封装的Graphic Buffer创建出一个画布来返回给调用者。
接下来,我们先分析ResourceProvider类的成员函数AcquireImage创建Graphic Buffer的过程,接着再分析ImageRasterBuffer类的成员函数LockForWrite根据Graphic Buffer创建画布的过程。
ResourceProvider类的成员函数AcquireImage的实现如下所示:
void ResourceProvider::AcquireImage(Resource* resource) { ...... GLES2Interface* gl = ContextGL(); ...... resource->image_id = gl->CreateImageCHROMIUM(resource->size.width(), resource->size.height(), TextureToStorageFormat(resource->format), GL_IMAGE_MAP_CHROMIUM); ...... }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数AcquireImage首先调用成员函数ContextGL获得一个OpenGL接口,如下所示:
GLES2Interface* ResourceProvider::ContextGL() const { ContextProvider* context_provider = output_surface_->context_provider(); return context_provider ? context_provider->ContextGL() : NULL; }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员变量output_surface_指向的是一个CompositorOutputSurface对象。这个CompositorOutputSurface对象描述的就是网页的绘图表面,它的创建过程可以参考前面Chromium网页绘图表面(Output Surface)创建过程分析一文。调用这个CompositorOutputSurface对象的成员函数context_provider可以获得一个ContextProviderCommandBuffer对象。这个ContextProviderCommandBuffer对象的创建过程可以参考前面Chromium的GPU进程启动过程分析一文。有了这个ContextProviderCommandBuffer对象之后,调用它的成员函数ContextGL就可以获得一个GLES2Implementation对象。这个GLES2Implementation对象描述的是一个Command Buffer GL接口,也就是它会将传递给它的GPU命令发送给GPU进程执行。这个Command Buffer GL接口的创建和使用过程可以参考前面Chromium硬件加速渲染的OpenGL上下文创建过程分析和Chromium硬件加速渲染的OpenGL命令执行过程分析这两篇文章。
回到ResourceProvider类的成员函数AcquireImage中,它获得了一个Command Buffer GL接口之后,就调用它的成员函数CreateImageCHROMIUM创建一个Graphic Buffer。Command Buffer GL接口成功创建了一个Graphic Buffer之后,就会将这个Graphic Buffer的ID返回给ResourceProvider类的成员函数AcquireImage,后者将其保存在参数resource指向的一个Resource对象的成员变量image_id中,表示这个Resource对象描述的纹理资源关联了一个Graphic Buffer。
接下来我们继续分析通过Command Buffer GL接口创建Graphic Buffer的过程,也就是分析GLES2Implementation类的成员函数CreateImageCHROMIUM的实现,如下所示:
GLuint GLES2Implementation::CreateImageCHROMIUM(GLsizei width, GLsizei height, GLenum internalformat, GLenum usage) { ...... GLuint image_id = CreateImageCHROMIUMHelper(width, height, internalformat, usage); ...... return image_id; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数CreateImageCHROMIUM调用另外一个成员函数CreateImageCHROMIUMHelper创建一个Graphic Buffer,如下所示:
GLuint GLES2Implementation::CreateImageCHROMIUMHelper(GLsizei width, GLsizei height, GLenum internalformat, GLenum usage) { ...... // Create new buffer. GLuint buffer_id = gpu_memory_buffer_tracker_->CreateBuffer( width, height, internalformat, usage); ...... return buffer_id; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员变量gpu_memory_buffer_tracker_指向的是一个GpuMemoryBufferTracker对象,GLES2Implementation类的成员函数CreateImageCHROMIUMHelper调用这个GpuMemoryBufferTracker对象的成员函数CreateBuffer创建一个Graphic Buffer,如下所示:
int32 GpuMemoryBufferTracker::CreateBuffer(size_t width, size_t height, int32 internalformat, int32 usage) { int32 image_id = 0; DCHECK(gpu_control_); gfx::GpuMemoryBuffer* buffer = gpu_control_->CreateGpuMemoryBuffer( width, height, internalformat, usage, &image_id); if (!buffer) return 0; std::pair<BufferMap::iterator, bool> result = buffers_.insert(std::make_pair(image_id, buffer)); DCHECK(result.second); return image_id; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gpu_memory_buffer_tracker.cc中。
GpuMemoryBufferTracker类的成员变量gpu_control_指向的是一个CommandBufferProxyImpl对象。这个CommandBufferProxyImpl对象的创建过程可以参考前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文,它主要是用来发送GPU相关的IPC消息的。
GpuMemoryBufferTracker类的成员函数CreateBuffer通过调用上述CommandBufferProxyImpl对象的成员函数CreateGpuMemoryBuffer请求Browser进程创建一个Graphic Buffer。这个Graphic Buffer的句柄被封装在一个gfx::GpuMemoryBuffer对象中。这个gfx::GpuMemoryBuffer接下来又会以其封装的Graphic Buffer的Image ID为键值,保存在GpuMemoryBufferTracker类的成员变量buffers_描述的一个Hash Map中。
GpuMemoryBufferTracker类的成员函数CreateBuffer最后将创建出来的Graphic Buffer的Image ID返回给调用者,调用者以后可以通过该Image ID引用创建出来的Graphic Buffer。
接下来我们继续分析Graphic Buffer的创建过程,也就是CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer的实现,如下所示:
gfx::GpuMemoryBuffer* CommandBufferProxyImpl::CreateGpuMemoryBuffer( size_t width, size_t height, unsigned internalformat, unsigned usage, int32* id) { *id = -1; ...... int32 new_id = channel_->ReserveGpuMemoryBufferId(); ...... scoped_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer( channel_->factory()->AllocateGpuMemoryBuffer( width, height, internalformat, usage)); ...... // This handle is owned by the GPU process and must be passed to it or it // will leak. In otherwords, do not early out on error between here and the // sending of the RegisterGpuMemoryBuffer IPC below. gfx::GpuMemoryBufferHandle handle = channel_->ShareGpuMemoryBufferToGpuProcess( gpu_memory_buffer->GetHandle()); if (!Send(new GpuCommandBufferMsg_RegisterGpuMemoryBuffer( route_id_, new_id, handle, width, height, internalformat))) { return NULL; } *id = new_id; gpu_memory_buffers_[new_id] = gpu_memory_buffer.release(); return gpu_memory_buffers_[new_id]; }这个函数定义在文件external/chromium_org/content/common/gpu/client/command_buffer_proxy_impl.cc中。
CommandBufferProxyImpl类的成员变量channel_指向的是一个GpuChannelHost对象。这个GpuChannelHost对象的创建过程可以参考前面Chromium的GPU进程启动过程分析一文,它用来描述Render进程与GPU进程之间的GPU通道。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer首先调用上述GpuChannelHost对象的成员函数ReserveGpuMemoryBufferId生成一个ID。这个ID将作为接下来在创建的Graphic Buffer的Image ID。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer接下来又调用上述GpuChannelHost对象的成员函数factory获得一个RenderThreadImpl对象。这个RenderThreadImpl对象描述的是Render进程的Main Thread。有了这个RenderThreadImpl对象之后,CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer就调用它的成员函数AllocateGpuMemoryBuffer请求Browser进程创建一个Graphic Buffer。
Browser进程创建了一个Graphic Buffer之后,会将该Graphic Buffer的句柄返回给请求者,请求者再将这个句柄封装在一个gfx::GpuMemoryBuffer对象中。也就是说,CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer会获得一个gfx::GpuMemoryBuffer对象,通过这个gfx::GpuMemoryBuffer对象可以访问到前面请求Browser进程创建的Graphic Buffer。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer接下来将获得的Graphic Buffer注册到GPU进程中去,因为这个Graphic Buffer最终是在GPU进程中使用的。注册是通过向GPU进程发送一个类型为GpuCommandBufferMsg_RegisterGpuMemoryBuffer的IPC消息实现的。这个IPC消息携带了要注册的Graphic Buffer的句柄。这个句柄可以通过调用前面获得的gfx::GpuMemoryBuffer对象的成员函数GetHandle得到。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer最后会将封装了Graphic Buffer句柄的gfx::GpuMemoryBuffer对象返回给调用者。在返回之前,这个gfx::GpuMemoryBuffer对象会保存在CommandBufferProxyImpl类的成员变量gpu_memory_buffers_描述的一个std::map中,键值即为被封装的Graphic Buffer的Image ID。
接下来我们先分析Graphic Buffer的创建过程,也就是RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer的实现,接下来再分析Graphic Buffer的注册过程,也就是GPU进程处理类型为GpuCommandBufferMsg_RegisterGpuMemoryBuffer的IPC消息的过程。
RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer的实现如下所示:
scoped_ptr<gfx::GpuMemoryBuffer> RenderThreadImpl::AllocateGpuMemoryBuffer( size_t width, size_t height, unsigned internalformat, unsigned usage) { ...... gfx::GpuMemoryBufferHandle handle; bool success; IPC::Message* message = new ChildProcessHostMsg_SyncAllocateGpuMemoryBuffer( width, height, internalformat, usage, &handle); // Allow calling this from the compositor thread. if (base::MessageLoop::current() == message_loop()) success = ChildThread::Send(message); else success = sync_message_filter()->Send(message); if (!success) return scoped_ptr<gfx::GpuMemoryBuffer>(); return GpuMemoryBufferImpl::CreateFromHandle( handle, gfx::Size(width, height), internalformat) .PassAs<gfx::GpuMemoryBuffer>(); }这个函数定义在文件external/chromium_org/content/renderer/render_thread_impl.cc中。
RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer首先是向Browser进程发送一个类型为ChildProcessHostMsg_SyncAllocateGpuMemoryBuffer的IPC消息。Browser进程接收到这个IPC消息后,就会创建一个Graphic Buffer,并且将这个Graphic Buffer的句柄返回给RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer。
RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer获得了Browser进程返回来的Graphic Buffer句柄之后,再调用GpuMemoryBufferImpl类的静态成员函数CreateFromHandle将该句柄封装在一个gfx::GpuMemoryBuffer对象中,并且将该gfx::GpuMemoryBuffer对象返回给调用者。
接下来我们先分析Browser进程创建Graphic Buffer的过程,也就是Browser进程处理类型为ChildProcessHostMsg_SyncAllocateGpuMemoryBuffer的IPC消息的过程,接下来再分析将Graphic Buffer句柄封装为gfx::GpuMemoryBuffer对象的过程,也就是GpuMemoryBufferImpl类的静态成员函数CreateFromHandle的实现。
Browser进程是通过RenderProcessHostImpl类的成员函数OnMessageReceived接收类型为ChildProcessHostMsg_SyncAllocateGpuMemoryBuffer的IPC消息的,如下所示:
bool RenderProcessHostImpl::OnMessageReceived(const IPC::Message& msg) { ...... if (msg.routing_id() == MSG_ROUTING_CONTROL) { // Dispatch control messages. IPC_BEGIN_MESSAGE_MAP(RenderProcessHostImpl, msg) ...... IPC_MESSAGE_HANDLER_DELAY_REPLY( ChildProcessHostMsg_SyncAllocateGpuMemoryBuffer, OnAllocateGpuMemoryBuffer) ...... IPC_END_MESSAGE_MAP() return true; } ...... }这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。
RenderProcessHostImpl类的成员函数OnMessageReceived将接收到的为ChildProcessHostMsg_SyncAllocateGpuMemoryBuffer的IPC消息分发给另外一个成员函数OnAllocateGpuMemoryBuffer处理,如下所示:
void RenderProcessHostImpl::OnAllocateGpuMemoryBuffer(uint32 width, uint32 height, uint32 internalformat, uint32 usage, IPC::Message* reply) { ...... #if defined(OS_MACOSX) ...... if (GpuMemoryBufferImplIOSurface::IsConfigurationSupported(internalformat, usage)) { ...... base::ScopedCFTypeRef<IOSurfaceRef> io_surface(IOSurfaceCreate(properties)); if (io_surface) { gfx::GpuMemoryBufferHandle handle; handle.type = gfx::IO_SURFACE_BUFFER; handle.io_surface_id = IOSurfaceGetID(io_surface); ...... GpuMemoryBufferAllocated(reply, handle); return; } } #endif #if defined(OS_ANDROID) ...... if (GpuMemoryBufferImplSurfaceTexture::IsConfigurationSupported( internalformat, usage)) { // Each surface texture is associated with a render process id. This allows // the GPU service and Java Binder IPC to verify that a renderer is not // trying to use a surface texture it doesn't own. int surface_texture_id = CompositorImpl::CreateSurfaceTexture(GetID()); if (surface_texture_id != -1) { gfx::GpuMemoryBufferHandle handle; handle.type = gfx::SURFACE_TEXTURE_BUFFER; handle.surface_texture_id = gfx::SurfaceTextureId(surface_texture_id, GetID()); GpuMemoryBufferAllocated(reply, handle); return; } } #endif GpuMemoryBufferImpl::AllocateForChildProcess( gfx::Size(width, height), internalformat, usage, GetHandle(), base::Bind(&RenderProcessHostImpl::GpuMemoryBufferAllocated, weak_factory_.GetWeakPtr(), reply)); }
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_process_host_impl.cc中。
在MAC OS X平台,GPU内存块使用IO Surface来描述。在Android平台,GPU内存块使用Surface Texture来描述。其它的平台,GPU内存使用共享内存来模拟。这个共享内存通过调用GpuMemoryBufferImpl类的静态成员函数AllocateForChildProcess创建。
另外,即使是MAC OS X和Android平台,RenderProcessHostImpl类的成员函数OnAllocateGpuMemoryBuffer也不一定会创建真正的GPU内存块。这取决于参数internalformat和usage。前者表示要创建的GPU内存块的颜色格式,后者表示要创建的GPU内存块的用途。当创建的GPU内存块的颜色格式为RGBA,并且用途为GL_IMAGE_MAP_CHROMIUM时,这两个平台才会创建真正的GPU内存块。否则的话,它们也会像其它平台一样,使用共享内存来模拟GPU内存。
调用GpuMemoryBufferImplIOSurface类的静态成员函数IsConfigurationSupported和GpuMemoryBufferImplSurfaceTexture类的静态成员函数IsConfigurationSupported可以判断创建的GPU内存块的颜色格式和用途是否等于RGBA和GL_IMAGE_MAP_CHROMIUM。如果等于的话,它们的值就会等于true。
接下来我们只关心RenderProcessHostImpl类的成员函数OnAllocateGpuMemoryBuffer在Android平台上的实现,并且假设参数internalformat和usage的值均符合创建真正的GPU内存块的要求。这时候RenderProcessHostImpl类的成员函数OnAllocateGpuMemoryBuffer就会调用CompositorImpl类的成员函数CreateSurfaceTexture创建一个Surface Texture。
CompositorImpl类的成员函数CreateSurfaceTexture创建了一个Surface Texture之后,会将这个Surface Texture的ID返回给RenderProcessHostImpl类的成员函数OnAllocateGpuMemoryBuffer。在Android平台上,Surface Texture是SDK暴露给应用层的一个API接口,它描述的就是一个Graphic Buffer。
RenderProcessHostImpl类的成员函数OnAllocateGpuMemoryBuffer得到了一个Surface Texture的ID之后,就会将该ID以及请求创建Surface Texture的Render进程的ID记录在一个类型为gfx::SURFACE_TEXTURE_BUFFER的句柄中,并且通过调用另外一个成员函数GpuMemoryBufferAllocated将该句柄返回给请求创建Surface Texture的Render进程。
接下来我们就继续分析Surface Texture的创建过程,也就是CompositorImpl类的成员函数CreateSurfaceTexture的实现,如下所示:
int CompositorImpl::CreateSurfaceTexture(int child_process_id) { // Note: this needs to be 0 as the surface texture implemenation will take // ownership of the texture and call glDeleteTextures when the GPU service // attaches the surface texture to a real texture id. glDeleteTextures // silently ignores 0. const int kDummyTextureId = 0; scoped_refptr<gfx::SurfaceTexture> surface_texture = gfx::SurfaceTexture::Create(kDummyTextureId); return g_surface_texture_tracker.Pointer()->AddSurfaceTexture( surface_texture.get(), child_process_id); }这个函数定义在文件external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。
CompositorImpl类的成员函数CreateSurfaceTexture首先调用SurfaceTexture类的静态成员函数Create创建一个Texture ID为0的Surface Texture,接着再调用全局变量g_surface_texture_tracker指向的一个SurfaceTextureTrackerImpl对象的成员函数AddSurfaceTexture为该Surface Texture分配一个ID,以及将该Surface Texture注册在Browser进程之中,最后将获得的Surface Texture ID返回给调用者。
接下来我们先分析SurfaceTexture类的静态成员函数Create创建Surface Texture的过程,然后再分析SurfaceTextureTrackerImpl类的成员函数AddSurfaceTexture注册Surface Texture的过程。
SurfaceTexture类的静态成员函数Create的实现如下所示:
scoped_refptr<SurfaceTexture> SurfaceTexture::Create(int texture_id) { JNIEnv* env = base::android::AttachCurrentThread(); return new SurfaceTexture( Java_SurfaceTexturePlatformWrapper_create(env, texture_id)); }这个函数定义在文件external/chromium_org/ui/gl/android/surface_texture.cc中。
SurfaceTexture类的静态成员函数Create首先是通过函数Java_SurfaceTexturePlatformWrapper_create在Java层创建一个Surface Texture,然后再将这个Java层的Surface Texture封装在一个Native层的Surface Texture中,最后将Native层的Surface Texture返回给调用者。
函数Java_SurfaceTexturePlatformWrapper_create实际上是通过JNI调用了Java层的SurfaceTexturePlatformWrapper类的静态成员函数create创建一个Surface Texture,如下所示:
class SurfaceTexturePlatformWrapper { ...... @CalledByNative private static SurfaceTexture create(int textureId) { return new SurfaceTexture(textureId); } ...... }这个函数定义在文件external/chromium_org/ui/android/java/src/org/chromium/ui/gl/SurfaceTexturePlatformWrapper.java中。
从这里可以看到,SurfaceTexturePlatformWrapper类的静态成员函数create在Java层创建一个Surface Texture。这个Surface Texture将会传递给Native层使用。
这一步执行完成之后,回到CompositorImpl类的成员函数CreateSurfaceTexture中,它接下来将前面创建的Surface Texture注册在Browser进程中。这是通过调用SurfaceTextureTrackerImpl类的成员函数AddSurfaceTexture实现的,如下所示:
class SurfaceTextureTrackerImpl : public gfx::SurfaceTextureTracker { public: ...... int AddSurfaceTexture(gfx::SurfaceTexture* surface_texture, int child_process_id) { ...... int surface_texture_id = next_surface_texture_id_++; if (next_surface_texture_id_ == INT_MAX) next_surface_texture_id_ = 1; base::AutoLock lock(surface_textures_lock_); SurfaceTextureMapKey key(surface_texture_id, child_process_id); ...... surface_textures_[key] = surface_texture; content::RegisterChildProcessSurfaceTexture( surface_texture_id, child_process_id, surface_texture->j_surface_texture().obj()); return surface_texture_id; } ...... };
这个函数定义在文件external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。
SurfaceTextureTrackerImpl类的成员函数AddSurfaceTexture首先通过成员变量next_surface_texture_id_为参数surface_texture描述的Native层的Surface Texture分配一个ID。另外一个参数child_process_id描述的是请求创建Graphic Buffer的Render进程的ID。
SurfaceTextureTrackerImpl类的成员函数AddSurfaceTexture接下来将上述两个ID组合成一个Key,用来将参数surface_texture描述的Native层的Surface Texture保存在其成员变量surface_textures_描述的一个Hash Map中。
从前面的分析可以知道,Native层的Surface Texture封装了Java层的Surface Texture。这个Java层的Surface Texture可以通过调用Native层的Surface Texture的成员函数j_surface_texture获得。现在Native层的Surface Texture已经注册在SurfaceTextureTrackerImpl类的成员变量surface_textures_描述的一个Hash Map中了,接下来也需要将Java层对应的Surface Texture注册起来。这是通过调用函数RegisterChildProcessSurfaceTexture实现的,如下所示:
void RegisterChildProcessSurfaceTexture(int surface_texture_id, int child_process_id, jobject j_surface_texture) { JNIEnv* env = AttachCurrentThread(); DCHECK(env); Java_ChildProcessLauncher_registerSurfaceTexture( env, surface_texture_id, child_process_id, j_surface_texture); }
这个函数定义在文件external/chromium_org/content/browser/android/child_process_launcher_android.cc中。
函数RegisterChildProcessSurfaceTexture通过调用另外一个函数Java_ChildProcessLauncher_registerSurfaceTexture注册参数j_surface_texture描述的Java层的Surface Texture。
函数Java_ChildProcessLauncher_registerSurfaceTexture实际上是通过JNI调用了Java层的ChildProcessLauncher类的静态成员函数registerSurfaceTexture注册创建一个Java层的Surface Texture,如下所示:
public class ChildProcessLauncher { ...... // Map from surface texture id to Surface. private static Map<Pair<Integer, Integer>, Surface> sSurfaceTextureSurfaceMap = new ConcurrentHashMap<Pair<Integer, Integer>, Surface>(); ...... @CalledByNative private static void registerSurfaceTexture( int surfaceTextureId, int childProcessId, SurfaceTexture surfaceTexture) { Pair<Integer, Integer> key = new Pair<Integer, Integer>(surfaceTextureId, childProcessId); sSurfaceTextureSurfaceMap.put(key, new Surface(surfaceTexture)); } ...... }这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。
从前面的调用过程可以知道,参数surfaceTextureId描述的是前面创建的Surface Texture的ID,另外一个参数childProcessId描述的是请求创建Surface Texture的Render进程的ID。
ChildProcessLauncher类的静态成员函数registerSurfaceTexture同样是将上述两个ID组合成一个Key,用来将参数surfaceTexture描述的Java层的Surface Texture封装成一个Surface对象保存在其静态成员变量sSurfaceTextureSurfaceMap描述的一个Map中。
这一步执行完成后,Browser进程就创建了一个Surface Texture,并且将这个Surface Texture注册在了内部。为什么这个Surface Texture要注册在Browser进程内部呢?因为Browser进程接下来只会将该Surface Texture的ID返回给Render进程,以及后面Render进程也会将该ID传递给GPU进程。这样Render进程和GPU进程需要访问一个Surface Texture的内容时,就可以通过它所获得的ID到Browser进程中进行相应的操作。这里我们需要再强调的一点是,在Android平台上,Surface Texture即为Graphic Buffer。前者是在Java层使用,后者在Native层使用。
回到前面分析的RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer中,它获得了Browser进程创建的Graphic Buffer的句柄之后,就会调用GpuMemoryBufferImpl类的静态成员函数CreateFromHandle将该句柄封装在一个GpuMemoryBuffer对象中,如下所示:
scoped_ptr<GpuMemoryBufferImpl> GpuMemoryBufferImpl::CreateFromHandle( gfx::GpuMemoryBufferHandle handle, const gfx::Size& size, unsigned internalformat) { switch (handle.type) { case gfx::SHARED_MEMORY_BUFFER: { scoped_ptr<GpuMemoryBufferImplShm> buffer( new GpuMemoryBufferImplShm(size, internalformat)); if (!buffer->InitializeFromHandle(handle)) return scoped_ptr<GpuMemoryBufferImpl>(); return buffer.PassAs<GpuMemoryBufferImpl>(); } case gfx::SURFACE_TEXTURE_BUFFER: { scoped_ptr<GpuMemoryBufferImplSurfaceTexture> buffer( new GpuMemoryBufferImplSurfaceTexture(size, internalformat)); if (!buffer->InitializeFromHandle(handle)) return scoped_ptr<GpuMemoryBufferImpl>(); return buffer.PassAs<GpuMemoryBufferImpl>(); } default: return scoped_ptr<GpuMemoryBufferImpl>(); } }这个函数定义在文件external/chromium_org/content/common/gpu/client/gpu_memory_buffer_impl_android.cc中。
参数handle描述的就是前面请求Browser进程创建的Graphic Buffer的句柄。从前面的分析可以知道,在Android平台上,这个Graphic Buffer可能是一块共享内存,也可能是一个Surface Texture。
如果是一块共享内存,那么GpuMemoryBufferImpl类的静态成员函数CreateFromHandle就将参数handle描述的Graphic Buffer句柄封装在一个GpuMemoryBufferImplShm对象中,然后调用这个GpuMemoryBufferImplShm对象的成员函数InitializeFromHandle对其进行初始化,实际上就是在内部创建一个SharedMemory对象描述它所封装的共享内存。
如果是一个Surface Texture,那么GpuMemoryBufferImpl类的静态成员函数CreateFromHandle就会将参数handle描述的Graphic Buffer句柄封装在一个GpuMemoryBufferImplSurfaceTexture对象中,然后调用这个GpuMemoryBufferImplSurfaceTexture对象的成员函数InitializeFromHandle对其进行初始化,实际上就是在内部创建一个ANativeWindow对象描述它所封装的Surface Texture。
在Android平台上,ANativeWindow也是用来描述一个Graphic Buffer的。通过ANativeWindow,可以将Graphic Buffer映射到系统内存去,以便CPU可以进行访问。这样CPU就可以根据这块系统内存创建一个画布,然后通过该画布的绘图接口将网页分块光栅化在Graphic Buffer中。
无论是GpuMemoryBufferImplShm对象,还是GpuMemoryBufferImplSurfaceTexture对象,它们都是从GpuMemoryBufferImpl类继承下来的,因此它们最终都可以转化为一个GpuMemoryBufferImpl对象返回给调用者,即RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer。GpuMemoryBufferImpl类又是从GpuMemoryBuffer类继承下来的,因此RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer又可以通过一个GpuMemoryBuffer指针引用它所获得的GpuMemoryBufferImpl对象。
我们假设Browser进程创建的Graphic Buffer是一个Surface Texture,接下来我们就继续分析GpuMemoryBufferImplSurfaceTexture类的成员函数InitializeFromHandle为其创建一个ANativeWindow对象的过程,如下所示:
bool GpuMemoryBufferImplSurfaceTexture::InitializeFromHandle( gfx::GpuMemoryBufferHandle handle) { ...... native_window_ = SurfaceTextureLookup::GetInstance()->AcquireNativeWidget( handle.surface_texture_id.primary_id, handle.surface_texture_id.secondary_id); ...... ANativeWindow_setBuffersGeometry(native_window_, size_.width(), size_.height(), WindowFormat(internalformat_)); surface_texture_id_ = handle.surface_texture_id; return true; }这个函数定义在文件external/chromium_org/content/common/gpu/client/gpu_memory_buffer_impl_surface_texture.cc中。
GpuMemoryBufferImplSurfaceTexture类的成员函数InitializeFromHandle首先通过调用SurfaceTextureLookup类的静态成员函数GetInstance获得一个SurfaceTexturePeerChildImpl对象,然后再调用这个SurfaceTexturePeerChildImpl对象的成员函数AcquireNativeWidget根据记录参数handle描述的句柄中Surface Texture ID和Render进程ID获得一个ANativeWindow对象,并且保存在成员变量native_window_中。
有了上述ANativeWindow对象之后,GpuMemoryBufferImplSurfaceTexture类的成员函数InitializeFromHandle就可以通过调用函数ANativeWindow_setBuffersGeometry设置该ANativeWindow对象描述的Graphic Buffer的大小以及颜色格式。
GpuMemoryBufferImplSurfaceTexture类的成员函数InitializeFromHandle最后还会将记录参数handle描述的句柄中Surface Texture ID保存在成员变量surface_texture_id_中,以便以后可以使用。
接下来我们继续分析SurfaceTexturePeerChildImpl类的成员函数AcquireNativeWidget的实现,以便了解它是如何根据Surface Texture ID和Render进程ID获得一个ANativeWindow对象的,如下所示:
class SurfaceTexturePeerChildImpl : public SurfaceTexturePeer, public GpuSurfaceLookup, public SurfaceTextureLookup { public: ...... virtual gfx::AcceleratedWidget AcquireNativeWidget(int primary_id, int secondary_id) OVERRIDE { JNIEnv* env = base::android::AttachCurrentThread(); gfx::ScopedJavaSurface surface( content::Java_ChildProcessService_getSurfaceTextureSurface( env, service_.obj(), primary_id, secondary_id)); if (surface.j_surface().is_null()) return NULL; // Note: This ensures that any local references used by // ANativeWindow_fromSurface are released immediately. This is needed as a // workaround for https://code.google.com/p/android/issues/detail?id=68174 base::android::ScopedJavaLocalFrame scoped_local_reference_frame(env); ANativeWindow* native_window = ANativeWindow_fromSurface(env, surface.j_surface().obj()); return native_window; } private: // The instance of org.chromium.content.app.ChildProcessService. base::android::ScopedJavaGlobalRef<jobject> service_; ...... };这个函数定义在文件external/chromium_org/content/app/android/child_process_service.cc中。
SurfaceTexturePeerChildImpl类的成员变量service_指向的是一个Java层的ChildProcessService对象,SurfaceTexturePeerChildImpl类的成员函数AcquireNativeWidget通过函数Java_ChildProcessService_getSurfaceTextureSurface调用这个ChildProcessService对象的成员函数getSurfaceTextureSurface获得一个Native层的Surface对象。调用这个Native层的Surface对象的成员函数j_surface又可以获得它在Java层对应的一个Surface对象。有了这个Java层的Surface对象,就可以通过函数ANativeWindow_fromSurface获得其内部所创建的一个ANativeWindow对象。这个ANativeWindow最后就会返回给调用者。
注意,Java层的Surface描述的是一个窗口。在Java层,Activity和SurfaceView都是属于一个窗口,它们在内部都维护有一个Surface对象。在Native层,窗口是通过ANativeWindow来描述的。于是,Java层的每一个Surface对象在Native层都有一个对应的ANativeWindow对象。有这个ANativeWindow对象之后,就可以在Native层操作一个窗口了,例如绘制这个窗口。
在调用Java层的ChildProcessService类的成员函数getSurfaceTextureSurface的时候,需要指定一个Surface Texture ID和一个Render进程ID,这样它就可以在Browser进程中找到之前以这两个ID为Key所注册的Surface Texture,进而将这个Surface Texture封装成一个Java层的Surface对象。这个Java层的Surface对象再进一步被封装为Native层的Surface对象。
接下来我们就继续分析Java层的ChildProcessService类的成员函数getSurfaceTextureSurface的实现,以便了解它根据Surface Texture ID和Render进程ID查找之前注册在Browser进程中的Surface Texture的过程,如下所示:
public class ChildProcessService extends Service { ...... @CalledByNative private Surface getSurfaceTextureSurface(int primaryId, int secondaryId) { ...... try { return mCallback.getSurfaceTextureSurface(primaryId, secondaryId).getSurface(); } catch (RemoteException e) { Log.e(TAG, "Unable to call getSurfaceTextureSurface: " + e); return null; } } ...... }
这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/app/ChildProcessService.java中。
ChildProcessService类的成员变量mCallback是一个Binder代理接口,它引用了运行在Browser进程中的一个IChildProcessCallback.Stub对象。通过这个Binder代理接口的成员函数getSurfaceTextureSurface就可以跨进程调用到它所引用的IChildProcessCallback.Stub对象的成员函数getSurfaceTextureSurface。后者的实现如下所示:
public class ChildProcessLauncher { ...... private static Map<Pair<Integer, Integer>, Surface> sSurfaceTextureSurfaceMap = new ConcurrentHashMap<Pair<Integer, Integer>, Surface>(); ...... private static IChildProcessCallback createCallback( final int childProcessId, final int callbackType) { return new IChildProcessCallback.Stub() { ...... @Override public SurfaceWrapper getSurfaceTextureSurface(int primaryId, int secondaryId) { ...... Pair<Integer, Integer> key = new Pair<Integer, Integer>(primaryId, secondaryId); // Note: This removes the surface and passes the ownership to the caller. Surface surface = sSurfaceTextureSurfaceMap.remove(key); ...... return new SurfaceWrapper(surface); } }; } ...... }这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncher.java中。
从前面的分析可以知道,Browser进程会将Render进程请求它创建的Surface Texture封装成一个Surface对象保存ChildProcessLauncher类的静态成员变量sSurfaceTextureSurfaceMap描述的一个Map中,并且是以Surface Texture的ID以及请求创建Surface Texture的Render进程的ID为键值进行保存的。
因此,给出一个Surface Texture的ID和一个Render进程的ID,IChildProcessCallback.Stub类的成员函数getSurfaceTextureSurface就可以在上述Map中找到之前所保存的一个Surface对象。这个Surface对象被封装在一个SurfaceWrapper对象中返回给Render进程,也就是ChildProcessService类的成员函数getSurfaceTextureSurface。
回到ChildProcessService类的成员函数getSurfaceTextureSurface中,它获得了一个SurfaceWrapper对象之后,就可以调用它的成员函数getSurface获得它所封装的Surface对象。这个Surface对象就会返回到Native层进行后续处理,也就是交给前面分析的SurfaceTexturePeerChildImpl类的成员函数AcquireNativeWidget进行处理。
这一步执行完成之后,回到CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer中,这时候Render进程就请求Browser进程创建了一个Graphic Buffer,并且Browser进程将这个Graphic Buffer的句柄返回给了Render进程。Render进程通过这个句柄又获得了一个ANativeWindow对象。这个ANativeWindow对象就在Render进程中代表了在Browser进程所创建的Graphic Buffer,也就是Render进程可以通过这个ANativeWindow对象访问Browser进程创建的Graphic Buffer。
接下来,CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer就会向GPU进程发送一个类型为GpuCommandBufferMsg_RegisterGpuMemoryBuffer的IPC消息,用来将前面请求Browser进程创建的Graphic Buffer注册到GPU进程中去。
GPU进程是通过GpuCommandBufferStub类的成员函数OnMessageReceived接收类型为GpuCommandBufferMsg_RegisterGpuMemoryBuffer的IPC消息的,如下所示:
bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) { ...... bool handled = true; IPC_BEGIN_MESSAGE_MAP(GpuCommandBufferStub, message) ...... IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_RegisterGpuMemoryBuffer, OnRegisterGpuMemoryBuffer); ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() ...... return handled; }这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
从这里可以看到,GpuCommandBufferStub类的成员函数OnMessageReceived将类型为GpuCommandBufferMsg_RegisterGpuMemoryBuffer的IPC消息分发给另外一个成员函数OnRegisterGpuMemoryBuffer处理,如下所示:
void GpuCommandBufferStub::OnRegisterGpuMemoryBuffer( int32 id, gfx::GpuMemoryBufferHandle gpu_memory_buffer, uint32 width, uint32 height, uint32 internalformat) { ...... if (gpu_control_service_) { gpu_control_service_->RegisterGpuMemoryBuffer( id, gpu_memory_buffer, width, height, internalformat); } }
这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
GpuCommandBufferStub类的成员变量gpu_control_service_指向的是一个GpuControlService对象。GpuCommandBufferStub类的成员函数OnRegisterGpuMemoryBuffer调用这个GpuControlService对象的成员函数RegisterGpuMemoryBuffer注册参数gpu_memory_buffer描述的Graphic Buffer,如下所示:
void GpuControlService::RegisterGpuMemoryBuffer( int32 id, gfx::GpuMemoryBufferHandle buffer, size_t width, size_t height, unsigned internalformat) { gpu_memory_buffer_manager_->RegisterGpuMemoryBuffer( id, buffer, width, height, internalformat); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gpu_control_service.cc中。
GpuControlService类的成员变量gpu_memory_buffer_manager_指向的是一个ImageManager对象。GpuControlService类的成员函数RegisterGpuMemoryBuffer调用这个ImageManager对象的成员函数RegisterGpuMemoryBuffer注册参数buffer描述的Graphic Buffer,如下所示:
void ImageManager::RegisterGpuMemoryBuffer(int32 id, gfx::GpuMemoryBufferHandle buffer, size_t width, size_t height, unsigned internalformat) { ...... scoped_refptr<gfx::GLImage> gl_image = gfx::GLImage::CreateGLImageForGpuMemoryBuffer(buffer, gfx::Size(width, height), internalformat); ...... AddImage(gl_image.get(), id); }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/image_manager.cc中。
ImageManager类的成员函数RegisterGpuMemoryBuffer首先调用GLImage类的静态成员函数CreateGLImageForGpuMemoryBuffer根据参数buffer描述的Graphic Buffer创建一个GL Image,然后再调用另外一个成员函数AddImage将这个GL Image保存在内部,如下所示:
void ImageManager::AddImage(gfx::GLImage* image, int32 service_id) { gl_images_[service_id] = image; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/image_manager.cc中。
参数service_id描述的是要注册的Graphic Buffer的Image ID。ImageManager类的成员函数AddImage以这个Image ID为键值,将前面创建的GL Image保存在成员变量gl_images_描述的一个Hash Map中。
为了弄清楚GL Image的含义和作用,我们接下来继续分析GLImage类的静态成员函数CreateGLImageForGpuMemoryBuffer的实现,如下所示:
scoped_refptr<GLImage> GLImage::CreateGLImageForGpuMemoryBuffer( gfx::GpuMemoryBufferHandle buffer, gfx::Size size, unsigned internalformat) { ...... switch (GetGLImplementation()) { case kGLImplementationEGLGLES2: switch (buffer.type) { case SHARED_MEMORY_BUFFER: { scoped_refptr<GLImageShm> image( new GLImageShm(size, internalformat)); if (!image->Initialize(buffer)) return NULL; return image; } case ANDROID_NATIVE_BUFFER: { scoped_refptr<GLImageAndroidNativeBuffer> image( new GLImageAndroidNativeBuffer(size)); if (!image->Initialize(buffer)) return NULL; return image; } case SURFACE_TEXTURE_BUFFER: { scoped_refptr<GLImageSurfaceTexture> image( new GLImageSurfaceTexture(size)); if (!image->Initialize(buffer)) return NULL; return image; } default: NOTREACHED(); return NULL; } case kGLImplementationMockGL: return new GLImageStub; default: NOTREACHED(); return NULL; } }这个函数定义在文件external/chromium_org/ui/gl/gl_image_android.cc中。
从前面Chromium的GPU进程启动过程分析一篇文章可以知道,在Android平台上,GPU进程使用的是类型为kGLImplementationEGLGLES2的OpenGL实现。参数buffer描述的Graphic Buffer的类型是SHARED_MEMORY_BUFFER、ANDROID_NATIVE_BUFFER和SURFACE_TEXTURE_BUFFER三者之一。
对于类型为SHARED_MEMORY_BUFFER的Graphic Buffer,它实际上是一块共享内存。这时候GLImage类的静态成员函数CreateGLImageForGpuMemoryBuffer会创建一个GLImageShm对象来描述该Graphic Buffer。GLImageShm类在内部会创建一个类型为GL_TEXTURE_2D的纹理,并且将它内部封装的共享内存作为上述纹理的数据。之后这个纹理又会用来创建一个EGL Image,这是通过调用EGL函数eglCreateImageKHR实现的。上述共享内存在Render进程中又会用作一个Skia Canvas的底层存储。这个Skia Canvas就是用来光栅化网页分块的。这意味着网页分块光栅化完成后,GLImageShm类封装的共享内存就保存了网页分块的光栅化结果。由于这个共享内存通过一个纹理与GLImageShm类内部创建的一个EGL Image建立了关联,因此通过这个EGL Image就可以访问到网页的光栅化结果。
从前面的分析可以知道,每一个网页分块又被分配了一个纹理资源。当网页分块光栅化完成后,分配给它的纹理就会被合成。在合成之前,这个纹理会通过OpenGL函数glEGLImageTargetTexture2DOES与GLImageShm类内部创建的EGL Image建立关联。这时候为网页分块分配的纹理将会以GLImageShm类内部创建的EGL Image为底层存储。这样在合成这个纹理的时候,就相当于是使用了之前通过Skia Cavnas获得的网页分块光栅化结果。这一点可以参考GLImageShm类的成员函数BindTexImage的实现。
对于类型为SHARED_MEMORY_BUFFER的Graphic Buffer,它与类型为SURFACE_TEXTURE_BUFFER的Graphic Buffer类似,描述的均是GPU内存。这时候GLImage类的静态成员函数CreateGLImageForGpuMemoryBuffer会创建一个GLImageAndroidNativeBuffer对象来描述该Graphic Buffer。GLImageAndroidNativeBuffer类与GLImageShm类一样,都会创建一个EGL Image与它所封装的GPU内存建立关联,这也是通过调用EGL函数eglCreateImageKHR实现的。由于EGL函数eglCreateImageKHR通过EGL_NATIVE_BUFFER_ANDROID扩展可以直接在EGL Image与GPU内存之间建立关联,因此GLImageAndroidNativeBuffer类不像GLImageShm一样,需要间接创建一个纹理。
有了EGL Image之后,GLImageAndroidNativeBuffer类的作用就与GLImageShm类就类似了,它所封装的GPU内存接下来会被映射到系统内存中。这个系统内存又会作为一个Skia Canvas的底层存储。这个Skia Canvas也是用来光栅化网页分块的。网页分块在合成之前,分配给它的纹理同样会通过OpenGL函数glEGLImageTargetTexture2DOES与GLImageAndroidNativeBuffer类内部创建的EGL Image建立关联,这就相当于是使用了之前通过Skia Canvas获得的网页分块光栅化结果。
类型为SURFACE_TEXTURE_BUFFER的Graphic Buffer的与类型为SHARED_MEMORY_BUFFER的Graphic Buffer的使用过程是完全一样的,只不过前者不直接调用EGL函数eglCreateImageKHR创建EGL Image,也不直接调用OpenGL函数glEGLImageTargetTexture2DOES在EGL Image与分配给网页分块的纹理之间建立关联,而都是间接地通过Android SDK提供的SurfaceTexture接口来调用。
从前面的分析可以知道,参数buffer描述的Graphic Buffer的类型为SURFACE_TEXTURE_BUFFER,因此接下来我们就详细分析将类型为SURFACE_TEXTURE_BUFFER的Graphic Buffer封装为GL Image对象的过程。这个GL Image实际上是一个GLImageSurfaceTexture对象。GLImage类的静态成员函数CreateGLImageForGpuMemoryBuffer在创建了这个GLImageSurfaceTexture对象之后,会调用它的成员函数Initialize执行初始化工作,如下所示:
bool GLImageSurfaceTexture::Initialize(gfx::GpuMemoryBufferHandle buffer) { ...... surface_texture_ = SurfaceTextureTracker::GetInstance()->AcquireSurfaceTexture( buffer.surface_texture_id.primary_id, buffer.surface_texture_id.secondary_id); return !!surface_texture_; }这个函数定义在文件external/chromium_org/ui/gl/gl_image_surface_texture.cc中。
从前面的分析可以知道,参数buffer描述的是类型为SURFACE_TEXTURE_BUFFER的Graphic Buffer句柄。这个句柄记录了两个ID。一个是之前在Browser进程中创建的一个Surface Textrue的ID,另一个是请求Browser进程创建该Surface Texture的Render进程的ID。
GLImageSurfaceTexture类的成员函数Initialize首先调用SurfaceTextureTracker类的静态成员函数GetInstance获得一个SurfaceTextureTrackerImpl对象,接着以上述两个ID为参数,调用这个SurfaceTextureTrackerImpl对象的成员函数AcquireSurfaceTexture,用来获得之前在Browser进程中创建的Surface Textrue,如下所示:
class SurfaceTextureTrackerImpl : public gfx::SurfaceTextureTracker { public: ...... virtual scoped_refptr<gfx::SurfaceTexture> AcquireSurfaceTexture( int primary_id, int secondary_id) OVERRIDE { base::AutoLock lock(surface_textures_lock_); SurfaceTextureMapKey key(primary_id, secondary_id); SurfaceTextureMap::iterator it = surface_textures_.find(key); if (it == surface_textures_.end()) return scoped_refptr<gfx::SurfaceTexture>(); scoped_refptr<gfx::SurfaceTexture> surface_texture = it->second; surface_textures_.erase(it); return surface_texture; } ...... };这个函数定义在文件external/chromium_org/content/browser/renderer_host/compositor_impl_android.cc中。
从前面的分析可以知道,Browser进程创建的Native层的Surface Texture都保存在一个SurfaceTextureTrackerImpl单例对象的成员变量surface_textures_描述的一个Hash Map中,并且在Java层也有一个对应的Surface Texture。
从前面的分析又可以知道,Render进程在获取Browser进程创建的Surface Texture的时候,需要通过Binder IPC去Browser进程获得。但是GPU进程不会像Render进程一样,要通过Binder IPC去获得Browser进程创建的Surface Texture。这是因为在Android平台上,GPU进程与Browser进程实际上同一个进程,也就是Browser进程通过一个GPU线程模拟一个GPU进程。这一点可以参考前面Chromium的GPU进程启动过程分析一文。
因此,前面GLImageSurfaceTexture类的成员函数Initialize获得的SurfaceTextureTrackerImpl对象就是Browser进程中的SurfaceTextureTrackerImpl单例对象。当这个SurfaceTextureTrackerImpl单例对象的成员函数AcquireSurfaceTexture被调用的时候,就可以直接在它的成员变量surface_textures_描述的Hash Map中检查参数primary_id和secondary_id描述的Surface Texture是否存在。如果存在,就将它返回给调用者,也就是GLImageSurfaceTexture类的成员函数Initialize。
GLImageSurfaceTexture类的成员函数Initialize会将获得的Surface Texture保存在成员变量surface_texture_中。当一个网页分块光栅化完成被合成的时候,分配给它的纹理资源就会传递给Browser进程。这个纹理资源同时也记录了网页分块光栅化时所使用的Graphic Buffer的Image ID。有了这个Image ID,就可以找到在注册Graphic Buffer时所创建的GLImageSurfaceTexture对象。有了这个GLImageSurfaceTexture对象之后,就可以调用它的成员函数BindTexImage与分配给网页分块的纹理建立关联,如下所示:
bool GLImageSurfaceTexture::BindTexImage(unsigned target) { ...... GLint texture_id; glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texture_id); ...... if (texture_id != texture_id_) { // Note: Surface textures used as gpu memory buffers are created with an // initial dummy texture id of 0. We need to call DetachFromGLContext() here // to detach from the dummy texture before we can attach to a real texture // id. DetachFromGLContext() will delete the texture for the current // attachment point so it's important that this is never called when // attached to a real texture id. Detaching from the dummy texture id should // not cause any problems as the GL should silently ignore 0 when passed to // glDeleteTextures. DCHECK_EQ(0, texture_id_); surface_texture_->DetachFromGLContext(); // This will attach the surface texture to the texture currently bound to // GL_TEXTURE_EXTERNAL_OES target. surface_texture_->AttachToGLContext(); texture_id_ = texture_id; } surface_texture_->UpdateTexImage(); return true; }这个函数定义在文件external/chromium_org/ui/gl/gl_image_surface_texture.cc中。
GLImageSurfaceTexture类的成员函数BindTexImage首先是通过OpenGL函数获得当前与GL_TEXTURE_BINDING_EXTERNAL_OES绑定的纹理的ID。这个纹理ID即为分配给当前要合成的网页分块的纹理的ID。
从前面的分析可以知道,一个Surface Texture在创建的时候,指定的纹理ID为一个Dummy Texture ID,也就是等于0。GLImageSurfaceTexture类的成员变量texture_id_的初始值也等于0,表示它的成员变量surface_texture_指向的Surface Texture还没有与分配给网页分块的纹理进行绑定。这时候GLImageSurfaceTexture类的成员函数BindTexImage首先是将解除成员变量surface_texture_指向的Surface Texture与Dummy Texture的绑定关系,接着将该Surface Texture与分配给网页分块的纹理进行绑定。
将一个Surface Texture与一个纹理进行绑定和解除绑定是通过调用Native层的SurfaceTexture类的成员函数AttachToGLContext和DetachFromGLContext实现的。这两个成员函数又是分别通过调用Java层的SurfaceTexture类的成员函数attachToGLContext和detachFromGLContext实现的。Java层的SurfaceTexture类的成员函数attachToGLContext会通过EGL函数eglCreateImageKHR根据它所描述的GPU内存创建一个EGL Image,并且通过OpenGL函数glEGLImageTargetTexture2DOES将这个EGL Image与分配给当前要合成的网页分块的纹理进行绑定。
事实上,Surface Texture描述的并不仅仅是一个GPU内存块,而是一系列的GPU内存块。一个网页分块每次执行光栅化操作的时候,都会从为它分配的Surface Textrue获得一个GPU内存块来使用。使用完成后,再将这个GPU内存块交还给Surface Texture。接下来在合成这个网页分块的时候,都需要调用Native层的SurfaceTexture类的成员函数UpdateTexImage将上次使用的GPU内存块的内容作为分配给网页分块的纹理的数据,这样才能保证每次合成网页分块的时候,使用的是正确的光栅化结果。
理解了Graphic Buffer注册到GPU进程的过程之后,回到ResourceProvider类的成员函数MapImageRasterBuffer中,这时候它不仅为当前要执行光栅化操作的网页分块请求Browser进程创建了一个Graphic Buffer,而且还将这个Graphic Buffer注册到了GPU进程,接下来它又会调用之前为要执行光栅化操作的网页分块所创建的一个ImageRasterBuffer对象的成员函数LockForWrite,用来创建一个Skia Canvas。
ImageRasterBuffer类继承了BitmapRasterBuffer类,BitmapRasterBuffer类又继承了RasterBuffer类。ImageRasterBuffer类的成员函数LockForWrite是从父类RasterBuffer继承下来的,它的实现如下所示:
SkCanvas* ResourceProvider::RasterBuffer::LockForWrite() { ...... locked_canvas_ = DoLockForWrite(); canvas_save_count_ = locked_canvas_ ? locked_canvas_->save() : 0; return locked_canvas_; }
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
RasterBuffer类的成员函数LockForWrite主要是调用由子类BitmapRasterBuffer实现的成员函数DoLockForWrite创建一个Skia Canvas,如下所示:
SkCanvas* ResourceProvider::BitmapRasterBuffer::DoLockForWrite() { ...... int stride = 0; mapped_buffer_ = MapBuffer(&stride); ...... switch (resource()->format) { ...... case RGBA_8888: case BGRA_8888: { SkImageInfo info = SkImageInfo::MakeN32Premul(resource()->size.width(), resource()->size.height()); ...... raster_bitmap_.installPixels(info, mapped_buffer_, stride); break; } ...... } raster_canvas_ = skia::AdoptRef(new SkCanvas(raster_bitmap_)); ...... return raster_canvas_.get(); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
BitmapRasterBuffer类的成员函数DoLockForWrite首先调用由子类ImageRasterBuffer实现的成员函数MapBuffer获得一个图形缓冲区mapped_buffer_,然后将这个图形缓冲区作为成员变量raster_bitmap_描述的一个Skia Bitmap的底层存储,最后再根据上述Skia Bitmap创建一个Skia Canvas返回给调用者。
这意味着当我们使用BitmapRasterBuffer类的成员函数DoLockForWrite创建的Skia Canvas光栅化网页分块时,得到的光栅化结果就保存在调用ImageRasterBuffer类的成员函数MapBuffer获得的图形缓冲区中。接下来我们分析这个图形缓冲区的获取过程,即ImageRasterBuffer类的成员函数MapBuffer的实现,如下所示:
uint8_t* ResourceProvider::ImageRasterBuffer::MapBuffer(int* stride) { return resource_provider()->MapImage(resource(), stride); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ImageRasterBuffer类的成员函数MapBuffer首先是调用成员函数resource获得一个纹理资源。前面Render进程为这个纹理资源请求Browser进程创建了一个Graphic Buffer。
ImageRasterBuffer类的成员函数MapBuffer接下来又调用成员函数resource_provider获得一个ResourceProvider对象,并且调用这个ResourceProvider对象的成员函数MapImage将前面创建的Graphic Buffer映射到当前进程的地址空间来,以便CPU可以访问这个Graphic Buffer的内容。
ResourceProvider类的成员函数MapImage的实现如下所示:
uint8_t* ResourceProvider::MapImage(const Resource* resource, int* stride) { ...... if (resource->type == GLTexture) { ...... GLES2Interface* gl = ContextGL(); ...... uint8_t* pixels = static_cast<uint8_t*>(gl->MapImageCHROMIUM(resource->image_id)); ...... return pixels; } ...... return resource->pixels; }
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
参数resource指向的Resource对象描述的是一个纹理资源。这个Resource对象的成员变量image_id记录了之前为它所创建的一个Graphic Buffer的Image ID。
ResourceProvider类的成员函数MapImage首先调用成员函数ContextGL获得一个OpenGL接口。从前面的分析可以知道,这个OpenGL接口实际上是一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,ResourceProvider类的成员函数MapImage再调用它的成员函数MapImageCHROMIUM将上述Image ID所描述的Graphic Buffer映射到当前进程的地址空间来。映射完成后,就可以得到一块图形缓冲区。这个图形缓冲区的地址pixels将会返回给调用者,以便调用者用来创建一个Skia Canvas。
从前面的分析可以知道,Command Buffer GL接口是通过类GLES2Implementation描述的,它的成员函数MapImageCHROMIUM的实现如下所示:
void* GLES2Implementation::MapImageCHROMIUMHelper(GLuint image_id) { gfx::GpuMemoryBuffer* gpu_buffer = gpu_memory_buffer_tracker_->GetBuffer( image_id); ...... return gpu_buffer->Map(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
前面提到,GLES2Implementation类的成员变量gpu_memory_buffer_tracker_指向的是一个GpuMemoryBufferTracker对象。GLES2Implementation类的成员函数MapImageCHROMIUM首先调用这个GpuMemoryBufferTracker对象的成员函数GetBuffer获得与参数image_id对应的一个GpuMemoryBuffer对象,如下所示:
gfx::GpuMemoryBuffer* GpuMemoryBufferTracker::GetBuffer(int32 image_id) { BufferMap::iterator it = buffers_.find(image_id); return (it != buffers_.end()) ? it->second : NULL; }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gpu_memory_buffer_tracker.cc中。
从前面的分析可以知道,Render进程是通过调用GpuMemoryBufferTracker类的成员函数CreateBuffer请求Browser进程创建Graphic Buffer的。Browser进程创建完成请求的Graphic Buffer之后,就会将该Graphic Buffer的句柄返回给GpuMemoryBufferTracker类的成员函数CreateBuffer。GpuMemoryBufferTracker类的成员函数CreateBuffer再根据这个Graphic Buffer句柄创建一个GpuMemoryBufferImplSurfaceTexture对象,并且将这个GpuMemoryBufferImplSurfaceTexture对象保存在GpuMemoryBufferTracker类的成员变量buffers_ 描述的一个Hash Map中,键值即为分配给请求创建的Graphic Buffer的Image ID。
因此,GpuMemoryBufferTracker类的成员函数GetBuffer所做的事情就是在成员变量buffers_ 描述的一个Hash Map中找到一个GpuMemoryBufferImplSurfaceTexture对象,这个GpuMemoryBufferImplSurfaceTexture对象的Image ID就是等于参数image_id的值。
回到GLES2Implementation类的成员函数MapImageCHROMIUM中,它获得了一个GpuMemoryBufferImplSurfaceTexture对象之后,就调用这个GpuMemoryBufferImplSurfaceTexture对象的成员函数Map将其所描述的一个Graphic Buffer映射到当前进程的地址空间来,如下所示:
void* GpuMemoryBufferImplSurfaceTexture::Map() { ...... ANativeWindow_Buffer buffer; int status = ANativeWindow_lock(native_window_, &buffer, NULL); ...... return buffer.bits; }这个函数定义在文件external/chromium_org/content/common/gpu/client/gpu_memory_buffer_impl_surface_texture.cc中,
从前面的分析可以知道,GpuMemoryBufferImplSurfaceTexture类的成员变量native_window_指向的是一个ANativeWindow对象。这个ANativeWindow对象描述的是之前请求Browser进程创建的一个Surface Texture。这个Surface Texture内部维护有一个Graphic Buffer队列。通过调用函数ANativeWindow_lock可以从这个Graphic Buffer队列中获得一个Graphic Buffer。这个Graphic Buffer通过一个ANativeWindow_Buffer对象描述。
函数ANativeWindow_lock还会将从Graphic Buffer队列中获得的Graphic Buffer映射到当前进程的地址空间来,映射后得到的地址就保存在用来描述获得的Graphic Buffer的ANativeWindow_Buffer对象的成员变量bits。GpuMemoryBufferImplSurfaceTexture类的成员函数Map最后将这个地址返回给调用者,以便调用者可以创建一个Skia Canvas。
这一步执行完成后,我们就获得了一个Skia Canvas。这个Skia Canvas的底层存储是一个Graphic Buffer。有了这个Skia Canvas之后,就可以对网页分块进行光栅化,并且光栅化结果就保存它的底层存储Graphic Buffer中。网页分块的光栅化过程可以参考前面Chromium网页光栅化过程分析一文。
从前面Chromium网页光栅化过程分析一文可以知道,在Zero Copy这种CPU光栅化方式中,当网页分块光栅化过程执行完成后,ImageRasterWorkerPool类的成员函数ReleaseCanvasForRaster会被调用,用来执行清理工作,如下所示:
void ImageRasterWorkerPool::ReleaseCanvasForRaster(RasterTask* task) { resource_provider_->UnmapImageRasterBuffer(task->resource()->id()); ...... }这个函数定义在文件external/chromium_org/cc/resources/image_raster_worker_pool.cc中。
ImageRasterWorkerPool类的成员函数ReleaseCanvasForRaster所做的事情就是将参数task描述的光栅化任务所使用的Graphic Buffer从当前进程的地址空间中解除映射,这是通过调用成员变量resource_provider_指向的一个ResourceProvider对象的成员函数UnmapImageRasterBuffer实现的,如下所示:
bool ResourceProvider::UnmapImageRasterBuffer(ResourceId id) { Resource* resource = GetResource(id); ...... return resource->image_raster_buffer->UnlockForWrite(); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数UnmapImageRasterBuffer首先调用成员函数GetResource获得与参数id对应的一个Resource对象。这个Resource对象描述的是一个纹理资源。这个纹理资源是分配给刚刚执行完成光栅化操作的网页分块的。
从前面的分析可以知道,前面获得的Resource对象的成员变量image_raster_buffer指向的是一个ImageRasterBuffer对象。ResourceProvider类的成员函数UnmapImageRasterBuffer调用这个ImageRasterBuffer对象的成员函数UnlockForWrite将一个光栅化任务所使用的Graphic Buffer从当前进程的地址空间中解除映射。
ImageRasterBuffer类的成员函数UnlockForWrite是从父类RasterBuffer继承下来的,它的实现如下所示:
bool ResourceProvider::RasterBuffer::UnlockForWrite() { ...... return DoUnlockForWrite(); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ImageRasterBuffer类的成员函数UnlockForWrite主要是调用由子类BitmapRasterBuffer实现的成员函数DoUnlockForWrite将一个光栅化任务所使用的Graphic Buffer从当前进程的地址空间中解除映射,如下所示:
bool ResourceProvider::BitmapRasterBuffer::DoUnlockForWrite() { ..... UnmapBuffer(); ...... return raster_bitmap_changed; }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
BitmapRasterBuffer类的成员函数DoUnlockForWrite主要是调用由子类ImageRasterBuffer实现的成员函数UnmapBuffer将一个光栅化任务所使用的Graphic Buffer从当前进程的地址空间中解除映射,如下所示:
void ResourceProvider::ImageRasterBuffer::UnmapBuffer() { resource_provider()->UnmapImage(resource()); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ImageRasterBuffer类的成员函数UnmapBuffer首先调用成员函数resource获得一个纹理资源。这个纹理资源记录了一个Graphic Buffer。这个Graphic Buffer即为上述提到的光栅化任务所使用的Graphic Buffer。
ImageRasterBuffer类的成员函数UnmapBuffer接下来又调用成员函数resource_provider获得一个ResourceProvider对象,并且调用这个ResourceProvider对象的成员函数UnmapImage将上述Graphic Buffer从当前进程的地址空间中解除映射,如下所示:
void ResourceProvider::UnmapImage(const Resource* resource) { ...... if (resource->image_id) { GLES2Interface* gl = ContextGL(); ...... gl->UnmapImageCHROMIUM(resource->image_id); } }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
参数resource指向的一个Resource对象的成员变量image_id记录的是之前为其所创建的Graphic Buffer的Image ID。ResourceProvider类的成员函数UnmapImage所要做的事情就是将这个Graphic Buffer从当前进程的地址空间中解除映射。这是通过调用Command Buffer GL接口的成员函数UnmapImageCHROMIUM实现的。
从前面的分析可以知道,ResourceProvider类内部所使用的Command Buffer GL接口可以通过调用成员函数ContextGL获得。这个Command Buffer GL接口通过由GLES2Implementation类实现,它的成员函数UnmapImageCHROMIUM的实现如下所示:
void GLES2Implementation::UnmapImageCHROMIUMHelper(GLuint image_id) { gfx::GpuMemoryBuffer* gpu_buffer = gpu_memory_buffer_tracker_->GetBuffer( image_id); ...... gpu_buffer->Unmap(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数UnmapImageCHROMIUM首先调用成员变量gpu_memory_buffer_tracker_指向的一个GpuMemoryBufferTracker对象的成员函数GetBuffer获得Image ID等于参数image_id的一个GpuMemoryBufferImplSurfaceTexture对象,接着再调用这个GpuMemoryBufferImplSurfaceTexture对象的成员函数Unmap将记录在它内部的一个Graphic Buffer从当前进程的地址空间中解除映射,如下所示:
void GpuMemoryBufferImplSurfaceTexture::Unmap() { ...... ANativeWindow_unlockAndPost(native_window_); ...... }这个函数定主在文件external/chromium_org/content/common/gpu/client/gpu_memory_buffer_impl_surface_texture.cc中。
从前面的分析可以知道,GpuMemoryBufferImplSurfaceTexture类的成员变量native_window_指向的是一个ANativeWindow对象。之前我们从这个ANativeWindow对象的Graphic Buffer队列中获取了一个Graphic Buffer。这个Graphic Buffer用来创建了一个Skia Canvas。现在这个Skia Canvas已经使用完毕,于是就可以将它所使用的Graphic Buffer归还给上述ANativeWindow对象的Graphic Buffer队列,并且将该Graphic Buffer从当前进程的地址空间中解除映射。
从这里我们就可以看到,函数ANativeWindow_lock和ANativeWindow_unlockAndPost总是成对出现的。其中,调用函数ANativeWindow_lock是为了从一个ANativeWindow对象中获得一个Graphic Buffer。这个Graphic Buffer在使用完成之后,需要通过调用函数ANativeWindow_unlockAndPost返回给它所属的ANativeWindow对象。
这样,我们就分析完成了Zero Copy光栅化实现原理。它的核心实现原理就是为每一个网页分块都分配一个Graphic Buffer,并且每一个网页分块都光栅化各自的Graphic Buffer中。这些Graphic Buffer既可以被CPU访问,也可以被GPU访问。其中,CPU负责向这些Graphic Buffer写入网页分块的光栅化结果,而GPU在合成网页分块的时候,会读取这些Graphic Buffer的内容,并且将它们作为分配给网页分块的纹理对象的底层存储,从而通过操作这些纹理对象就可以合成网页分块的光栅化结果。这个过程不涉及到数据的拷贝,因此就称为Zero Copy光栅化。
注意,在Zero Copy光栅化中,分配给网页分块的Graphic Buffer会一直被网页分块使用,直到网页分块不再需要为止。接下来我们分析另一种CPU光栅化的原理。这种CPU光栅化称为One Copy光栅化,或者Image Copy光栅化。它的实现原理与Zero Copy光栅化是基本上一样的,只不过网页分块光栅化所使用的Graphic Buffer是临时创建的,并且在网页分块光栅化完成的时候,Graphic Buffer的内容会被拷贝到分配网页分块的纹理对象中去。
2. Image Copy Raster
从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,当使用One Copy光栅化方式时,CC模块会创建一个ImageCopyRasterWorkerPool对象来执行光栅化任务。从前面Chromium网页光栅化过程分析一文又可以知道,在执行光栅化任务的过程中,ImageCopyRasterWorkerPool类的成员函数AcquireCanvasForRaster会被调用来创建一个画布,如下所示:
SkCanvas* ImageCopyRasterWorkerPool::AcquireCanvasForRaster(RasterTask* task) { ...... scoped_ptr<ScopedResource> resource( resource_pool_->AcquireResource(task->resource()->size())); SkCanvas* canvas = resource_provider_->MapImageRasterBuffer(resource->id()); ...... raster_task_states_.push_back(RasterTaskState(task, resource.release())); return canvas; }
这个函数定义在文件external/chromium_org/cc/resources/image_copy_raster_worker_pool.cc中。
参数task指向的是一个RasterTaskImpl对象。这个RasterTaskImpl对象描述的便是当前要执行的光栅化任务。调用这个RasterTaskImpl对象的成员函数resource可以获得一个Resource对象。这个Resource对象描述的是一个纹理资源。这个纹理资源便是分配给当前要执行光栅化任务的网页分块的。
ImageCopyRasterWorkerPool类的成员函数AcquireCanvasForRaster并没有使用上述纹理资源来创建一个Skia Canvas,而是调用成员变量resource_pool_指向的一个ResourcePool对象的成员函数AcquireResource创建了另外一个临时的纹理资源,并且使用这个临时纹理资源创建了一个Skia Canvas。这意味着网页分块的光栅化结果将会保存这个临时的纹理资源中。
ImageCopyRasterWorkerPool类的成员函数AcquireCanvasForRaster是通过调用成员变量resource_provider_指向的一个ResourceProvider对象的成员函数MapImageRasterBuffer将临时创建的纹理资源封装为Skia Canvas的,这个过程可以参考前面Zero Copy光栅化原理的分析。
ImageCopyRasterWorkerPool类的成员函数AcquireCanvasForRaster将临时创建的纹理资源封装为Skia Canvas之后,会将参数task指向的光栅化任务和临时创建的纹理资源封装在一个RasterTaskState对象保存成员变量raster_task_states_描述的一个Vector中。这相当于是将临时创建的纹理资源保存起来,并且记录它所关联的光栅化任务。
从前面Zero Copy光栅化原理的分析可以知道,当网页分块光栅化过程执行完成后,ImageRasterWorkerPool类的成员函数ReleaseCanvasForRaster就会被调用,用来执行清理工作,如下所示:
void ImageCopyRasterWorkerPool::ReleaseCanvasForRaster(RasterTask* task) { RasterTaskState::Vector::iterator it = std::find_if(raster_task_states_.begin(), raster_task_states_.end(), RasterTaskState::TaskComparator(task)); ...... scoped_ptr<ScopedResource> resource(it->resource); std::swap(*it, raster_task_states_.back()); raster_task_states_.pop_back(); bool content_has_changed = resource_provider_->UnmapImageRasterBuffer(resource->id()); // |content_has_changed| can be false as result of task being canceled or // task implementation deciding not to modify bitmap (ie. analysis of raster // commands detected content as a solid color). if (content_has_changed) { resource_provider_->CopyResource(resource->id(), task->resource()->id()); ...... } resource_pool_->ReleaseResource(resource.Pass()); }这个函数定义在文件external/chromium_org/cc/resources/image_copy_raster_worker_pool.cc中。
ImageRasterWorkerPool类的成员函数ReleaseCanvasForRaster首先是在成员变量raster_task_states_描述的一个Vector中找到前面为参数task描述的光栅化任务所创建的临时纹理资源。从前面Zero Copy光栅化原理的分析可以知道,在将这个临时纹理资源封装为Skia Canvas的过程中,曾经创建了一个Graphic Buffer,并且将这个Graphic Buffer映射到了当前进程的地址空间来。
现在既然光栅化操作已经执行完成,就不再需要上述Graphic Buffer。这时候ImageRasterWorkerPool类的成员函数ReleaseCanvasForRaster就会调用成员变量resource_provider_指向的一个ResourceProvider对象的成员函数UnmapImageRasterBuffer将上述Graphic Buffer从当前进程的地址空间中解除映射。这个过程可以参考前面Zero Copy光栅化原理的分析。
ResourceProvider类的成员函数UnmapImageRasterBuffer的返回值是一个布尔值。当这个布尔值等于true的时候,就表示网页分块执行了光栅化操作,并且光栅化结果与前一次不相同。在这种情况下,ImageRasterWorkerPool类的成员函数ReleaseCanvasForRaster需要将当前这次的光栅化结果拷贝到分配给网页分块的纹理资源去,也就是将上面提到的临时创建的纹理资源的内容拷贝到分配给网页分块的纹理资源去。这实际上是一个纹理拷贝操作,可以通过调用成员变量resource_provider_指向的一个ResourceProvider对象的成员函数CopyResource完成。
将一个纹理src拷贝到另外一个纹理dst的原理如下所示:
1. 创建一个FBO,并且将该FBO与纹理dst绑定。
2. 将纹理src渲染在前面创建的FBO上。
第2步执行完成后,纹理dst的内容就会和纹理src的内容一样了。注意,这个拷贝过程都是在GPU内部完成的,所要拷贝的数据也是位于GPU内存中的,不涉及到将数据从CPU上传到GPU的过程。因此这个拷贝操作的效率是很高的。
将临时创建的纹理资源的内容拷贝到分配给网页分块的纹理资源去之后,临时创建的纹理资源就不再需要了,这时候ImageRasterWorkerPool类的成员函数ReleaseCanvasForRaster就可以调用Resource_provider_指向的一个ResourceProvider对象的成员函数ReleaseResource回收它,同时也能回收之前为它所创建的Graphic Buffer。这样就可以减少Graphic Buffer的使用。
Graphic Buffer是个好东西,但是毕竟它涉及到硬件,也就是GPU,在不同平台会有差异,同时它也是稀缺的资源,有些平台可能不允许App使用。在这种情况下,Chromium只能将网页分块光栅化在一个CPU能够访问的Pixel Buffer中。这种CPU光栅化方式就是我们最后要分析的一种称为Pixel Buffer的CPU光栅化方式。
3. Pixel Buffer Raster
从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,当使用Pixel Buffer光栅化方式时,CC模块会创建一个PixelBufferRasterWorkerPool对象来执行光栅化任务。从前面Chromium网页光栅化过程分析一文又可以知道,在执行光栅化任务的过程中,PixelBufferRasterWorkerPool类的成员函数AcquireCanvasForRaster会被调用来创建一个画布,如下所示:
SkCanvas* PixelBufferRasterWorkerPool::AcquireCanvasForRaster( RasterTask* task) { ...... resource_provider_->AcquirePixelRasterBuffer(task->resource()->id()); return resource_provider_->MapPixelRasterBuffer(task->resource()->id()); }这个函数定义在文件external/chromium_org/cc/resources/pixel_buffer_raster_worker_pool.cc中。
PixelBufferRasterWorkerPool类的成员变量resource_provider_指向的是一个ResourceProvider对象。PixelBufferRasterWorkerPool类的成员函数AcquireCanvasForRaster首先调用这个ResourceProvider对象的成员函数AcquirePixelRasterBuffer为正在执行光栅化操作的网页分块创建一个Pixel Buffer,接着又调用这个ResourceProvider对象的成员函数MapPixelRasterBuffer将该Pixel Buffer映射到当前进程的地址空间来。
Pixel Buffer被映射到当前进程的地址空间之后,就会得到一个图形缓冲区。这个图形缓冲区可以被CPU访问。这个图形缓冲区又会被用来创建一个Skia Canvas。这个Skia Canvas最后就会返回给调用者,用来执行网页分块的光栅化操作。这意味着网页分块会被光栅化在一个Pixel Buffer中。
接下来我们就继续分析ResourceProvider类的成员函数AcquirePixelRasterBuffer和MapPixelRasterBuffer的实现,以便了解一个基于Pixel Buffer的Skia Canvas的创建过程。
ResourceProvider类的成员函数AcquirePixelRasterBuffer的实现如下所示:
void ResourceProvider::AcquirePixelRasterBuffer(ResourceId id) { Resource* resource = GetResource(id); AcquirePixelBuffer(resource); resource->pixel_raster_buffer.reset(new PixelRasterBuffer(resource, this)); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
参数id描述的是为当前正在执行光栅化操作的网页分块所分配的一个纹理资源的ID。通过调用ResourceProvider类的成员函数GetResource可以根据该ID获得一个Resource对象。这个Resource对象描述的就是为当前正在执行光栅化操作的网页分块所分配的纹理资源。
有了这个Resource对象之后,ResourceProvider类的成员函数AcquirePixelRasterBuffer接下来就再调用另外一个成员函数AcquirePixelBuffer为它创建一个Pixel Buffer,如下所示:
void ResourceProvider::AcquirePixelBuffer(Resource* resource) { ...... GLES2Interface* gl = ContextGL(); ...... if (!resource->gl_pixel_buffer_id) resource->gl_pixel_buffer_id = buffer_id_allocator_->NextId(); gl->BindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, resource->gl_pixel_buffer_id); unsigned bytes_per_pixel = BitsPerPixel(resource->format) / 8; gl->BufferData(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, resource->size.height() * RoundUp(bytes_per_pixel * resource->size.width(), 4u), NULL, GL_DYNAMIC_DRAW); gl->BindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数AcquirePixelBuffer首先是调用成员函数ContextGL获得一个Command Buffer GL接口,接着又通过成员变量buffer_id_allocator_描述的一个IdAllocator对象分配了一个ID。这个ID将作为接下来要创建的Pixel Buffer的ID,并且会保存在参数resource指向的一个Resource对象的成员变量gl_pixel_buffer_id中。
ResourceProvider类的成员函数AcquirePixelBuffer接下来就通过前面获得的Command Buffer GL接口的成员函数BindBuffer和BufferData创建了一个类型为GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM的Pxiel Buffer,也就是调用GLES2Implementation类的成员函数BindBuffer和BufferData创建了一个类型为GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM的Pxiel Buffer。这两个函数的实现可以参考前面Chromium硬件加速渲染的GPU数据上传机制分析一文。
这一步执行完成之后,回到ResourceProvider类的成员函数AcquirePixelRasterBuffer中,它接下来就会将分配给当前正在执行光栅化操作的网页分块的纹理资源封装在一个PixelRasterBuffer对象中,并且这个PixelRasterBuffer对象会保存在该纹理资源的成员变量pixel_raster_buffer中。注意,这个纹理资源此时关联了一个Pixel Buffer。
回到PixelBufferRasterWorkerPool类的成员函数AcquireCanvasForRaster会中,它为当前正在执行光栅化操作的网页分块创建了一个Pixel Buffer之后,接下来就会调用ResourceProvider类的成员函数MapPixelRasterBuffer将这个Pixel Buffer映射到当前进程的地址空间来,并且使用映射后获得的图形缓冲区创建一个Skia Canvas,如下所示:
SkCanvas* ResourceProvider::MapPixelRasterBuffer(ResourceId id) { Resource* resource = GetResource(id); DCHECK(resource->pixel_raster_buffer.get()); return resource->pixel_raster_buffer->LockForWrite(); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数MapPixelRasterBuffer首先调用成员函数GetResource获得参数id描述的纹理资源,即一个Resource对象。从前面的分析可以知道,这个Resource对象的成员变量pixel_raster_buffer指向了一个PixelRasterBuffer对象。这个PixelRasterBuffer对象内部封装了前面所创建的Pixel Buffer。通过调用这个PixelRasterBuffer对象的成员函数LockForWrite就可以将该Pixel Buffer映射到当前进程的地址空间来,并且创建一个Skia Canvas。
PixelRasterBuffer类继承了BitmapRasterBuffer类,BitmapRasterBuffer类又继承了RasterBuffer类。PixelRasterBuffer类的成员函数LockForWrite是从RasterBuffer类继承下来的。前面分析Zero Copy光栅化原理时,我们已经分析过RasterBuffer类的成员函数LockForWrite的实现。它主要是调用由子类BitmapRasterBuffer实现的成员函数DoLockForWrite将前面创建的Pixel Buffer映射到当前进程的地址空间来,并且根据映射得到的图形缓冲区创建了一个Skia Canvas。
从前面分析的Zero Copy光栅化原理还可以知道,BitmapRasterBuffer类的成员函数DoLockForWrite是通过调用由子类实现的成员函数MapBuffer将前面创建的Pixel Buffer映射到当前进程的地址空间来的。在我们这个情景中,这个子类即为PixelRasterBuffer类。因此,接下来我们继续分析PixelRasterBuffer类的成员函数MapBuffer的实现,以便了解Pixel Buffer映射到一个进程的地址空间的过程。
PixelRasterBuffer类的成员函数MapBuffer的实现如下所示:
uint8_t* ResourceProvider::PixelRasterBuffer::MapBuffer(int* stride) { return resource_provider()->MapPixelBuffer(resource(), stride); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
PixelRasterBuffer类的成员函数MapBuffer首先调用成员函数resource获得分配给当前正在执行光栅化操作的网页分块的纹理资源,接着再调用成员变量resource_provider_指向的一个ResourceProvider对象的成员函数MapPixelBuffer将为该纹理资源创建的Pixel Buffer映射到当前进程的地址空间来。
ResourceProvider类的成员函数MapPixelBuffer的实现如下所示:
uint8_t* ResourceProvider::MapPixelBuffer(const Resource* resource, int* stride) { ...... GLES2Interface* gl = ContextGL(); ...... gl->BindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, resource->gl_pixel_buffer_id); uint8_t* image = static_cast<uint8_t*>(gl->MapBufferCHROMIUM( GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, GL_WRITE_ONLY)); gl->BindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0); ...... return image; }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
参数resource指向的Resource对象描述的是一个纹理资源。这个Resource对象的成员变量gl_pixel_buffer_id记录了之前为它所创建的一个Pixel Buffer的ID。
ResourceProvider类的成员函数MapPixelBuffer首先调用成员函数ContextGL获得一个OpenGL接口。从前面的分析可以知道,这个OpenGL接口实际上是一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,ResourceProvider类的成员函数MapPixelBuffer先调用它的成员函数BindBuffer设置当前要使用的Pixel Buffer的ID,接下来再调用它的成员函数MapBufferCHROMIUM将该Pixel Buffer映射到当前进程的地址空间来。映射完成后,就可以得到一块图形缓冲区。这个图形缓冲区的地址image将会返回给调用者,以便调用者用来创建一个Skia Canvas。
从前面的分析可以知道,Command Buffer GL接口是通过类GLES2Implementation描述的,它的成员函数MapBufferCHROMIUM的实现如下所示:
void* GLES2Implementation::MapBufferCHROMIUM(GLuint target, GLenum access) { ...... GLuint buffer_id; GetBoundPixelTransferBuffer(target, "glMapBufferCHROMIUM", &buffer_id); ...... BufferTracker::Buffer* buffer = buffer_tracker_->GetBuffer(buffer_id); ...... return buffer->address(); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数MapBufferCHROMIUM首先调用成员函数GetBoundPixelTransferBuffer获得上一步通过调用Command Buffer GL接口的成员函数BindBuffer设置的Pixel Buffer的ID,接下来又调用成员变量bufffer_tracker_指向的一个BufferTracker对象的成员函数GetBuffer获得上述ID所描述的一个BufferTracker::Buffer对象。从前面Chromium硬件加速渲染的GPU数据上传机制分析一文可以知道,这个BufferTracker::Buffer对象描述的实际上是一块共享内存。这块共享内存在创建的时候就已经映射到了当前进程的地址空间中,它是用来传递数据给GPU进程的。在我们这个情景中,传递的数据即为网页分块的光栅化结果。这个光栅化结果通过共享内存传递到GPU进程之后,GPU进程将会将它作为一个PBO的数据上传到GPU里面去。这个PBO的数据又会作为分配给网页分块的纹理对象的数据使用。这样通过分配给网页分块的纹理对象就可以访问网页分块的光栅化结果了。
通过调用前面获得的BufferTracker::Buffer对象的成员函数address可以获得它所描述的共享内存映射到当前进程地址空间的地址。这个地址最后被GLES2Implementation类的成员函数MapBufferCHROMIUM返回给调用者。调用者获得了这个地址之后,就可以用来创建一个Skia Canvas了。
这一步执行完成之后,我们就获得了一个基于Pixel Buffer的Skia Canvas。有了这个Skia Canvas之后,就可以通过CPU对网页分块执行光栅化操作了。从前面Zero Copy光栅化原理的分析可以知道,当网页分块的光栅化操作执行完成后,PixelBufferRasterWorkerPool类的成员函数ReleaseCanvasForRaster就会被调用,用来执行清理工作,如下所示:
void PixelBufferRasterWorkerPool::ReleaseCanvasForRaster(RasterTask* task) { ...... resource_provider_->ReleasePixelRasterBuffer(task->resource()->id()); }这个函数定义在文件external/chromium_org/cc/resources/pixel_buffer_raster_worker_pool.cc中。
PixelBufferRasterWorkerPool类的成员函数ReleaseCanvasForRaster调用成员变量resource_provider_指向的一个ResourceProvider对象的成员函数ReleasePixelRasterBuffer释放为参数task描述的光栅化任务所创建的Pixel Buffer,如下所示:
void ResourceProvider::ReleasePixelRasterBuffer(ResourceId id) { Resource* resource = GetResource(id); resource->pixel_raster_buffer.reset(); ReleasePixelBuffer(resource); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数ReleasePixelRasterBuffer首先调用成员函数GetResource获得参数id所描述的纹理资源,即一个Resource对象。从前面的分析可以知道,这个Resource对象的成员变量pixel_raster_buffer指向了一个PixelRasterBuffer对象。一个网页分块的光栅化操作执行完成之后,之前为它所创建的PixelRasterBuffer对象就不需要了,因此,ResourceProvider类的成员函数ReleasePixelRasterBuffer就可以将它释放掉。
最后,ResourceProvider类的成员函数ReleasePixelRasterBuffer还会调用另外一个成员函数ReleasePixelBuffer释放之前为Resource对象resource创建的Pixel Buffer,如下所示:
void ResourceProvider::ReleasePixelBuffer(Resource* resource) { ...... GLES2Interface* gl = ContextGL(); ...... gl->BindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, resource->gl_pixel_buffer_id); gl->BufferData( GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0, NULL, GL_DYNAMIC_DRAW); gl->BindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0); }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数ReleasePixelBuffer首先调用成员函数ContextGL获得一个Command Buffer GL接口。有了这个Command Buffer GL接口(也就是一个GLES2Implementation对象)之后,接下来就可以调用它的成员函数BindBuffer和BindData释放之前为参数resource指向的Resource对象所创建的Pixel Buffer。GLES2Implementation类的成员函数BindBuffer和BindData可以参考前面Chromium硬件加速渲染的GPU数据上传机制分析一文。
这样,我们就分析完成了Pixel Buffer光栅化实现原理。它的核心就是通过Skia Canvas将网页分块的内容光栅化在一个Pixel Buffer上。这个Pixel Buffer的数据接下来会上传到GPU里面去,作为分配给网页分块的纹理对象的数据。
至此,我们就分析完成了Zero Copy、One Copy和Pixel Buffer三种CPU光栅化原理。这里,我们做一个小结:
1. Zero Copy将网页分块光栅化在一个Graphic Buffer中。这个Graphic Buffer在网页分块光栅化结束之后,仍然被网页分块使用,因为网页分块的结果就保存在它里面。
2. One Copy也是将网页分块光栅化在一个Graphic Buffer中。不过这个Graphic Buffer在网页分块光栅化结束之后,就可以释放掉,因为它的内容会拷贝到分配给网页分块的纹理对象中去。这种方式可以减少对Graphic Buffer的需求。
3. Pixel Buffer将网页分块光栅化在一个Pixel Buffer中。这个Pixel Buffer的数据需要上传到GPU里面去,才能作为分配给网页分块的纹理对象的数据。其中,将Pixel Buffer的数据上传到GPU可能会遭遇到性能问题。前面两种方式之所以不会遇到这个性能问题,是因为Graphic Buffer可以同时被CPU和GPU访问,因此就不需要在CPU和GPU之间传递数据。
在前面Chromium网页GPU光栅化原理分析一文中,我们分析了使用GPU光栅化网页分块的原理。与CPU光栅化相比,GPU光栅化的性能更好。一方面是因为GPU更擅长执行图形绘制和渲染工作,另一方面是因为光栅化结果就留在GPU中,后面可以直接用来合成,不需要在CPU和GPU之间传递数据。不过,GPU涉及到硬件,不同平台的实现会有所差异,从而可能会引发兼容性问题。CPU光栅化是一种通用的方案,不会出现兼容性问题。可以说两者各有优缺点。
如前面Chromium网页光栅化过程分析一文所说,当所有标记为Acquired For Activation的网页分块都光栅化完成之后,CC模块的调度器就会触发一个ACTION_ACTIVATE_PENDING_TREE操作,也就是将CC Pending Layer Tree激活为CC Active Layer Tree。CC Active Layer Tree激活之后,就会被渲染到屏幕中,实际上就是将前面已经光栅化好的网页分块合成到屏幕中来显示。在接下来一篇文章中,我们就详细分析CC Pending Layer Tree激活为CC Active Layer Tree,以及CC Active Layer Tree激活之的的渲染过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。