首页 > 代码库 > webview chromium v35 2dcanvas实现流程详解
webview chromium v35 2dcanvas实现流程详解
与原生chromium(surfaceview方案)有很大不同。下面详细记下webview chromium中
2dcanvas的实现方案.及其在个别gpu上存在的一个性能瓶颈。
webkit建树时,遇到2dcanvas会为其单独创建一个RenderLayer,开启硬件绘制的情况下,
2dcanvas的RenderLayer对应的后端存储是cc模块中的TextureLayer.
2dcanvas对应的TextureLayer关联在cc的Layer Tree上。
网页上js通过canvas.getContext("2d")调用的具体绘制操作都
保存在SkDeferredDevice的DeferredPipeController中并没有真正执行。
render线程开始ThreadProxy::BeginMainFrame()流程,会依次调用cc的Layer Tree中各个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版本的唯一一个合成线程只做上屏的合成。即,render线程的ThreadProxy::BeginMainFrame()流程执行完,
合成线程(browser线程)开始的ThreadProxy::ScheduledActionDrawAndSwapIfPossible流程,
直接将网页内容渲染到on-screen framebuffer上。
这是L以前的渲染方式,确切说,只是android4.4采用的方案。android L上又有根本不同。
L的渲染机制已经非常类似原生chromium.
这里只说2dcanvas在android4.4方案上的实现。
对2dcanvas的硬绘而言,render线程和合成线程(browser线程)之间存在texture同步的问题。
这个texture先是在render线程中被绘制,存储绘制结果,然后在browser线程中被渲染到on-screen framebuffer上。
render线程和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().(这个过程不晓得起什么作用)
Canvas2DLayerBridge::prepareMailbox()接着调用webContext->insertSyncPoint()完成render线程和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().到这,我们看到了render线程和browser线程在cpu层面上的同步。
这个SyncPoint在render线程中signal后,browser线程ResourceProvider::LockForRead()才会执行
ConsumeTextureCHROMIUM()。开始消耗texture.
接着看gpu层面的同步:
render线程中,一个texture上可能执行了多次gl绘制指令,必须保证这些gl绘制指令在
该texture被browser线程使用前都在gpu上真正执行完成。
gpu提供了两种同步方式一种是显式的glflush,一种是隐式的GLFence.
所以render线程在执行绘制texture操作时,每次都会在改动texture的情况下
插入一个GLFence.browser线程中需要读写这个texture的位置都会wait
render线程中创建的这个GLFence.这样就实现了render线程和browser线程在gpu层面上对一个texture的同步。
browser线程wait render线程中创建的glfence的位置:
GLES2DecoderImpl::DoDrawElements();
GLES2DecoderImpl::PrepareTexturesForRender();
GLImageSync::WillUseTexImage();
NativeImageBuffer::WillRead();
render线程中,绘制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());
render线程和browser线程在gpu上的同步方式,存在一个问题,这个问题导致在个别gpu上2dcanvas存在性能瓶颈。
render线程对同一个texture可能调用多次gl绘制命令,35的实现方案中,每一个改变texture的gl 绘制命令后都会插入一个
glfence.实际上,只需要在同一个texture的最后一个改变texture内容的gl draw命令后插入一个glfence即可。如果在每一个
改变texture的gl绘制命令后都插入一个glfence,在个别gpu上,创建并插入glfence的过程会影响render线程下一个gldrawelement
的执行,导致这条命令执行时间过长,从而导致2dcanvas的性能瓶颈。偶觉得解决这个瓶颈思路应该是怎么在render线程合适的时机只创建一个glfence.
难点在于如何识别对一个texture调用的一组绘制命令中哪个是最后一个改变texture内容的。或者哪位大神有思路,可以交流一下哈。
PS 1:
Canvas2DLayerBridge::prepareMailbox()调用Canvas2DLayerBridge::flush()触发的
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。
webview chromium v35 2dcanvas实现流程详解