Monday, July 8, 2013

How GPU Process work in multi process

I analysed GPU Process in InProcess mode. Refer to http://luxtella.blogspot.de/2013/06/gpu-process-analysis-hellotriangleexe.html

In this article, I analyse GPU Process in multi processs.

In Host

Pivotal classes are WebGraphicsContext3DCommandBufferImpl, CommandBufferProxyImpl, CommandBufferHelper.

Store cmd in Ring buffer

stack trace is as follows
gpu::CommandBufferHelper::WaitForAvailableEntries()
gpu::CommandBufferHelper::GetSpace()
gpu::gles2::GLES2Implementation::BindTexture()
content::WebGraphicsContext3DCommandBufferImpl::bindTexture()
cc::ResourceProvider::BindForSampling()
cc::GLRenderer::DrawContentQuad()
cc::GLRenderer::DoDrawQuad()
cc::DirectRenderer::DrawRenderPass()
cc::DirectRenderer::DrawFrame()
cc::LayerTreeHostImpl::DrawLayers()
cc::SingleThreadProxy::DoComposite()
cc::SingleThreadProxy::CommitAndComposite()
cc::SingleThreadProxy::CompositeImmediately()
content::RenderWidget::Composite()
content::RenderWidget::DoDeferredUpdate()
content::RenderWidget::DoDeferredUpdateAndSendInputAck()
content::RenderWidget::OnViewContextSwapBuffersComplete()
base::MessageLoop::RunTask()
base::MessageLoop::DeferOrRunPendingTask()
base::MessageLoop::DoWork()
base::MessagePumpDefault::Run()
base::MessageLoop::RunInternal()
base::RunLoop::Run()
base::MessageLoop::Run()
content::RendererMain()
content::RunZygote()
content::RunNamedProcessTypeMain()
content::ContentMainRunnerImpl::Run()
content::ContentMain()
main

WebGraphicsContext3D::bindTexture() calls GLES2Implementation::BindTexture()
void GLES2Implementation::BindTexture(GLenum target, GLuint texture) {
  ...
  if (IsTextureReservedId(texture)) {
    SetGLError(GL_INVALID_OPERATION, "BindTexture", "texture reserved id");
    return;
  }
  if (BindTextureHelper(target, texture)) {
    helper_->BindTexture(target, texture);
  }
  CheckGLError();
}

and GLES2CmdHelper::BindTexture is called.
void BindTexture(GLenum target, GLuint texture) {
    gles2::cmds::BindTexture* c = GetCmdSpace[gles2::cmds::BindTexture]();
    if (c) {
      c->Init(target, texture);
    }
  }

and CommandBufferHelper::GetSpace is called to alloc msg.
CommandBufferEntry* CommandBufferHelper::GetSpace(uint32 entries) {
  AllocateRingBuffer();
  if (!usable()) {
    return NULL;
  }
  GPU_DCHECK(HaveRingBuffer());
  ++commands_issued_;
  WaitForAvailableEntries(entries);
  CommandBufferEntry* space = &entries_[put_];
  put_ += entries;
  GPU_DCHECK_LE(put_, total_entry_count_);
  if (put_ == total_entry_count_) {
    put_ = 0;
  }
  return space;
}

WaitForAvailableEntries() calls CommandBufferHelper::FlushSync() if ring buffer is full.

Send cmd to GPU Process

stack trace is as follows
In common, SwapBuffer sends msg to GPU Process.
content::CommandBufferProxyImpl::Flush()
content::CompositorOutputSurface::SwapBuffers()
cc::GLRenderer::SwapBuffers()
cc::LayerTreeHostImpl::SwapBuffers()
cc::SingleThreadProxy::CompositeImmediately()
content::RenderWidget::Composite()
content::RenderWidget::DoDeferredUpdate()
content::RenderWidget::DoDeferredUpdateAndSendInputAck()
content::RenderWidget::OnViewContextSwapBuffersComplete()
base::MessageLoop::RunTask()
base::MessageLoop::DeferOrRunPendingTask()
base::MessageLoop::DoWork()
base::MessagePumpDefault::Run()
base::MessageLoop::RunInternal()
base::RunLoop::Run()
base::MessageLoop::Run()
content::RendererMain()
content::RunZygote()
content::RunNamedProcessTypeMain()
content::ContentMainRunnerImpl::Run()
content::ContentMain()
main
It is important to remember that getter also sends msg to GPU Process.
content::CommandBufferProxyImpl::Flush()
content::CommandBufferProxyImpl::FlushSync()
gpu::CommandBufferHelper::FlushSync()
gpu::CommandBufferHelper::Finish()
gpu::gles2::GLES2Implementation::WaitForCmd()
gpu::gles2::GLES2Implementation::GetBucketContents()
gpu::gles2::GLES2Implementation::GetBucketAsString()
gpu::gles2::GLES2Implementation::GetStringHelper()
gpu::gles2::GLES2Implementation::GetString()
content::WebGraphicsContext3DCommandBufferImpl::getString()
cc::GLRenderer::Initialize()
cc::GLRenderer::Create()
cc::LayerTreeHostImpl::CreateAndSetRenderer()
cc::LayerTreeHostImpl::InitializeRenderer()
cc::SingleThreadProxy::CreateAndInitializeOutputSurface()
cc::LayerTreeHost::InitializeOutputSurfaceIfNeeded()
cc::SingleThreadProxy::CommitAndComposite()
cc::SingleThreadProxy::CompositeImmediately()
content::RenderWidget::Composite()
content::RenderWidget::DoDeferredUpdate()
content::RenderWidget::DoDeferredUpdateAndSendInputAck()
content::RenderWidget::InvalidationCallback()
base::MessageLoop::RunTask()
...

