网页渲染过程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网页渲染过程相关的知识,希望对你有一定的参考价值。

参考技术A

这里,我们就以最简单的情况(仅包含html、css)说明一下网页的渲染过程。

从处理HTML文件中的一串串字符开始:

当HTML转化成DOM后,接下来浏览器会处理CSS,与转换HTML十分类似。

最终就转成下面的CSSOM树:

从DOM树的根部开始,遍历每个可见节点 :

比如,以上DOM树和CSSOM树合并成渲染树的结果如下:

从渲染树我们可以知道哪些节点是可见的,以及它们的CSS计算样式和几何形状,当渲染树完成之后,就可以开始绘制页面。

上面的步骤1到4也被称为关键路径绘制(英文简称:CRP,具体概念可参考谷歌浏览器的说明: https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path ):

概括的说,关键路径绘制包括:

了解网页渲染过程,有助于我们优化网页,提示渲染速度。这个过程被称作 优化关键渲染路径 (使上述步骤1到4所花费的总时间最小化的过程)

优化关键渲染路径 可以使内容尽快呈现到屏幕上,以获得更好的用户体验,但它也是一个很深的课题,不同的浏览器有不同的做法,这是火狐浏览器关于关键渲染路径的文档( https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path )。

以上,便是笔者对网页渲染过程的简单见解,此文章由笔者参考资料并加上 自己的见解 得来,若其中有不准确的地方还请指正

https://blog.logrocket.com/how-browser-rendering-works-behind-the-scenes-6782b0e8fb10/

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction

https://stackoverflow.com/questions/27637184/what-is-dom-reflow/27637245#27637245

Chromium网页绘图表面(Output Surface)创建过程分析

       在Chromium中,Render进程在绘制网页之前,要为网页创建一个绘图表面。绘图表面描述的是网页经过渲染之后得到的输出。这个输出需要交给Browser进程处理,才能显示在屏幕上。在硬件加速渲染条件下,这个输出有可能是一个OpenGL纹理,也有可能是一系列需要进一步进行绘制的OpenGL纹理,取决于Render进程使用直接渲染器还是委托渲染器。本文接下来就对网页的绘图表面的创建过程进行详细分析。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       关于网页绘图表面的更详细描述,可以参考Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文。本文的重点是分析Chromium的CC模块是如何触发网页绘图表面的创建的,以作为Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文的补充。

       网页绘图表面是由CC模块的调度器触发创建的,如图1所示:


图1 网页绘图表面的创建时机

       CC模块内部的状态机一旦检测到网页的Layer Tree创建和初始化完毕,就会通知调度器触发一个创建绘图表面的操作。CC模块在为网页创建绘图表面的过程中,也有伴随着网页分块管理器、资源池和光栅化工作者线程池等基础设施的创建。一旦这些基础设施准备完毕,网页才能开始进行绘制,也就是在图1中,第1步完成后,第2到第6步才可以周而复始地执行。

       从前面Chromium网页Layer Tree创建过程分析一文可以知道,当网页的Graphics Layer Tree的根节点创建出来之后,WebKit就会通知Chromium的Content层初始化一个CC Layer Tree,如下所示:

void RenderWidget::initializeLayerTreeView() {  
  compositor_ = RenderWidgetCompositor::Create(  
      this, is_threaded_compositing_enabled_);  
  ......  
  if (init_complete_)  
    StartCompositor();  
}  
       这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

       初始化CC Layer Tree的工作是通过调用RenderWidgetCompositor类的静态成员函数Create实现的。在前面Chromium网页Layer Tree创建过程分析一文中,我们分析了此时RenderWidget类的成员变量init_complete_的值等于true,因此接下来RenderWidget类的成员函数initializeLayerTreeView会调用另外一个成员函数StartCompositor为网页创建绘图表面。

       RenderWidget类的成员函数StartCompositor的实现如下所示:

void RenderWidget::StartCompositor() {
  ......
  compositor_->setSurfaceReady();
}
       这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。

       RenderWidget类的成员变量compositor_指向的是一个RenderWidgetCompositor对象,RenderWidget类的成员函数StartCompositor调用这个RenderWidgetCompositor对象的成员函数setSurfaceReady,用来通知它可以为网页创建绘图表面了。

       RenderWidgetCompositor类的成员函数setSurfaceReady的实现如下所示:

void RenderWidgetCompositor::setSurfaceReady() {
  layer_tree_host_->SetLayerTreeHostClientReady();
}

       这个函数定义在文件external/chromium_org/content/renderer/gpu/render_widget_compositor.cc中。

       RenderWidgetCompositor类的成员变量layer_tree_host_指向的是一个LayerTreeHost对象。这个LayerTreeHost对象就是用来管理CC Layer Tree的。RenderWidgetCompositor类的成员函数setSurfaceReady调用这个LayerTreeHost对象的成员函数SetLayerTreeHostClientReady,用来通知调度器为网页创建绘图表面。

       LayerTreeHost类的成员函数SetLayerTreeHostClientReady的实现如下所示:

