webview chromium v35 2dcanvas实现流程详解

来源:互联网 发布:日本人的素质 知乎 编辑:程序博客网 时间:2024/06/07 19:22

与原生chromium(surfaceview方案)有很大不同。下面详细记下webviewchromium中2dcanvas的实现方案.及其在个别gpu上存在的一个性能瓶颈。

三个线程(gpu线程,render线程,合成线程)及render线程调用gpu线程实现硬件绘制的具体过程

2dCanvas采用硬件加速的情况下,chromium内核中会多开一个gpu线程,gpu线程负责直接调用gles2库的gl指令。render线程作为gpu线程的一个client端,对gpu指令的所有调用都是通过gpu线程完成的。网页上js通过2dcontext调用的具体绘制操作都运行在render线程中。这些绘制操作最后都会通过GrContext转给gpu线程。在gpu线程中调用相应的gl指令。WebGraphicsContext3DInProcessCommandBuffer可以看做单进程版本gpu线程的入口类。下面我们看GrContext与

WebGraphicsContext3DInProcessCommandBuffer的关系图。


对于gpu线程可以认为2dcanvas硬件绘制的入口是GrContext。

下面我们看网页上的js命令通过2d context调用的绘制命令,通过GrContext最终绘制到texture上这个过程涉及的整体类图。


其中红色的路径标注的是网页上的js命令通过2d context调用的绘制命令,最终绘制到texture上的关键路径。

页面上通过2d context调用的gl操作最终都是通过GrContext调用到系统gles库的gl指令的。GrContext又是通过GrGpuGL调用gles库的gl指令,最终绘制到texture上。GrContext-GrGpuGL-texture之间的类图如下。


GrGpuGL中调用的gl指令就都通过WebGraphicsContext3DInProcessCommandBuffer传递到了gpu线程,从而调用到gles2库中的gl指令。

canvas-surface-device之间的关系图如下:


下面我们看render线程中2dcanvas准备一帧的触发过程

webkit建树时,遇到2dcanvas会为其单独创建一个RenderLayer。开启硬件绘制的情况下,2dcanvas的RenderLayer对应的后端存储是cc模块中的TextureLayer。2dcanvas对应的TextureLayer关联在cc的LayerTree上。

render线程开始ThreadProxy::BeginMainFrame()流程,会依次调用cc的LayerTree中各个Layer的update。对2dcanvas而言,调用的是texturelayer的update。
TextureLayer::Update()会触发Canvas2DLayerBridge::prepareMailbox()。
Canvas2DLayerBridge::prepareMailbox()中完成了2dcanvas绘制同步的所有关键步骤。
webview版本chromium,只有一个上屏的合成线程,这个合成线程使用的是browser线程的message_loop,所以合成线程与browser线程实际上是同一个。webview版本chromium与原生chromium版本渲染流程最根本的差别在于webview版本没有将网页内容先合成到off-screen的texture上,再将这个off-screen的texture合成到on-screen的framebuffer上。Webview方案chromium的唯一一个合成线程只做上屏的合成。即render线程的ThreadProxy::BeginMainFrame()流程执行完,合成线程(browser线程)开始的ThreadProxy::ScheduledActionDrawAndSwapIfPossible流程,直接将网页内容渲染到on-screen framebuffer上。
2dcanvas硬件绘制中存在的gpu线程和合成线程的同步问题
对2dcanvas的硬绘而言,gpu线程和合成线程(browser线程)之间存在texture同步的问题。这个texture先是在gpu线程中被绘制,存储绘制结果,然后在browser线程中被渲染到on-screen framebuffer上。gpu线程和browser线程对共享资源texure的协调使用,需要两个层面的同步cpu层面和gpu层面。
cpu层面的同步是通过SyncPoint完成的。
gpu层面的同步是通过GLFence完成的。

同步关系如下图所示:


先看cpu层面的同步
Canvas2DLayerBridge::prepareMailbox()调用完Canvas2DLayerBridge::flush(),
使texture的绘制命令都到达WebGraphicsContext3DInProcessCommandBufferImpl。
注意此时还没有真正在gpu上执行。接着调用m_canvas->newImageSnapshot()得到被绘制的目标texture的信息,保存在SkImage变量中。Canvas2DLayerBridge::prepareMailbox()接着调用webContext->produceTextureCHROMIUM()。这个过程会触发mailboxSynchronizer::PushTextureUpdates().将gpu线程中创建的texture通过eglCreateImageKHR转变成可在不同context共享的iamge资源,并将这个可共享的imamge资源存入全局的texture mailboxmap中,以供brower线程使用。