CommandBufferProxyImpl::Flush() send GpuCommandBufferMsg_AsyncFlush to GPU Process to run stored cmds.
When sending GpuCommandBufferMsg_AsyncFlush, send offset on ring buffer and flush count as flush id.
void CommandBufferProxyImpl::Flush(int32 put_offset) {
  ...
  Send(new GpuCommandBufferMsg_AsyncFlush(route_id_,
                                          put_offset,
                                          ++flush_count_));
}

FYI, just see how cmd looks like

GLES2CmdHelper::BindTexture calls gles2::cmds::BindTexture::Init()
in gles2_cmd_format_autogen.h
struct BindTexture {
  typedef BindTexture ValueType;
  static const CommandId kCmdId = kBindTexture;
  static const cmd::ArgFlags kArgFlags = cmd::kFixed;

  static uint32 ComputeSize() {
    return static_cast(sizeof(ValueType));  // NOLINT
  }

  void SetHeader() {
    header.SetCmd();
  }

  void Init(GLenum _target, GLuint _texture) {
    SetHeader();
    target = _target;
    texture = _texture;
  }

  void* Set(void* cmd, GLenum _target, GLuint _texture) {
    static_cast(cmd)->Init(_target, _texture);
    return NextCmdAddress(cmd);
  }

  gpu::CommandHeader header;
  uint32 target;
  uint32 texture;
};

In GPU Process

Pivotal classes are GpuCommandBufferStub, CommandBufferService, GpuScheduler, CommandParser, GLES2DecoderImpl

Run cmd

stacktrace is as follows
gpu::gles2::GLES2DecoderImpl::DoBindTexture()
gpu::gles2::GLES2DecoderImpl::HandleBindTexture()
gpu::gles2::GLES2DecoderImpl::DoCommand()
gpu::CommandParser::ProcessCommand()
gpu::GpuScheduler::PutChanged()
gpu::CommandBufferService::Flush()
content::GpuCommandBufferStub::OnAsyncFlush()
_ZN30GpuCommandBufferMsg_AsyncFlush8DispatchIN7content20GpuCommandBufferStubES2_MS2_FvijEEEbPKN3IPC7MessageEPT_PT0_T1_.isra.51
content::GpuCommandBufferStub::OnMessageReceived()
content::MessageRouter::RouteMessage()
content::GpuChannel::HandleMessage()
base::MessageLoop::RunTask()
base::MessageLoop::DeferOrRunPendingTask()
base::MessageLoop::DoWork()
base::MessagePumpDefault::Run()
base::MessageLoop::RunInternal()
base::RunLoop::Run()
base::MessageLoop::Run()
content::GpuMain()
content::RunNamedProcessTypeMain()
content::ContentMainRunnerImpl::Run()
content::ContentMain()
main

As I mentioned in previous article, GLES2DecoderImpl calls real gl call via GLApi. Refer to gl_bindings_autogen_gl.cc and gl_gl_api_implementation.h

Where MakeCurrent()

GpuCommandBufferStub::OnMessageReceived() call GpuCommandBufferStub::MakeCurrent() before handling msg.
GpuCommandBufferStub::MakeCurrent() has two tasks.
1. glMakeCurrent() via decoder
2. If there is a error, handle lostContext.
bool GpuCommandBufferStub::MakeCurrent() {
  if (decoder_->MakeCurrent())
    return true;
  DLOG(ERROR) << "Context lost because MakeCurrent failed.";
  command_buffer_->SetContextLostReason(decoder_->GetContextLostReason());
  command_buffer_->SetParseError(gpu::error::kLostContext);
  if (gfx::GLContext::LosesAllContextsOnContextLost())
    channel_->LoseAllContexts();
  return false;
}


Compare to InProcess.

CommandBuffer

- Multi Process: WebGraphicsContext3DCommandBufferImpl uses CommandBufferProxyImpl. CommandBufferProxyImpl communicates to CommandBufferService via IPC
- InProcess : WebGraphicsContext3DInProcessCommandBufferImpl use directly CommandBufferService

Who control CommandBufferService. They must handle various exceptions (i.e. lostContext)

- Multi Process: GpuCommandBufferStub
- InProcess: GLInProcessContext

Both handle exceptions before gpu::GpuScheduler::PutChanged(). How does CommandBufferService::Flush() calls gpu::GpuScheduler::PutChanged() while CommandBufferService does not know GpuScheduler.

in GpuCommandBufferStub::OnInitialize()
  command_buffer_->SetPutOffsetChangeCallback(
      base::Bind(&GpuCommandBufferStub::PutChanged, base::Unretained(this)));

in GLInProcessContext::Initialize()
  command_buffer->SetPutOffsetChangeCallback(base::Bind(
      &GLInProcessContextImpl::PumpCommands, base::Unretained(this)));
Both register various callbacks to CommandBufferService as well as OffsetChangeCallback.