void LayerTreeHost::SetLayerTreeHostClientReady() {
  proxy_->SetLayerTreeHostClientReady();
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

       LayerTreeHost类的成员变量proxy_指向的是一个ThreadProxy对象,LayerTreeHost类的成员函数SetLayerTreeHostClientReady调用这个ThreadProxy对象的成员函数SetLayerTreeHostClientReady,用来调度器为网页创建绘图表面。

       ThreadProxy类的成员函数SetLayerTreeHostClientReady的实现如下所示:

void ThreadProxy::SetLayerTreeHostClientReady() {
  ......
  Proxy::ImplThreadTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(&ThreadProxy::SetLayerTreeHostClientReadyOnImplThread,
                 impl_thread_weak_ptr_));
}
       这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。

       ThreadProxy类的成员函数SetLayerTreeHostClientReady向Compositor线程的消息队列发送一个Task。这个Task绑定了ThreadProxy类的成员函数SetLayerTreeHostClientReadyOnImplThread。因此接下来ThreadProxy类的成员函数SetLayerTreeHostClientReadyOnImplThread就会在Compositor线程中执行。

       ThreadProxy类的成员函数SetLayerTreeHostClientReadyOnImplThread的实现如下所示:

void ThreadProxy::SetLayerTreeHostClientReadyOnImplThread() {
  ......
  impl().scheduler->SetCanStart();
}
       这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。

       ThreadProxy类的成员函数SetLayerTreeHostClientReadyOnImplThread将调度器设置为启动状态,这是通过调用Scheduler类的成员函数SetCanStart实现的,如下所示:

void Scheduler::SetCanStart() {
  state_machine_.SetCanStart();
  ProcessScheduledActions();
}
       这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。

       Scheduler类的成员函数SetCanStart首先将内部的状态机设置为启动状态,这是通过调用SchedulerStateMachine类的成员函数SetCanStart实现的,如下所示:

class CC_EXPORT SchedulerStateMachine {
 public:
  ......

  // Set that we can create the first OutputSurface and start the scheduler.
  void SetCanStart() { can_start_ = true; }

  ......
};
       这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.h中。

       SchedulerStateMachine类的成员函数SetCanStart将成员变量can_start_的值设置为true,这样就会触发调度器为网页创建绘图表面。      

       回到Scheduler类的成员函数SetCanStart中,它将内部的状态机设置为启动状态之后,接着调用另外一个成员函数ProcessScheduledActions检查下一个要执行的操作,这个操作即为创建绘图表面。

       Scheduler类的成员函数ProcessScheduledActions的实现如下所示:

void Scheduler::ProcessScheduledActions() {
  ......

  SchedulerStateMachine::Action action;
  do {
    action = state_machine_.NextAction();
    ......
    state_machine_.UpdateState(action);
    ......
    switch (action) {
      ......
      case SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION:
        client_->ScheduledActionBeginOutputSurfaceCreation();
        break;
      ......
    }
  } while (action != SchedulerStateMachine::ACTION_NONE);

  SetupNextBeginFrameIfNeeded();
  ......

  if (state_machine_.ShouldTriggerBeginImplFrameDeadlineEarly()) {
    ......
    ScheduleBeginImplFrameDeadline(base::TimeTicks());
  }
}

       这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

       Scheduler类的成员函数ProcessScheduledAction的详细实现可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文,这里我们只关注它是如何触发网页绘图表面的创建的。

       Scheduler类的成员函数ProcessScheduledAction首先调用SchedulerStateMachine类的成员函数NextAction询问状态机下一个应该执行的操作,后者的实现如下所示:

SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
  if (ShouldUpdateVisibleTiles())
    return ACTION_UPDATE_VISIBLE_TILES;
  if (ShouldActivatePendingTree())
    return ACTION_ACTIVATE_PENDING_TREE;
  if (ShouldCommit())
    return ACTION_COMMIT;
  if (ShouldAnimate())
    return ACTION_ANIMATE;
  if (ShouldDraw()) {
    if (PendingDrawsShouldBeAborted())
      return ACTION_DRAW_AND_SWAP_ABORT;
    else if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)
      return ACTION_DRAW_AND_SWAP_FORCED;
    else
      return ACTION_DRAW_AND_SWAP_IF_POSSIBLE;
  }
  if (ShouldManageTiles())
    return ACTION_MANAGE_TILES;
  if (ShouldSendBeginMainFrame())
    return ACTION_SEND_BEGIN_MAIN_FRAME;
  if (ShouldBeginOutputSurfaceCreation())
    return ACTION_BEGIN_OUTPUT_SURFACE_CREATION;
  return ACTION_NONE;
}
      这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

      在我们这个情景中,SchedulerStateMachine类的成员函数NextAction在调用到另外一个成员函数ShouldBeginOutputSurfaceCreation时得到的返回值为true,后者的实现如下所示:

bool SchedulerStateMachine::ShouldBeginOutputSurfaceCreation() const {
  // Don't try to initialize too early.
  if (!can_start_)
    return false;

  // We only want to start output surface initialization after the
  // previous commit is complete.
  if (commit_state_ != COMMIT_STATE_IDLE)
    return false;

  // Make sure the BeginImplFrame from any previous OutputSurfaces
  // are complete before creating the new OutputSurface.
  if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_IDLE)
    return false;

  // We want to clear the pipline of any pending draws and activations
  // before starting output surface initialization. This allows us to avoid
  // weird corner cases where we abort draws or force activation while we
  // are initializing the output surface.
  if (active_tree_needs_first_draw_ || has_pending_tree_)
    return false;

  // We need to create the output surface if we don't have one and we haven't
  // started creating one yet.
  return output_surface_state_ == OUTPUT_SURFACE_LOST;
}
       这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

       SchedulerStateMachine类的成员函数ShouldBeginOutputSurfaceCreation返回true要满足以下六个条件:

       1. 网页的CC Layer Tree已经创建和初始化完成。这时候SchedulerStateMachine类的成员变量can_start_的值等于true。

       2. 状态机的CommitState状态等于COMMIT_STATE_IDLE。这意味着当前Main线程不是正在提交变化给Compositor线程渲染。

       3. 状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_IDLE。这意味着调度器已经向Compositor线程发出了一个渲染请求。

       4. Compositor线程的CC Pending Layer Tree已经激活为CC Active Layer Tree。这时候SchedulerStateMachine类的成员变量has_pending_tree_的值等于false。

       5. Compositor线程的CC Active Layer Tree被激活后,已经被执行过至少一次渲染操作了。这时候SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值等于false。

       6. 状态机的OutputSurfaceState状态等于OUTPUT_SURFACE_LOST。

       从前面的分析可以知道,第1个条件是满足的。由于这时候状态机也是刚刚初始化完成,因此后面五个条件也得到满足的。因此SchedulerStateMachine类的成员函数ShouldBeginOutputSurfaceCreation会返回true值给调用者。

       同时,从SchedulerStateMachine类的成员函数ShouldBeginOutputSurfaceCreation的实现也可以看出,如果网页绘图表面在创建之后,由于其它原因失效了,那么要等到网页的渲染管线达到一个稳定状态之后,才可以重新创建。失效的原因可以参考Chromium网页渲染调度器(Scheduler)实现分析一文。稳定状态指的是Main线程已经将当前的网页变化提交给了Compositor线程,而Compositor线程也已经处理这些变化引起的渲染工作。总的来说,就是图1所示的第2到第6个操作均已经连贯执行完成。

       回到SchedulerStateMachine类的成员函数NextAction中,当调用另外一个成员函数ShouldBeginOutputSurfaceCreation得到的返回值等于true的时候,它会返回一个ACTION_BEGIN_OUTPUT_SURFACE_CREATION值给调用者,也就是调度器,表示现在需要为网页创建绘图表面。

       回到Scheduler类的成员函数ProcessScheduledActions中,它知道了下一个要执行的操作是ACTION_BEGIN_OUTPUT_SURFACE_CREATION之后,接下来会先调用SchedulerStateMachine类的成员函数UpdateState更新状态机的状态,如下所示:

void SchedulerStateMachine::UpdateState(Action action) {
  switch (action) {
    ......

    case ACTION_BEGIN_OUTPUT_SURFACE_CREATION:
      ......
      output_surface_state_ = OUTPUT_SURFACE_CREATING;

      ......
      return;

      ......
  }
}

       这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

       当调度器接下要执行的操作是ACTION_BEGIN_OUTPUT_SURFACE_CREATION时,SchedulerStateMachine类的成员函数UpdateState主要就是修改状态机的OutputSurfaceState状态,也就是将它的状态从OUTPUT_SURFACE_LOST迁移至OUTPUT_SURFACE_CREATING,表示正在为网页创建绘图表面。

       回到Scheduler类的成员函数ProcessScheduledActions中,它更新了状态机的OutputSurfaceState状态之后,接下来就会调用成员变量client_指向的一个ThreadProxy对象的成员函数ScheduledActionBeginOutputSurfaceCreation为网页创建绘图表面。

       ThreadProxy类的成员函数ScheduledActionBeginOutputSurfaceCreation的实现如下所示:

void ThreadProxy::ScheduledActionBeginOutputSurfaceCreation() {
  ......
  Proxy::MainThreadTaskRunner()->PostTask(
      FROM_HERE,
      base::Bind(&ThreadProxy::CreateAndInitializeOutputSurface,
                 main_thread_weak_ptr_));
}
      这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。

      ThreadProxy类的成员函数ScheduledActionBeginOutputSurfaceCreation向Main线程的消息队列发送了一个Task,这个Task绑定了ThreadProxy类的成员函数CreateAndInitializeOutputSurface。因此,接下来ThreadProxy类的成员函数CreateAndInitializeOutputSurface就会在Main线程中执行。

      ThreadProxy类的成员函数CreateAndInitializeOutputSurface的实现如下所示:

void ThreadProxy::CreateAndInitializeOutputSurface() {
  ......
  DCHECK(IsMainThread());

  scoped_ptr<OutputSurface> output_surface =
      layer_tree_host()->CreateOutputSurface();

  if (output_surface) {
    Proxy::ImplThreadTaskRunner()->PostTask(
        FROM_HERE,
        base::Bind(&ThreadProxy::InitializeOutputSurfaceOnImplThread,
                   impl_thread_weak_ptr_,
                   base::Passed(&output_surface)));
    return;
  }

  ......
}
       这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。

       ThreadProxy类的成员函数CreateAndInitializeOutputSurface首先调用另外一个成员函数layer_tree_host获得一个LayerTreeHost对象。然后再调用这个LayerTreeHost对象的成员函数CreateOutputSurface为网页创建绘图表面。

       ThreadProxy类的成员函数CreateAndInitializeOutputSurface接下来又向Compositor线程的消息队列发送一个Task。这个Task绑定的函数是ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread,用来在Compositor线程中初始化前面创建好的绘图表面。

       接下来我们先分析LayerTreeHost类的成员函数CreateOutputSurface的实现,接着再分析ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread的实现。

       LayerTreeHost类的成员函数CreateOutputSurface的实现如下所示:

scoped_ptr<OutputSurface> LayerTreeHost::CreateOutputSurface() {
  return client_->CreateOutputSurface(num_failed_recreate_attempts_ >= 4);
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。

       从前面Chromium网页Layer Tree创建过程分析一文可以知道,LayerTreeHost类的成员变量client_指向的是一个RenderWidgetCompositor对象。LayerTreeHost类的成员函数CreateOutputSurface调用这个RenderWidgetCompositor对象的成员函数CreateOutputSurface为网页创建绘图表面。

       RenderWidgetCompositor类的成员函数CreateOutputSurface的实现如下所示:

scoped_ptr<cc::OutputSurface> RenderWidgetCompositor::CreateOutputSurface(
    bool fallback) {
  return widget_->CreateOutputSurface(fallback);
}
       这个函数定义在文件external/chromium_org/content/renderer/gpu/render_widget_compositor.cc中。

       从前面Chromium网页Layer Tree创建过程分析一文可以知道,RenderWidgetCompositor类的成员变量widget_指向的是一个RenderViewImpl对象。RenderWidgetCompositor类的成员函数CreateOutputSurface调用这个RenderViewImpl对象的成员函数CreateOutputSurface为网页创建绘图表面。

       RenderViewImpl类的成员函数CreateOutputSurface是从父类RenderWidget继承下来的。RenderWidget类的成员函数CreateOutputSurface为网页创建绘图表面的过程可以参考前面Chromium的GPU进程启动过程分析一文。这个过程实际上就是Render进程请求与GPU进程建立GPU通道的过程。GPU通道建立起来之后,Render进程就可以发送GPU命令给GPU进程执行了。注意,在Render进程中加载的每一个网页都会与GPU进程建立一个GPU通道,并且每一个GPU通道在GPU进程又对应有一个OpenGL上下文。我们也可以认为Render进程的一个绘图表面对应于GPU进程中的一个OpenGL上下文。

      假设网页A与GPU进程建立的GPU通道为G1,并且GPU通道为G1在GPU进程中对应的OpenGL上下文C1。当Render进程要渲染网页A的UI时,它就会通过GPU通道G1向GPU进程发送相应的GPU命令。GPU进程接收到这些GPU命令之后,就会激活OpenGL上下文C1,然后执行接收到的GPU命令。通过这种方式,GPU进程就可以同时接收不同网页的渲染命令,并且它们不会互相干扰,因为不同网页的渲染命令都是在各自的OpenGL上下文中执行的。这些都是Chromium硬件加速渲染机制相关的知识,可以参考前面Chromium硬件加速渲染机制基础知识简要介绍和学习计划这个系列的文章。

      回到ThreadProxy类的成员函数CreateAndInitializeOutputSurface中,它调用LayerTreeHost类的成员函数CreateOutputSurface为网页创建了一个绘图表面之后,接下来会请求Compositor线程初始化这个绘图表面。这是通过在Compositor线程中调用ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread实现的。

      ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread的实现如下所示:

void ThreadProxy::InitializeOutputSurfaceOnImplThread(
    scoped_ptr<OutputSurface> output_surface) {
  ......
  DCHECK(IsImplThread());

  LayerTreeHostImpl* host_impl = impl().layer_tree_host_impl.get();
  bool success = host_impl->InitializeRenderer(output_surface.Pass());
  ......

  if (success)
    impl().scheduler->DidCreateAndInitializeOutputSurface();
}
       这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc。

       ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread首先获得一个LayerTreeHostImpl对象,接着调用这个LayerTreeHostImpl对象的成员函数InitializeRenderer初始化参数output_surface描述的绘图表面。从前面Chromium的GPU进程启动过程分析一文可以知道,参数output_surface指向的实际上是一个CompositorOutputSurface对象。也就是说,网页的绘图表面是通过一个CompositorOutputSurface对象来描述的。

       初始化完成后,ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread会调用Scheduler类的成员函数DidCreateAndInitializeOutputSurface通知调度器,它之前调度执行的ACTION_BEGIN_OUTPUT_SURFACE_CREATION操作已经执行完成。这时候调度器就会修改状态机的OutputSurfaceState状态。

       接下来我们先分析LayerTreeHostImpl类的成员函数InitializeRenderer的实现,接下来再分析Scheduler类的成员函数DidCreateAndInitializeOutputSurface的实现。

       LayerTreeHostImpl类的成员函数InitializeRenderer的实现如下所示:

bool LayerTreeHostImpl::InitializeRenderer(
    scoped_ptr<OutputSurface> output_surface) {
  ......

  if (!output_surface->BindToClient(this))
    return false;

  output_surface_ = output_surface.Pass();
  resource_provider_ =
      ResourceProvider::Create(output_surface_.get(),
                               shared_bitmap_manager_,
                               settings_.highp_threshold_min,
                               settings_.use_rgba_4444_textures,
                               settings_.texture_id_allocation_chunk_size,
                               settings_.use_distance_field_text);

  ......

  CreateAndSetRenderer();

  ......

  if (settings_.impl_side_painting)
    CreateAndSetTileManager();

  ......

  return true;
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。 

       LayerTreeHostImpl类的成员函数InitializeRenderer首先调用参数output_surface指向的一个CompositorOutputSurface对象的成员函数BindToClient,让这个CompositorOutputSurface对象为它所描述的绘图表面在GPU进程中创建一个OpenGL上下文,如下所示:

bool CompositorOutputSurface::BindToClient(
    cc::OutputSurfaceClient* client) {
  ......

  if (!cc::OutputSurface::BindToClient(client))
    return false;

  ......

  return true;
}
      这个函数定义在文件external/chromium_org/content/renderer/gpu/compositor_output_surface.cc中。

      CompositorOutputSurface类的成员函数BindToClient调用父类OutputSurface的成员函数BindToClient来为当前正在处理的绘图表面在GPU进程中创建一个OpenGL上下文,如下所示:

bool OutputSurface::BindToClient(OutputSurfaceClient* client) {
  DCHECK(client);
  client_ = client;
  bool success = true;

  if (context_provider_) {
    success = context_provider_->BindToCurrentThread();
    if (success)
      SetUpContext3d();
  }

  if (!success)
    client_ = NULL;

  return success;
}
       这个函数定义在文件external/chromium_org/cc/output/output_surface.cc中。

       从前面的调用过程可以知道,参数client指向的是一个LayerTreeHostImpl对象。OutputSurface类的成员函数BindToClient将这个LayerTreeHostImpl对象保存在成员变量client_中。

       OutputSurface类的成员函数BindToClient接下来调用成员变量context_provider_指向的一个ContextProviderCommandBuffer对象的成员函数BindToCurrentThread为当前正在处理的绘图表面在GPU进程中创建一个OpenGL上下文。这个ContextProviderCommandBuffer对象的创建过程可以参考前面Chromium的GPU进程启动过程分析一文。

       ContextProviderCommandBuffer类的成员函数BindToCurrentThread的实现如下:

bool ContextProviderCommandBuffer::BindToCurrentThread() {
  ......

  if (!context3d_->makeContextCurrent())
    return false;

  ......

  return true;
}
       这个函数定义在文件external/chromium_org/content/common/gpu/client/context_provider_command_buffer.cc中。

       ContextProviderCommandBuffer类的成员变量context3d_指向的是一个WebGraphicsContext3DCommandBufferImpl对象。这个WebGraphicsContext3DCommandBufferImpl对象的创建过程可以参考前面Chromium的GPU进程启动过程分析一文。

       ContextProviderCommandBuffer类的成员函数BindToCurrentThread调用成员变量context3d_指向的WebGraphicsContext3DCommandBufferImpl对象的成员函数makeContextCurrent在GPU进程中创建一个OpenGL上下文。这个OpenGL上下文与前面创建的绘图表面相对应。

       WebGraphicsContext3DCommandBufferImpl类的成员函数makeContextCurrent请求GPU进程创建OpenGL上下文的过程可以参考前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文。

       回到OutputSurface类的成员函数BindToClient中,它为当前正在处理的绘图表面在GPU进程中创建了一个OpenGL上下文之后,接下来会调用另外一个成员函数SetUpContext3d设置一些GPU进程回调通知,如下所示:

void OutputSurface::SetUpContext3d() {
  ......

  context_provider_->SetLostContextCallback(
      base::Bind(&OutputSurface::DidLoseOutputSurface,
                 base::Unretained(this)));
  context_provider_->ContextSupport()->SetSwapBuffersCompleteCallback(
      base::Bind(&OutputSurface::OnSwapBuffersComplete,
                 base::Unretained(this)));
  context_provider_->SetMemoryPolicyChangedCallback(
      base::Bind(&OutputSurface::SetMemoryPolicy,
                 base::Unretained(this)));
}
       这个函数定义在文件external/chromium_org/cc/output/output_surface.cc中。

       OutputSurface类的成员函数SetUpContext3d设置了三个GPU进程回调通知:Lost Context Callback、Swap Buffers Complete Callback、Memory Policy Changed Callback,分别是GPU通道失效通知、网页UI渲染完成通知和内存策略变化通知。当这三个回调通知发生时,OutputSurface类的成员函数DidLoseOutputSurface、OnSwapBuffersComplete和SetMemoryPolicy就会被调用。其中,OutputSurface类的成员函数DidLoseOutputSurface会通知调度器将状态机的OutputSurfaceState状态修改为OUTPUT_SURFACE_LOST,这样调度器以后就可以重新为网页创建一个绘图表面;OutputSurface类的成员函数OnSwapBuffersComplete会通知调度器前面向GPU进程发出的一个SwapBuffers操作已经完成,也就是网页的UI已经被Browser进程合成完毕。

       回到LayerTreeHostImpl类的成员函数InitializeRenderer中,它为参数output_surface描述的绘图表面在GPU进程中创建了一个OpenGL上下文之后,接下来会将这个参数指向的CompositorOutputSurface对象保存在成员变量output_surface_中,并且还会调用ResourceProvider类的静态成员函数Create创建一个ResourceProvider对象,保存在另外一个成员变量resource_provider_中。这个ResourceProvider对象在后面的网页渲染过程中,用来创建各种GPU资源。我们在后面的文章中就会看到这一点。

       LayerTreeHostImpl类的成员函数InitializeRenderer接下来调用另外一个成员函数CreateAndSetRenderer为网页创建渲染器。创建出来的渲染器决定了网页的UI以什么样的方式合成到浏览器窗口中。

       LayerTreeHostImpl类的成员函数CreateAndSetRenderer的实现如下所示:

void LayerTreeHostImpl::CreateAndSetRenderer() {
  ......

  if (output_surface_->capabilities().delegated_rendering) {
    renderer_ = DelegatingRenderer::Create(
        this, &settings_, output_surface_.get(), resource_provider_.get());
  } else if (output_surface_->context_provider()) {
    renderer_ = GLRenderer::Create(this,
                                   &settings_,
                                   output_surface_.get(),
                                   resource_provider_.get(),
                                   texture_mailbox_deleter_.get(),
                                   settings_.highp_threshold_min);
  } else if (output_surface_->software_device()) {
    renderer_ = SoftwareRenderer::Create(
        this, &settings_, output_surface_.get(), resource_provider_.get());
  }

  ......
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。

       从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,当Render进程的启动参数设置了“enable-delegated-renderer”选项时,Render进程使用委托渲染器。这个委托渲染器可以通过调用DelegatingRenderer类的静态成员函数Create创建。所谓委托渲染器,就是它委托Browser进程渲染和合成网页的UI。具体来说,就是Compositor线程在渲染网页时,它并没有真的执行渲染操作,而是计算出一系列的Render Pass,然后传递给Browser进程的的渲染器直接合成在浏览器窗口中。Render Pass描述的是网页当前可见的一个分块,它本质上是一个纹理,并且指定了纹理坐标,以及纹理的其它渲染参数,Browser进程根据这些信息就可以将网页的UI渲染出来。与非委托渲染器相比,委托渲染器可以减少一次渲染操作。非委托渲染器的Compositor线程在渲染网页时,会将网页当前可见的分块全部渲染一个纹理上,然后再将这个纹理传递给Browser进程。Browser进程拿到这个纹理后,还要再执行一次渲染操作,也就是合成在浏览窗口上,网页的UI才能显示出来。这过程包含了两次渲染操作,一次发生Render进程,另一次发生在Browser进程。因此非委托渲染器比委托渲染器多执行了一次渲染操作。

       如果Render进程的启动参数没有设置“enable-delegated-renderer”选项,但是设置了使用硬件方式渲染网页的UI,那么Render进程就会使用非委托渲染器。这个非委托渲染器可以通过调用GLRenderer类的静态成员函数Create创建。注意,Browser进程也是通过CC模块来渲染浏览器窗口的,也就是它也像Render进程一样,将浏览器窗口抽象成CC Layer Tree、CC Pending Layer Tree和CC Active Layer Tree,这时候网页的UI就是其中的一个Layer。由于Browser进程不能再委托别人渲染自己的UI,因此在采用硬件方式渲染浏览器窗口的情况下,它就只能使用非委托渲染器来渲染浏览器窗口UI。

       如果Render进程使用软件方式渲染网页的UI,那么它使用的渲染器通过调用SoftwareRenderer类的静态成员函数Create创建。一般来说,在获取网页的截图时,才会使用软件方式渲染网页的UI。

       更多关于委托渲染器和非委托渲染器的知识,可以参考Chromium硬件加速渲染的UI合成过程分析一文。

       再回到LayerTreeHostImpl类的成员函数InitializeRenderer中,它为网页创建了渲染器之后,接下来判断网页是否使用线程化渲染方式。如果是的话,LayerTreeHostImpl类的成员变量setting_描述的一个LayerTreeSettings对象的成员变量impl_side_painting的值就会等于ture。这时候LayerTreeHostImpl类的成员函数InitializeRenderer就会调用另外一个成员函数CreateAndSetTileManager创建一个分块管理器(Tile Manager)。这是由于线程化渲染方式中,Main线程只是记录了每一个网页分块的绘制命令。在这种情况下,Compositor线程在渲染网页的UI之前,要先通过分块管理器执行每一个分块的绘制命令,以便得到一个图像。这个过程也就是光栅化过程。在非线程化渲染方式中,Main线程直接就将分块光栅化在一个图像中了,也就是它已经做了光栅化的操作了。

      我们假设网页使用的是线程化渲染方式,因此接下来我们继续分析LayerTreeHostImpl类的成员函数CreateAndSetTileManager的实现,如下所示:

void LayerTreeHostImpl::CreateAndSetTileManager() {
  ......

  ContextProvider* context_provider = output_surface_->context_provider();
  ......

  if (use_gpu_rasterization_ && context_provider) {
    resource_pool_ =
        ResourcePool::Create(resource_provider_.get(),
                             GL_TEXTURE_2D,
                             resource_provider_->best_texture_format());

    raster_worker_pool_ =
        DirectRasterWorkerPool::Create(proxy_->ImplThreadTaskRunner(),
                                       resource_provider_.get(),
                                       context_provider);
    on_demand_task_graph_runner_ = &synchronous_task_graph_runner_;
  } else if (UseZeroCopyTextureUpload()) {
    resource_pool_ =
        ResourcePool::Create(resource_provider_.get(),
                             GetMapImageTextureTarget(context_provider),
                             resource_provider_->best_texture_format());

    raster_worker_pool_ =
        ImageRasterWorkerPool::Create(proxy_->ImplThreadTaskRunner(),
                                      RasterWorkerPool::GetTaskGraphRunner(),
                                      resource_provider_.get());
    on_demand_task_graph_runner_ = RasterWorkerPool::GetTaskGraphRunner();
  } else if (UseOneCopyTextureUpload()) {
    // We need to create a staging resource pool when using copy rasterizer.
    staging_resource_pool_ =
        ResourcePool::Create(resource_provider_.get(),
                             GetMapImageTextureTarget(context_provider),
                             resource_provider_->best_texture_format());
    resource_pool_ =
        ResourcePool::Create(resource_provider_.get(),
                             GL_TEXTURE_2D,
                             resource_provider_->best_texture_format());

    raster_worker_pool_ = ImageCopyRasterWorkerPool::Create(
        proxy_->ImplThreadTaskRunner(),
        RasterWorkerPool::GetTaskGraphRunner(),
        resource_provider_.get(),
        staging_resource_pool_.get());
    on_demand_task_graph_runner_ = RasterWorkerPool::GetTaskGraphRunner();
  } else {
    resource_pool_ = ResourcePool::Create(
        resource_provider_.get(),
        GL_TEXTURE_2D,
        resource_provider_->memory_efficient_texture_format());

    raster_worker_pool_ = PixelBufferRasterWorkerPool::Create(
        proxy_->ImplThreadTaskRunner(),
        RasterWorkerPool::GetTaskGraphRunner(),
        resource_provider_.get(),
        transfer_buffer_memory_limit_);
    on_demand_task_graph_runner_ = RasterWorkerPool::GetTaskGraphRunner();
  }

  tile_manager_ =
      TileManager::Create(this,
                          proxy_->ImplThreadTaskRunner(),
                          resource_pool_.get(),
                          raster_worker_pool_->AsRasterizer(),
                          rendering_stats_instrumentation_);

  ......
}
       这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。

       分块管理器通过一个TileManager对象描述。这个TileManager对象可以通过调用TileManager类的静态成员函数Create创建。

       创建分块管理器一个Resource Pool和Raster Worker Pool。分块管理器的职责是光栅化分块,不同的光栅化方式需要不同类型的Resource Pool和Raster Worker Pool。

       CC模块提供了四种光栅化方式。第一种是使用GPU执行光栅化操作,后面三种使用CPU执行光栅化操作。注意,在硬件方式渲染网页UI的情况下,经过CPU光栅化后的分块仍然是通过GPU进行渲染的。

       当Render进程设置了"force-gpu-rasterization"和"enable-impl-side-painting"启动选项时,LayerTreeHostImpl类的成员变量use_gpu_rasterization_的值就会等于true,表示要使用GPU光栅化网页分块。但是只有在网页使用硬件方式渲染时,才会真的使用GPU光栅化网页分块。这是因为使用GPU光栅化分块时,分块就直接光栅化在一个纹理中。这个纹理可以被GPU继续渲染出来形成网页的UI。因此,只有在使用硬件方式渲染网页的情况下,使用GPU光栅化分块才有意义。否则的话,光栅化后的分块内容还需要从GPU读取出来再交给CPU渲染。这样效率将会极其低下。

        LayerTreeHostImpl类的成员变量output_surface_描述的是网页的绘图表面。从前面的分析可以知道,这个绘图表面是通过一个CompositorOutputSurface对象描述的,也就是LayerTreeHostImpl类的成员变量output_surface_指向的是一个CompositorOutputSurface对象。当调用这个CompositorOutputSurface对象的成员函数context_provider获得一个不为NULL的ContextProvider对象时,就说明网页使用硬件方式渲染UI。

        GPU将分块光栅化在一个类型为GL_TEXTURE_2D的纹理上,因此这时候分块管理器需要一个类型为GL_TEXTURE_2D的Resource Pool,也就是一个能够创建类型为GL_TEXTURE_2D的纹理的Resource Pool。与此同时,分块管理器通过一个名称为”Direct“的Raster Worker Pool执行GPU光栅化操作。这个Raster Worker Pool可以通过调用DirectRasterWorkerPool类的静态成员函数Create创建。“Direct”的意思就是直接将分块光栅化在GPU里面,以后可以继续通过GPU进行渲染。

       当Render进程设置了“enable-zero-copy”启动选项,并且平台支持GPU和CPU共享内存时,调用LayerTreeHostImpl类的成员函数UseZeroCopyTextureUpload得到的返回值就等于true,这时候将会使用CPU光栅化网页分块。在这种情况下,CPU将分块光栅化一块GPU和CPU都能访问的内存上,这样就可以避免在光栅化操作完成后,将分块内容从CPU拷贝到GPU的过程。因此,这种光栅化方式称为"Zero Copy"光栅化。GPU和CPU都能访问的内存是一种特殊的内存。在Android平台上,这种内存称为Graphics Buffer。CPU光栅化完成后,GPU可以将这个Graphics Buffer当作一个类型为GL_TEXTURE_EXTERNAL_OES的纹理访问。因此,这时候分块管理器需要一个类型为GL_TEXTURE_EXTERNAL_OES的Resource Pool,也就是一个能够创建类型为GL_TEXTURE_EXTERNAL_OES的纹理的Resource Pool。与此同时,分块管理器通过一个名称为”Image“的Raster Worker Pool执行CPU光栅化操作。这个Raster Worker Pool可以通过调用ImageRasterWorkerPool类的静态成员函数Create创建。

       当Render进程设置了“enable-one-copy”启动选项,并且平台支持GPU和CPU共享内存时,调用LayerTreeHostImpl类的成员函数UseZeroCopyTextureUpload得到的返回值就等于true,这时候也会使用CPU光栅化网页分块。不过在这种情况下,CPU只是将分块光栅化一块临时的GPU和CPU都能访问的内存上,然后再将这个内存拷贝在一个类型为GL_TEXTURE_2D的纹理上。由于需要执行一次拷贝操作,因此这种光栅化方式称为”One Copy“光栅化。不过,这个拷贝是直接在GPU内完成的,并没有涉及到从CPU读取数据到GPU或者从GPU读取数据到CPU的操作,因此效率不是问题。这时候分块管理器需要两个Resource Pool,一个类型为GL_TEXTURE_EXTERNAL_OES,另一个类型为GL_TEXTURE_2D。其中,前者用来创建临时的GPU和CPU都能访问的内存,后者用来创建类型为GL_TEXTURE_2D的纹理。与此同时,分块管理器通过一个名称为”Image Copy“的Raster Worker Pool执行CPU光栅化操作。这个Raster Worker Pool可以通过调用ImageCopyRasterWorkerPool类的静态成员函数Create创建。

       在其余情况下,分块管理器将使用CPU光栅化网页分块,并且是将分块光栅化在一个Pixel Buffer Object(PBO)中。这些PBO最后需要从CPU上传到GPU中,才能被GPU当作纹理访问。在这种情况下,分块管理器需要一个类型为GL_TEXTURE_2D的Resource Pool,用来创建类型为GL_TEXTURE_2D的纹理保存分块光栅化后得到的内容。与此同时,分块管理器通过一个名称为”Pixel Buffer“的Raster Worker Pool执行CPU光栅化操作。这个Raster Worker Pool可以通过调用PixelBufferRasterWorkerPool类的静态成员函数Create创建。

       我们注意到,LayerTreeHostImpl类有一个成员变量on_demand_task_graph_runner_,它指向的是一个TaskGraphRunner对象。这个TaskGraphRunner对象也是用来执行光栅化任务的,不过它只用在非委托渲染器中。在非委托渲染方式中,有时候会因为内存限制,使得不能够为所有的网页分块都分配一块独立的内存执行光栅化操作。这时候非委托渲染器会使用一块共用的内存来光栅化那些没有独立内存的分块。这些分块光栅化完成后就会马上渲染,以便将共用的内存释放出来给其它分块使用。因此,对于这些没有独立内存的分块,它们的光栅化过程是比较特殊的,需要通过LayerTreeHostImpl类的成员变量on_demand_task_graph_runner_描述的TaskGraphRunner对象进行。

       关于网页分块的光栅化过程,我们在后面的文章中再进行详细分析。

       这一步执行完成之后,网页的绘图表面就初始化完成了。回到ThreadProxy类的成员函数InitializeOutputSurfaceOnImplThread中,它接下来就会调用Scheduler类的成员函数DidCreateAndInitializeOutputSurface通知调度器修改状态机的OutputSurfaceState状态,如下所示:

void Scheduler::DidCreateAndInitializeOutputSurface() {
  ......
  state_machine_.DidCreateAndInitializeOutputSurface();
  ProcessScheduledActions();
}
       这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。

       Scheduler类的成员函数DidCreateAndInitializeOutputSurface调用SchedulerStateMachine类的成员函数DidCreateAndInitializeOutputSurface将状态机的OutputSurfaceState状态修改为OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT,如下所示:

void SchedulerStateMachine::DidCreateAndInitializeOutputSurface() {
  DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_CREATING);
  output_surface_state_ = OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT;

  ......
}
       这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。

       当状态机的OutputSurfaceState状态为OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT时,将会触发调度器尽快执行一个ACTION_SEND_BEGIN_MAIN_FRAME,也就是图1所示的第2个操作。这个操作将会请求Main线程绘制CC Layer Tree的内容。CC Layer Tree的内容绘制好之后,将会被同步到CC Pending Layer Tree中去执行光栅化操作,然后激活为CC Active Layer Tree。这个CC Active Layer Tree经过Compositor渲染后就得到网页的UI。

       至此,我们就分析完成网页的绘图表面创建和初始化过程了。有了绘图表面之后,接下来就可以绘制和渲染网页的内容了。在接下来的一篇文章中,我们就继续分析网页的绘制过程,也就是Main线程绘制CC Layer Tree的过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

以上是关于网页渲染过程的主要内容,如果未能解决你的问题,请参考以下文章

浏览器加载渲染网页过程解析--总结

浏览器加载渲染网页过程解析

Chromium插件(Plugin)执行3D渲染的过程分析

Android WebView启动Chromium渲染引擎的过程分析

网页从输入网址到渲染完成经历了哪些过程?

网页在浏览器的渲染过程