Canvas2DLayerBridge::prepareMailbox()接着调用webContext->insertSyncPoint()完成gpu线程和browser线程在cpu层面上的同步。
看browser线程在SyncPoint上的wait情况:
TextureLayer::Update()得到的是封装了绘制texture信息的mailbox.
TextureLayer::PushPropertiesTo()中将这个mailbox传给了TextureLayerImpl.
TextureLayerImpl::WillDraw()中调用
ResourceProvider::CreateResourceFromTextureMailbox().将mailbox保存在了
ResourceProvider创建的Resource中。
Browser线程合成时有如下调用关系:
GLRenderer::DrawRenderPassQuad()
ScopedReadLockGL::ScopedReadLockGL()调用
ResourceProvider::LockForRead()
ResourceProvider::LockForRead()中会调用
gl->WaitSyncPointCHROMIUM().到这,我们看到了gpu线程和browser线程在cpu层面上的同步。
这个SyncPoint在gpu线程中signal后,browser线程ResourceProvider::LockForRead()才会执行ConsumeTextureCHROMIUM()。开始消耗texture。
接着看gpu层面的同步
gpu线程中,一个texture上可能执行了多次gl绘制指令,必须保证这些gl绘制指令在
该texture被browser线程使用前都在gpu上真正执行完成。
gpu提供了两种同步方式一种是显式的glfinish,一种是隐式的GLFence.
所以gpu线程在执行绘制texture操作时,每次都会在改动texture的情况下
插入一个GLFence.browser线程中需要读写这个texture的位置都会wait
gpu线程中创建的这个GLFence.这样就实现了gpu线程和browser线程在gpu层面上对一个texture的同步。
browser线程waitgpu线程中创建的glfence的位置:
GLES2DecoderImpl::DoDrawElements();
GLES2DecoderImpl::PrepareTexturesForRender();
GLImageSync::WillUseTexImage();
NativeImageBuffer::WillRead();
gpu线程中,绘制texture触发的glfence创建:
GLES2DecoderImpl::DoDrawElements();
ScopedRenderTo::~ScopedRenderTo();
Framebuffer::OnDidRenderTo();
TextureAttachment::OnDidRenderTo();
Texture::OnDidModifyPixels();
GLImageSync::DidModifyTexImage();
NativeImageBuffer::DidWrite(gfx::GLImage*client);
write_fence_.reset(gfx::GLFence::CreateWithoutFlush());

2dcanvas硬件绘制的性能瓶颈
gpu线程和browser线程在gpu上的同步方式,存在一个问题,这个问题导致在个别gpu上2dcanvas存在性能瓶颈。gpu线程对同一个texture可能调用多次gl绘制命令,35的实现方案中,每一个改变texture的gl 绘制命令后都会插入一个glfence.实际上,只需要在同一个texture的最后一个改变texture内容的gl draw命令后插入一个glfence即可。如果在每一个改变texture的gl绘制命令后都插入一个glfence,在个别gpu上,创建并插入glfence的过程会影响gpu线程下一个gldrawelement的执行,导致这条命令执行时间过长,从而导致2dcanvas的性能瓶颈。

PS 1:

Canvas2DLayerBridge::prepareMailbox()调用Canvas2DLayerBridge::flush()触发的

draw命令转到WebGraphicsContext3DInProcessCommandBufferImpl中的流程。
Canvas2DLayerBridge::prepareMailbox();
Canvas2DLayerBridge::flush();
SkCanvas::flush();
SkDeferredDevice::flush();

void SkDeferredDevice::flush() {
    this->flushPendingCommands(kNormal_PlaybackMode);
    fImmediateCanvas->flush();
}
1.SkDeferredDevice的DeferredPipeController中pending的draw命令得以
执行(drawBitmap为例).
SkDeferredDevice::flushPendingCommands();
DeferredPipeController::playback()
SkGPipeReader::playback()
SkGPipeRead.cpp::drawBitmapRect_rp();
SkCanvas::drawBitmapRectToRect();
SkCanvas::internalDrawBitmapRect();
SkGpuDevice::drawBitmapRect();
SkGpuDevice::drawBitmapCommon();
SkGpuDevice::internalDrawBitmap();
GrContext::drawRectToRect();
GrDrawTarget::drawRect();
GrInOrderDrawBuffer::onDrawRect();
2.对texture的draw命令转到WebGraphicsContext3DInProcessCommandBufferImpl中的流程。
SkDeferredDevice::flush()
SkCanvas::flush()
SkGpuDevice::flush()
GrContext::resolveRenderTarget()
GrContext::flush()
GrInOrderDrawBuffer::flush()
GrGpu::onDraw()
GrGpuGL::onGpuDraw()


PS 2:

2dcanvas使用的texture在browser线程和render线程中的传递使用了mailbox_synchronize机制。
简单记下:
AwBrowserMainParts::PreMainMessageLoopRun()
中初始化gpu::gles2::MailboxSynchronizer::Initialize()。如果初始化不成功会禁止掉2dcanvas的硬件渲染。
MailboxManager::MailboxManager()中使用MailboxSynchronizer。
class MailboxSynchronizer包含
typedef std::map<Texture*, TextureVersion> TextureMap;
TextureMap textures_;
TextureVersion包含linked_ptr<TextureGroup> group;
struct TextureGroup包含
TextureDefinition definition;
std::set<TargetName> mailboxes;
TextureDefinition包含
scoped_refptr<NativeImageBuffer> image_buffer_;
NativeImageBuffer包含
struct ClientInfo {
    ClientInfo(gfx::GLImage* client);
    ~ClientInfo();

    gfx::GLImage* client;
    bool needs_wait_before_read;
    linked_ptr<gfx::GLFence> read_fence;
  };

std::list<ClientInfo> client_infos_;
scoped_ptr<gfx::GLFence> write_fence_;
gfx::GLImage* write_client_;

class GLImageSync : public gfx::GLImage;
GLImageSync包含scoped_refptr<NativeImageBuffer> buffer_;
GLImageSync的所有调用都转给NativeImageBuffer。
TextureDefinition的构造函数中,创建了NativeImageBuffer,
这个NativeImageBuffer随后设置给了GLImageSync。
这个GLImageSync随后又设置给了构建TextureDefinition的Texture.
GLImageSync通过NativeImageBuffer的AddClient注册给NativeImageBuffer。


0 0