Chromium网页CPU光栅化原理分析.docx
《Chromium网页CPU光栅化原理分析.docx》由会员分享,可在线阅读,更多相关《Chromium网页CPU光栅化原理分析.docx(52页珍藏版)》请在冰豆网上搜索。
Chromium网页CPU光栅化原理分析
Chromium网页CPU光栅化原理分析
Chromium除了支持网页分块GPU光栅化,还支持CPU光栅化。
GPU光栅化的特点是快,缺点是硬件之间可能会导差异性,以及不是所有的绘图操作硬件都能很好地支持。
CPU光栅化的特点是通用,以及能够支持所有的绘图操作,缺点是较慢,特别是在网页使用硬件加速渲染的情况下,CPU的光栅化结果还需要上传到GPU去渲染。
本文接下来将详细分析CPU光栅化的原理,着重描述它是如何快速地光栅化结果上传到GPU去的。
从前面一文可以知道,网页分块的光栅化过程,实际上是执行之前所记录的分块绘制命令,最终得到一个包含RGB值的图形缓冲区,如下所示:
在CPU光栅化方式中,CPU将网页分块绘制一个图形缓冲区中。
这个图形缓冲区需要进一步交给GPU渲染,才能显示在屏幕中。
一般情况下,CPU访问的是系统内存,而GPU访问的是显卡内存。
CPU将数据提交给GPU,需要执行的一个操作是将数据从系统内存拷贝到显卡内存。
这个操作也称为GPU上传操作。
相比于GPU渲染操作,GPU上传操作是一个非常慢的过程。
典型的GPU上传操作是纹理上传。
纹理数据通常比较大,因此将它们上传到GPU去就会是一个性能问题。
网页分块CPU光栅化完成后得到的图形缓冲区就相当于是一个纹理数据。
因此在CPU光栅化过程中,也会碰到影响性能的纹理上传问题。
为了解决纹理上传慢的问题,Android平台提供了一种NativeBuffer,也称为GraphicBuffer。
GraphicBuffer有一个很好的特性,就是它既可以被CPU访问,也可以被GPU访问。
因此,如果我们为每一个网页分块都分配一个GraphicBuffer,并且将每一个网页分块都光栅化在各自的GaphicBuffer中,那么就可以免去将光栅化结果上传给GPU的步骤,从而大大地提高网页的渲染效率。
这种CPU光栅化方式就得名为ZeroCopy光栅化方式,也称为ImageRaster方式。
通常,好的东西都是有代价的。
GraphicBuffer也不例外,它不像系统内存一样,可以大量地使用。
为了减少GraphicBuffer的使用,Chromium只申请临时使用的GraphicBuffer,并且为每一个网页分配一个纹理对象。
每一个网页分块都光栅化在临时使用的GraphicBuffer中,并且在光栅化完成后,会将临时使用的GraphicBuffer的内容拷贝到各自的纹理对象中去,然后将临时使用的GraphicBuffer释放掉。
注意,这个拷贝操作是在GPU内部完成的,因此它的执行过程很快,不像纹理上传操作那样存在性能问题。
这种CPU光栅化方式就得名为OneCopy光栅化方式,也称为ImageCopyRaster方式。
有些Android手机,虽然本身提供了GraphicBuffer,但是只允许系统使用,不允许App使用。
在这种情况下,Chromium只能将网页分块光栅化在一个CPU能够访问的PixelBuffer中。
光栅化完成之后,再将这个PixelBuffer上传到GPU去渲染。
这样就避不开纹理上传慢的问题了。
这种CPU光栅化方称为PixelBufferRaster方式。
从前面的分析就可以知道,CPU光栅化方式有三种,分别是ImageRaster、ImageCopyRaster和PixelBufferRaster。
接下来,我们分别对它们的实现原理进行分析。
1.ImageRaster
从前面一文可以知道,当使用ZeroCopy光栅化方式时,CC模块会创建一个ImageRasterWorkerPool对象来执行光栅化任务。
从前面文章中一文又可以知道,在执行光栅化任务的过程中,ImageRasterWorkerPool类的成员函数AcquireCanvasForRaster会被调用来创建一个画布,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
SkCanvas*ImageRasterWorkerPool:
:
AcquireCanvasForRaster(RasterTask*task){
returnresource_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的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
SkCanvas*ResourceProvider:
:
MapImageRasterBuffer(ResourceIdid){
Resource*resource=GetResource(id);
AcquireImage(resource);
if(!
resource->image_raster_buffer.get())
resource->image_raster_buffer.reset(newImageRasterBuffer(resource,this));
returnresource->image_raster_buffer->LockForWrite();
}
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员函数MapImageRasterBuffer首先通过调用成员函数GetResource获得参数id描述的纹理资源,然后调用另外一个成员函数AcquireImage为该纹理资源创建一个GraphicBuffer。
为参数id描述的纹理资源创建了GraphicBuffer之后,ResourceProvider类的成员函数MapImageRasterBuffer再将该GraphicBuffer封装在一个ImageRasterBuffer对象中,然后再调用这个ImageRasterBuffer对象的成员函数LockForWrite根据它所封装的GraphicBuffer创建出一个画布来返回给调用者。
接下来,我们先分析ResourceProvider类的成员函数AcquireImage创建GraphicBuffer的过程,接着再分析ImageRasterBuffer类的成员函数LockForWrite根据GraphicBuffer创建画布的过程。
ResourceProvider类的成员函数AcquireImage的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidResourceProvider:
:
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接口,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
GLES2Interface*ResourceProvider:
:
ContextGL()const{
ContextProvider*context_provider=output_surface_->context_provider();
returncontext_provider?
context_provider->ContextGL():
NULL;
}
这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
ResourceProvider类的成员变量output_surface_指向的是一个CompositorOutputSurface对象。
这个CompositorOutputSurface对象描述的就是网页的绘图表面,它的创建过程可以参考前面一文。
调用这个CompositorOutputSurface对象的成员函数context_provider可以获得一个ContextProviderCommandBuffer对象。
这个ContextProviderCommandBuffer对象的创建过程可以参考前面一文。
有了这个ContextProviderCommandBuffer对象之后,调用它的成员函数ContextGL就可以获得一个GLES2Implementation对象。
这个GLES2Implementation对象描述的是一个CommandBufferGL接口,也就是它会将传递给它的GPU命令发送给GPU进程执行。
这个CommandBufferGL接口的创建和使用过程可以参考前面和这两篇文章。
回到ResourceProvider类的成员函数AcquireImage中,它获得了一个CommandBufferGL接口之后,就调用它的成员函数CreateImageCHROMIUM创建一个GraphicBuffer。
CommandBufferGL接口成功创建了一个GraphicBuffer之后,就会将这个GraphicBuffer的ID返回给ResourceProvider类的成员函数AcquireImage,后者将其保存在参数resource指向的一个Resource对象的成员变量image_id中,表示这个Resource对象描述的纹理资源关联了一个GraphicBuffer。
接下来我们继续分析通过CommandBufferGL接口创建GraphicBuffer的过程,也就是分析GLES2Implementation类的成员函数CreateImageCHROMIUM的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
GLuintGLES2Implementation:
:
CreateImageCHROMIUM(GLsizeiwidth,
GLsizeiheight,
GLenuminternalformat,
GLenumusage){
......
GLuintimage_id=
CreateImageCHROMIUMHelper(width,height,internalformat,usage);
......
returnimage_id;
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员函数CreateImageCHROMIUM调用另外一个成员函数CreateImageCHROMIUMHelper创建一个GraphicBuffer,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
GLuintGLES2Implementation:
:
CreateImageCHROMIUMHelper(GLsizeiwidth,
GLsizeiheight,
GLenuminternalformat,
GLenumusage){
......
//Createnewbuffer.
GLuintbuffer_id=gpu_memory_buffer_tracker_->CreateBuffer(
width,height,internalformat,usage);
......
returnbuffer_id;
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
GLES2Implementation类的成员变量gpu_memory_buffer_tracker_指向的是一个GpuMemoryBufferTracker对象,GLES2Implementation类的成员函数CreateImageCHROMIUMHelper调用这个GpuMemoryBufferTracker对象的成员函数CreateBuffer创建一个GraphicBuffer,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
int32GpuMemoryBufferTracker:
:
CreateBuffer(size_twidth,
size_theight,
int32internalformat,
int32usage){
int32image_id=0;
DCHECK(gpu_control_);
gfx:
:
GpuMemoryBuffer*buffer=gpu_control_->CreateGpuMemoryBuffer(
width,height,internalformat,usage,&image_id);
if(!
buffer)
return0;
std:
:
pair:
iterator,bool>result=
buffers_.insert(std:
:
make_pair(image_id,buffer));
DCHECK(result.second);
returnimage_id;
}
这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gpu_memory_buffer_tracker.cc中。
GpuMemoryBufferTracker类的成员变量gpu_control_指向的是一个CommandBufferProxyImpl对象。
这个CommandBufferProxyImpl对象的创建过程可以参考前面一文,它主要是用来发送GPU相关的IPC消息的。
GpuMemoryBufferTracker类的成员函数CreateBuffer通过调用上述CommandBufferProxyImpl对象的成员函数CreateGpuMemoryBuffer请求Browser进程创建一个GraphicBuffer。
这个GraphicBuffer的句柄被封装在一个gfx:
:
GpuMemoryBuffer对象中。
这个gfx:
:
GpuMemoryBuffer接下来又会以其封装的GraphicBuffer的ImageID为键值,保存在GpuMemoryBufferTracker类的成员变量buffers_描述的一个HashMap中。
GpuMemoryBufferTracker类的成员函数CreateBuffer最后将创建出来的GraphicBuffer的ImageID返回给调用者,调用者以后可以通过该ImageID引用创建出来的GraphicBuffer。
接下来我们继续分析GraphicBuffer的创建过程,也就是CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
gfx:
:
GpuMemoryBuffer*CommandBufferProxyImpl:
:
CreateGpuMemoryBuffer(
size_twidth,
size_theight,
unsignedinternalformat,
unsignedusage,
int32*id){
*id=-1;
......
int32new_id=channel_->ReserveGpuMemoryBufferId();
......
scoped_ptr:
GpuMemoryBuffer>gpu_memory_buffer(
channel_->factory()->AllocateGpuMemoryBuffer(
width,height,internalformat,usage));
......
//ThishandleisownedbytheGPUprocessandmustbepassedtoitorit
//willleak.Inotherwords,donotearlyoutonerrorbetweenhereandthe
//sendingoftheRegisterGpuMemoryBufferIPCbelow.
gfx:
:
GpuMemoryBufferHandlehandle=
channel_->ShareGpuMemoryBufferToGpuProcess(
gpu_memory_buffer->GetHandle());
if(!
Send(newGpuCommandBufferMsg_RegisterGpuMemoryBuffer(
route_id_,
new_id,
handle,
width,
height,
internalformat))){
returnNULL;
}
*id=new_id;
gpu_memory_buffers_[new_id]=gpu_memory_buffer.release();
returngpu_memory_buffers_[new_id];
}
这个函数定义在文件external/chromium_org/content/common/gpu/client/command_buffer_proxy_impl.cc中。
CommandBufferProxyImpl类的成员变量channel_指向的是一个GpuChannelHost对象。
这个GpuChannelHost对象的创建过程可以参考前面一文,它用来描述Render进程与GPU进程之间的GPU通道。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer首先调用上述GpuChannelHost对象的成员函数ReserveGpuMemoryBufferId生成一个ID。
这个ID将作为接下来在创建的GraphicBuffer的ImageID。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer接下来又调用上述GpuChannelHost对象的成员函数factory获得一个RenderThreadImpl对象。
这个RenderThreadImpl对象描述的是Render进程的MainThread。
有了这个RenderThreadImpl对象之后,CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer就调用它的成员函数AllocateGpuMemoryBuffer请求Browser进程创建一个GraphicBuffer。
Browser进程创建了一个GraphicBuffer之后,会将该GraphicBuffer的句柄返回给请求者,请求者再将这个句柄封装在一个gfx:
:
GpuMemoryBuffer对象中。
也就是说,CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer会获得一个gfx:
:
GpuMemoryBuffer对象,通过这个gfx:
:
GpuMemoryBuffer对象可以访问到前面请求Browser进程创建的GraphicBuffer。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer接下来将获得的GraphicBuffer注册到GPU进程中去,因为这个GraphicBuffer最终是在GPU进程中使用的。
注册是通过向GPU进程发送一个类型为GpuCommandBufferMsg_RegisterGpuMemoryBuffer的IPC消息实现的。
这个IPC消息携带了要注册的GraphicBuffer的句柄。
这个句柄可以通过调用前面获得的gfx:
:
GpuMemoryBuffer对象的成员函数GetHandle得到。
CommandBufferProxyImpl类的成员函数CreateGpuMemoryBuffer最后会将封装了GraphicBuffer句柄的gfx:
:
GpuMemoryBuffer对象返回给调用者。
在返回之前,这个gfx:
:
GpuMemoryBuffer对象会保存在CommandBufferProxyImpl类的成员变量gpu_memory_buffers_描述的一个std:
:
map中,键值即为被封装的GraphicBuffer的ImageID。
接下来我们先分析GraphicBuffer的创建过程,也就是RenderThreadImpl类的成员函数AllocateGpuMemoryBuffer的实现,接下来再分析Gra