Chromium网页Pending Layer Tree激活为Active Layer Tree的过程分析

2446次阅读  |  发布于5年以前

网页分块的光栅化操作完成后,CC Pending Layer Tree就会激活为CC Active Layer Tree。CC Active Layer Tree代表用户当前在屏幕上看到的网页内容,它可以快速响应用户输入,例如滚动和缩放。本文接下来就分析CC Pending Layer Tree激活为CC Active Layer Tree,以及CC Active Layer Tree的渲染过程。

CC Pending Layer Tree激活为CC Active Layer Tree是由调度器发起的,对应于网页渲染过程中的第5个步骤ACTION_ACTIVE_PENDING_TREE;激活后的CC Active Layer Tree会马上被调度器发起一个渲染操作,对应于网页渲染过程中的第6个步骤ACTION_DRAW_AND_SWAP_FORCED,如下所示:

图1 CC Active Layer Tree激活和渲染操作的时机

从前面Chromium网页渲染调度器(Scheduler)实现分析一文可以知道,当调度器调用SchedulerStateMachine类的成员函数NextAction询问状态机下一步要执行的操作时,SchedulerStateMachine类的成员函数NextAction会调用另外一个成员函数ShouldActivatePendingTree。当SchedulerStateMachine类的成员函数ShouldActivatePendingTree返回值等于true的时候,状态机就会提示调度器接下来需要执行ACTION_ACTIVATE_PENDING_TREE操作,也就是将CC Pending Layer Tree激活为CC Active Layer Tree,如下所示:

SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
      ......
      if (ShouldActivatePendingTree())
        return ACTION_ACTIVATE_PENDING_TREE;
      ......
      return ACTION_NONE;
    }

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

接下来我们就继续分析SchedulerStateMachine类的成员函数ShouldActivatePendingTree什么情况下会返回true,它的实现如下所示:

bool SchedulerStateMachine::ShouldActivatePendingTree() const {
      // There is nothing to activate.
      if (!has_pending_tree_)
        return false;

      // We should not activate a second tree before drawing the first one.
      // Even if we need to force activation of the pending tree, we should abort
      // drawing the active tree first.
      if (active_tree_needs_first_draw_)
        return false;

      // If we want to force activation, do so ASAP.
      if (PendingActivationsShouldBeForced())
        return true;

      // At this point, only activate if we are ready to activate.
      return pending_tree_is_ready_for_activation_;
    }

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

SchedulerStateMachine类的成员函数ShouldActivatePendingTree的返回值为true有两个前提条件:

1. 当前存在CC Pending Layer Tree。这时候SchedulerStateMachine类的成员变量has_pending_tree_的值等于true。从前面Chromium网页Layer Tree绘制过程分析一文可以知道,当CC模块采用Impl Side Painting机制时,也就是Main线程在绘制CC Layer Tree时,仅仅是记录网页的绘制命令,那么调度器在请求Main线程在绘制CC Layer Tree之前,会将SchedulerStateMachine类的成员变量has_pending_tree_的值设置为true,表示在CC Layer Tree绘制完成后,会得到一个新的CC Pending Layer Tree。

2. 上一次激活得到的CC Active Layer Tree已经至少被渲染过一次。这时候SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值等于false。接下来我们会看到,当一个CC Pending Layer Tree激活为CC Active Layer Tree之后,SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值会被设置为true,表示新激活的CC Active Layer Tree正在等待第一次绘制。这一点用来保证每一个CC Active Layer Tree被激活后,都会至少被渲染一次,避免网页内容的渲染画面出现跳跃。

满足了上述两个前提条件后,SchedulerStateMachine类的成员函数ShouldActivatePendingTree在两种情况下会返回true:

1. 此时网页的绘图表面(Output Surface)丢失了,也就是之前创建的绘图表面失效了。这时候调用SchedulerStateMachine类的成员函数PendingActivationsShouldBeForced得到的返回值为true,如下所示:

bool SchedulerStateMachine::PendingActivationsShouldBeForced() const {
      // These are all the cases where, if we do not force activations to make
      // forward progress, we might deadlock with the main thread.

      // There is no output surface to trigger our activations.
      if (output_surface_state_ == OUTPUT_SURFACE_LOST)
        return true;

      return false;
    }

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

前面Chromium网页渲染调度器(Scheduler)实现分析一文提到,当Render进程与GPU进程之间的GPU通道断开了连接,或者GPU进程在解析Render进程发送来的GPU命令时发生了错误,网页绘图表面的状态就会被设置为OUTPUT_SURFACE_LOST状态,表示它已经失败。这时候SchedulerStateMachine类的成员变量output_surface_state_就会设置为OUTPUT_SURFACE_LOST。在这种情况下,SchedulerStateMachine类的成员函数PendingActivationsShouldBeForced的返回值就为true。

本来这种情况将CC Pending Layer Tree激活为CC Active Layer Tree,后者也是无法进行渲染的,因为此时网页没有有效的绘图表面。但是如果不将CC Pending Layer Tree激活为CC Active Layer Tree,会导致网页的渲染管线不能正常地执行下去。

从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,网页的绘图表面在失效的情况下,需要重新创建。但是重新创建网页的绘图表面,又要求此时不能存在CC Pending Layer Tree。这样在存在CC Pending Layer Tree的时候,网页的绘图表面又出现失效,就会导致死锁。解决办法就是将CC Pending Layer Tree正常地激活为CC Active Layer Tree,使得不再存在CC Pending Layer Tree,从而使得死锁条件不成立,网页的渲染管线可以正常地执行。

2. 此时网页的CC Pending Layer Tree已经准备就绪激活为CC Active Layer Tree。从前面Chromium网页光栅化过程分析一文可以知道,当CC Pending Layer Tree的光栅化操作执行完成后,它就准备就绪激活为CC Active Layer Tree。这时候SchedulerStateMachine类的成员变量pending_tree_is_ready_for_activation_的值就会被设置为true。

回到SchedulerStateMachine类的成员函数NextAction中,当它调用成员函数ShouldActivatePendingTree得到的返回值等于true,它就会返回一个ACTION_ACTIVATE_PENDING_TREE给Scheduler类的成员函数ProcessScheduledActions。这时候Scheduler类的成员函数ProcessScheduledActions就会请求Compositor线程将CC Pending Layer Tree激活为CC Active Layer Tree,如下所示:

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

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

      SetupNextBeginFrameIfNeeded();    
      ......    

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

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

Scheduler类的成员函数ProcessScheduledActions的详细分析可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文。这时候Scheduler类的成员函数ProcessScheduledActions首先调用SchedulerStateMachine类的成员函数UpdateState更新状态机的状态,接着再调用成员变量client_指向的一个ThreadProxy对象的成员函数ScheduledActionActivatePendingTree请求Compositor线程将CC Pending Layer Tree激活为CC Active Layer Tree。

SchedulerStateMachine类的成员函数UpdateState的实现如下所示:

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

        case ACTION_ACTIVATE_PENDING_TREE:
          UpdateStateOnActivation();
          return;

        ......
      }
    }

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

SchedulerStateMachine类的成员函数UpdateState调用另外一个成员函数UpdateStateOnActivation更新状态机的内部状态,如下所示:

void SchedulerStateMachine::UpdateStateOnActivation() {
      if (commit_state_ == COMMIT_STATE_WAITING_FOR_ACTIVATION)
        commit_state_ = COMMIT_STATE_IDLE;

      if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION)
        output_surface_state_ = OUTPUT_SURFACE_ACTIVE;

      if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION)
        forced_redraw_state_ = FORCED_REDRAW_STATE_WAITING_FOR_DRAW;

      has_pending_tree_ = false;
      pending_tree_is_ready_for_activation_ = false;
      active_tree_needs_first_draw_ = true;
      needs_redraw_ = true;
    }

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

SchedulerStateMachine类的成员函数UpdateStateOnActivation首先检查是否需要修改状态机的CommitState、OutputSurfaceState和ForcedRedrawOnTimeoutState状态:

1. 如果状态机的CommitState状态为COMMIT_STATE_WAITING_FOR_ACTIVATION,也就是CC Layer Tree正在等待上一个CC Pending Layer Tree激活为CC Acitve Layer Tree,那么既然现在CC Pending Layer Tree即将激活为CC Acitve Layer Tree,因此就可以将其迁移为COMMIT_STATE_IDLE。

2. 如果状态机的OutputSurfaceState状态为OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION,也就是新创建的绘图表面正在等待第一个CC Pending Layer Tree激活为CC Acitve Layer Tree,那么既然现在这个CC Pending Layer Tree即将激活为CC Acitve Layer Tree,因此就可以将其迁移为OUTPUT_SURFACE_ACTIVE。

3. 如果状态机的ForcedRedrawOnTimeoutState状态为FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION,也就是调度器正在等待上一个CC Pending Layer Tree被激活,以及可以Main线程可以绘制下一个CC Layer Tree,那么既然现在这个CC Pending Layer Tree即将激活为CC Acitve Layer Tree,因此就可以将其迁移为FORCED_REDRAW_STATE_WAITING_FOR_DRAW。

关于状态机的CommitState、OutputSurfaceState和ForcedRedrawOnTimeoutState状态迁移的更详细描述,可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文。

除了检查是否需要修改状态机的CommitState、OutputSurfaceState和ForcedRedrawOnTimeoutState状态,SchedulerStateMachine类的成员函数UpdateStateOnActivation还会:

1. 将成员变量has_pending_tree_和pending_tree_is_ready_for_activation_的值设置为false,表示上一个CC Pending Layer Tree已经激活为CC Active Layer Tree。

2. 将成员变量active_tree_needs_first_draw_和needs_redraw_的值设置为true,表示刚刚激活的CC Active Layer Tree需要进行第一次渲染。

注意,一旦调度器发出某个操作,那么就认为该操作一定会被执行,于是就会提前修改内部状态。例如,调度器发出ACTION_ACTIVATE_PENDING_TREE操作时,尽管现存的CC Pending Layer Tree还没有激活为CC Active Layer Tree,但是调度器会认为现在的CC Pending Layer Tree一定会被激活为CC Active Layer Tree。这样做是没有问题的,因为调度器发出的操作要么是给Main线程执行,要么是给Compositor线程执行。无论是哪一个线程,它们都是消息驱动执行的,并且先被请求执行的操作不会被后请求执行的操作中断或者推迟。这样就可以保证每一个请求执行的操作都会按照请求时的先后顺序执行。因此调度器就可以认为它一旦发出某个操作,这个操作就一定会执行。

回到Scheduler类的成员函数ProcessScheduledActions中,它修改了状态机的状态之后,接下来调用ThreadProxy类的成员函数ScheduledActionActivatePendingTree请求Compositor线程将CC Pending Layer Tree激活为CC Active Layer Tree,如下所示:

void ThreadProxy::ScheduledActionActivatePendingTree() {
      ......
      impl().layer_tree_host_impl->ActivatePendingTree();
    }

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

ThreadProxy类的成员函数ScheduledActionActivatePendingTree首先调用成员函数impl获得一个CompositorThreadOnly对象。这个CompositorThreadOnly对象的成员变量layer_tree_host_impl指向一个LayerTreeHostImpl对象。这个LayerTreeHostImpl对象负责管理CC Pending Layer Tree和CC Active Layer Tree。有了这个LayerTreeHostImpl对象之后,ThreadProxy类的成员函数ScheduledActionActivatePendingTree再调用它的成员函数ActivatePendingTree将CC Pending Layer Tree激活为CC Active Layer Tree。

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

void LayerTreeHostImpl::ActivatePendingTree() {
      ......

      if (pending_tree_->needs_full_tree_sync()) {
        active_tree_->SetRootLayer(
            TreeSynchronizer::SynchronizeTrees(pending_tree_->root_layer(),
                                               active_tree_->DetachLayerTree(),
                                               active_tree_.get()));
      }
      TreeSynchronizer::PushProperties(pending_tree_->root_layer(),
                                       active_tree_->root_layer());
      ......

      // Now that we've synced everything from the pending tree to the active
      // tree, rename the pending tree the recycle tree so we can reuse it on the
      // next sync.
      pending_tree_.swap(recycle_tree_);
      ......

      client_->OnCanDrawStateChanged(CanDraw());
      SetNeedsRedraw();
      ......
    }

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

LayerTreeHostImpl类的成员变量pending_tree_和active_tree_指向两个不同的LayerTreeImpl对象。其中,成员变量pending_tree_指向的LayerTreeImpl对象描述的就是刚刚完成光栅化操作的CC Pending Layer Tree,而成员变量active_tree_指向的LayerTreeImpl对象描述的上一次得到的CC Active Layer Tree。

LayerTreeHostImpl类的成员函数ActivatePendingTree首先判断刚刚完成光栅化操作的CC Pending Layer Tree与上次得到的CC Active Layer Tree在结构上是否发生了变化,也就是CC Pending Layer Tree是否新增或者删除了Layer。如果发生了变化,那么就需要将CC Pending Layer Tree的结构同步到CC Active Layer Tree中去。这是通过调用TreeSynchronizer类的静态成员函数SynchronizeTrees实现的。从前面Chromium网页Layer Tree同步为Pending Layer Tree的过程分析一文可以知道,将CC Layer Tree的结构同步到CC Pending Layer Tree也是通过调用TreeSynchronizer类的静态成员函数SynchronizeTrees实现的。因此,这里我们就不再重复分析TreeSynchronizer类的静态成员函数SynchronizeTrees的实现。

LayerTreeHostImpl类的成员函数ActivatePendingTree接下来又调用TreeSynchronizer类的静态成员函数PushProperties将CC Pending Layer Tree的属性变化同步到CC Active Layer Tree中去。从前面Chromium网页Layer Tree同步为Pending Layer Tree的过程分析一文可以知道,将CC Layer Tree的属性变化同步到CC Pending Layer Tree也是通过调用TreeSynchronizer类的静态成员函数PushProperties实现的。因此,这里我们也不再重复分析TreeSynchronizer类的静态成员函数PushProperties的实现。

从上面两个操作可以知道,将CC Pending Layer Tree激活为CC Active Layer Tree,实际上是将CC Pending Layer Tree的信息同步到CC Active Layer Tree中去,类似于前面Chromium网页Layer Tree同步为Pending Layer Tree的过程分析一文分析的将CC Layer Tree的信息同步到CC Pending Layer Tree中去。

再接下来,LayerTreeHostImpl类的成员函数ActivatePendingTree主要做了三件事情:

1. 将成员变量pending_tree_指向的LayerTreeImpl对象转移到另外一个成员变量recycle_tree_中去。这时候成员变量pending_tree_的值将变为NULL,表示目前不存在CC Pending Layer Tree了。与此同时,转移到成员变量recycle_tree_去的LayerTreeImpl对象在下一次将CC Layer Tree同步为CC Pending Layer Tree的时候,将会被复用。这一点可以参考前面Chromium网页Layer Tree同步为Pending Layer Tree的过程分析一文。

2. 调用成员变量client_指向的一个ThreadProxy对象的成员函数OnCanDrawStateChanged通知调度器新激活的CC Active Layer Tree已经准备就绪渲染。注意,这时候调用LayerTreeHostImpl类的成员函数CanDraw得到的返回值将会等于true。

3. 调用成员函数SetNeedsRedraw通知调度器对CC Active Layer Tree进行渲染。

接下来,我们就继续分析ThreadProxy类的成员函数OnCanDrawStateChanged和LayerTreeHostImpl类的成员函数SetNeedsRedraw的实现,以便了解新激活的CC Active Layer Tree的渲染过程。

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

void ThreadProxy::OnCanDrawStateChanged(bool can_draw) {
      ......
      impl().scheduler->SetCanDraw(can_draw);
      ......
    }

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

ThreadProxy类的成员函数OnCanDrawStateChanged主要是通知调度器,新激活的CC Active Layer Tree已经准备就绪渲染。这是通过调用Scheduler类的成员函数SetCanDraw实现的,如下所示:

void Scheduler::SetCanDraw(bool can_draw) {
      state_machine_.SetCanDraw(can_draw);
      ......
    }

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

Scheduler类的成员函数OnCanDrawStateChanged主要是调用SchedulerStateMachine类的成员函数SetCanDraw修改调度器内部使用的状态机的状态,如下所示:

void SchedulerStateMachine::SetCanDraw(bool can_draw) { can_draw_ = can_draw; }

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

SchedulerStateMachine类的成员函数SetCanDraw将参数can_draw的值保存在成员变量can_draw_中。从前面的调用过程可以知道,参数can_draw的值等于true,因此这时候SchedulerStateMachine类的成员变量can_draw_的值也等于true,表示CC Active Layer Tree当前可以进行渲染。

回到LayerTreeHostImpl类的成员函数ActivatePendingTree中,它通知调度器新激活的CC Active Layer Tree已经准备就绪渲染之后,接下来再调用另外一个成员函数SetNeedsRedraw对新激活的CC Active Layer Tree进行渲染,如下所示:

void LayerTreeHostImpl::SetNeedsRedrawRect(const gfx::Rect& damage_rect) {
      ......
      client_->SetNeedsRedrawRectOnImplThread(damage_rect);
    }

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

LayerTreeHostImpl类的成员函数SetNeedsRedraw主要是将调用成员变量client_指向的一个ThreadProxy对象的成员函数SetNeedsRedrawOnImplThread通知调度器对新激活的CC Active Layer Tree进行渲染,如下所示:

void ThreadProxy::SetNeedsRedrawOnImplThread() {
      ......
      impl().scheduler->SetNeedsRedraw();
    }

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

ThreadProxy类的成员函数SetNeedsRedrawOnImplThread主要是调用Scheduler类的成员函数SetNeedsRedraw通知调度器对新激活的CC Active Layer Tree进行渲染,如下所示:

void Scheduler::SetNeedsRedraw() {
      state_machine_.SetNeedsRedraw();
      ProcessScheduledActions();
    }

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

Scheduler类的成员函数SetNeedsRedraw首先调用SchedulerStateMachine类的成员函数SetNeedsRedraw修改调度器内部使用的状态机的状态,如下所示:

void SchedulerStateMachine::SetNeedsRedraw() { needs_redraw_ = true; }

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

SchedulerStateMachine类的成员函数SetNeedsRedraw主要是将成员变量needs_redraw_的值设置为true,表示接下来要调度执行一个渲染操作。

回到Scheduler类的成员函数SetNeedsRedraw中,当它将调度器内部使用的状态机的成员变量needs_redraw_的值设置为true之后,再调用另外一个成员函数ProcessScheduledActions时,就会触发调度器请求Compositor线程执行一个渲染操作,如下所示:

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

      SchedulerStateMachine::Action action;
      do {
        action = state_machine_.NextAction();
        ......
        state_machine_.UpdateState(action);
        ......
        switch (action) {
          ......
          case SchedulerStateMachine::ACTION_DRAW_AND_SWAP_IF_POSSIBLE:
            DrawAndSwapIfPossible();
            break;
          case SchedulerStateMachine::ACTION_DRAW_AND_SWAP_FORCED:
            client_->ScheduledActionDrawAndSwapForced();
            break;
          case SchedulerStateMachine::ACTION_DRAW_AND_SWAP_ABORT:
            // No action is actually performed, but this allows the state machine to
            // advance out of its waiting to draw state without actually drawing.
            break;
          ......
        }
      } while (action != SchedulerStateMachine::ACTION_NONE);

      SetupNextBeginFrameIfNeeded();
      ......

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

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

此时Scheduler类的成员函数ProcessScheduledActions调用SchedulerStateMachine类的成员函数NextAction询问状态机下一步要执行的操作时,SchedulerStateMachine类的成员函数NextAction的返回值将会等于ACTION_DRAW_AND_SWAP_ABORT、ACTION_DRAW_AND_SWAP_FORCED或者ACTION_DRAW_AND_SWAP_IF_POSSIBLE,如下所示:

SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
      ......
      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;
      }
      ......
      return ACTION_NONE;
    }

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

这是因为这时候调用SchedulerStateMachine类的成员函数ShouldDraw得到的返回值等于true,如下所示:

bool SchedulerStateMachine::ShouldDraw() const {
      // If we need to abort draws, we should do so ASAP since the draw could
      // be blocking other important actions (like output surface initialization),
      // from occuring. If we are waiting for the first draw, then perfom the
      // aborted draw to keep things moving. If we are not waiting for the first
      // draw however, we don't want to abort for no reason.
      if (PendingDrawsShouldBeAborted())
        return active_tree_needs_first_draw_;

      // After this line, we only want to send a swap request once per frame.
      if (HasRequestedSwapThisFrame())
        return false;

      // Do not queue too many swaps.
      if (pending_swaps_ >= max_pending_swaps_)
        return false;

      // Except for the cases above, do not draw outside of the BeginImplFrame
      // deadline.
      if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE)
        return false;

      // Only handle forced redraws due to timeouts on the regular deadline.
      if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)
        return true;

      return needs_redraw_;
    }

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

在三种情况下,SchedulerStateMachine类的成员函数ShouldDraw得到的返回值等于true。

第一种情况是调用SchedulerStateMachine类的另外一个成员函数PendingDrawsShouldBeAborted得到的返回值为true,并且SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值也等于true。

当SchedulerStateMachine类的成员函数PendingDrawsShouldBeAborted的返回值为true时,表示要取消当前正在等待执行的渲染操作。这时候SchedulerStateMachine类的成员函数ShouldDraw本应返回false,表示不要执行渲染操作。但是正如前面分析SchedulerStateMachine类的成员函数PendingActivationsShouldBeForced所提到的,如果此时不执行当前正在等待执行的渲染操作,那么就可能会导致整个渲染管线不能向前推进,也就是渲染环节出现死锁。什么情况下会出现这种现象呢?就是当SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值此时也等于true的情况下。

从前面的分析可以知道,当SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值等于true的时候,表示有一个CC Pending Layer Tree刚刚被激活为CC Active Layer Tree,并且激活后的CC Active Layer Tree正在等待执行第一次渲染操作。这时候即使SchedulerStateMachine类的成员函数PendingDrawsShouldBeAborted的返回值为true,也要执行一次渲染操作,避免网页的渲染管线不能向前推进,例如不能重新创建一个有效的绘图表面。

接下来我们继续分析SchedulerStateMachine类的成员函数PendingDrawsShouldBeAborted的实现,以便了解什么情况下需要取消当前正在等待执行的渲染操作,如下所示:

bool SchedulerStateMachine::PendingDrawsShouldBeAborted() const {
      // These are all the cases where we normally cannot or do not want to draw
      // but, if needs_redraw_ is true and we do not draw to make forward progress,
      // we might deadlock with the main thread.
      // This should be a superset of PendingActivationsShouldBeForced() since
      // activation of the pending tree is blocked by drawing of the active tree and
      // the main thread might be blocked on activation of the most recent commit.
      if (PendingActivationsShouldBeForced())
        return true;

      // Additional states where we should abort draws.
      // Note: We don't force activation in these cases because doing so would
      // result in checkerboarding on resize, becoming visible, etc.
      if (!can_draw_)
        return true;
      if (!visible_)
        return true;
      return false;
    }

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

从这里可以看到,SchedulerStateMachine类的成员函数PendingDrawsShouldBeAborted在三种情况下,会返回true给调用者:

1. 调用成员函数PendingActivationsShouldBeForced得到的返回值为true。从前面的分析可以知道,这种情况表明网页的绘图表面已经失效,因此就不能执行渲染操作。

2. 成员变量can_draw_的值等于true。这种情况表示网页当前的CC Active Layer Tree还未准备就绪渲染,因此也不能执行渲染操作。

3. 成员变量visible_的值等于true。这种情况表示网页当前是不可见的,因此不需要执行渲染操作。

回到SchedulerStateMachine类的成员函数ShouldDraw中,它的返回值等于true的第二种情况和第三种情况需要满足三个前提条件:

1. 在当前的VSync周期中,还没有执行过渲染操作。这时候调用SchedulerStateMachine类的成员函数HasRequestedSwapThisFrame得到的返回值为false。这个条件用来限制一个VSync周期至多执行一次渲染操作。关于VSync周期的更多描述,可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文。

2. 还未完成的渲染操作的次数不能多于预设置的最大值。这个预设的最大值保存在SchedulerStateMachine类的成员变量max_pending_swaps_中。还未完成的渲染操作的次数保存在SchedulerStateMachine类的成员变量pending_swaps_中。从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,Render进程渲染网页的操作实际上是通过GPU进程完成的。Render进程将渲染操作发送给GPU进程的时候,它们并不会马上被执行,而是要由GPU进程调度执行。当它们被调度执行的时候,才是网页渲染操作正在被执行的时候。Render进程每一次渲染网页时,最后都会向GPU进程发出一个SwapBuffers操作。当这个SwapBuffers操作被GPU进程调度执行的时候,就真正表示一个网页渲染操作执行完成。这个条件用来保证在GPU进程忙碌时,不会频繁执行渲染操作,以免更进一步增加GPU进程的负担。

3. 当前的VSync周期已经进入Deadline阶段。这时候SchedulerStateMachine类的成员变量begin_impl_frame_state_的值等于BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE。前面Chromium网页渲染调度器(Scheduler)实现分析一文提到,在每一个VSync周期中,只有进入Deadline阶段时,才可以执行渲染操作。这是为了能在当前VSync周期中快速响应用户的输入。

满足了上述三个前提条件之后,SchedulerStateMachine类的成员函数ShouldDraw返回true的第二种情况是调度器内部使用的状态机的ForcedRedrawOnTimeoutState状态等于FORCED_REDRAW_STATE_WAITING_FOR_DRAW,也就是SchedulerStateMachine类的成员变量forced_redraw_state_的值等于FORCED_REDRAW_STATE_WAITING_FOR_DRAW。这种情况表明调度器正在等待网页的CC Active Layer Tree被渲染。现在既然网页的CC Active Layer Tree已经准备就绪渲染,因此就需要马上执行一个渲染操作。关于调度器内部使用的状态机的ForcedRedrawOnTimeoutState状态迁移,可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文。

满足了上述三个前提条件之后,SchedulerStateMachine类的成员函数ShouldDraw返回true的第三种情况是CC模块明确表示要对网页的CC Active Layer Tree执行一次渲染操作。这时候SchedulerStateMachine类的成员变量needs_redraw_的值等于true。从前面的分析可以知道,CC Pending Layer Tree刚刚激活为CC Active Layer Tree时,调度器会将SchedulerStateMachine类的成员变量needs_redraw_的值设置为true,表示要对刚刚激活的CC Active Layer Tree执行一次渲染操作。

在我们这个情景中,SchedulerStateMachine类的成员函数ShouldDraw至少可以满足上述的第三种情况,因此它的返回值为true。回到SchedulerStateMachine类的成员函数NextAction中,这时候它的返回值将会是ACTION_DRAW_AND_SWAP_ABORT、ACTION_DRAW_AND_SWAP_FORCED和ACTION_DRAW_AND_SWAP_IF_POSSIBLE之一:

1. 如果调用前面分析的SchedulerStateMachine类的成员函数PendingDrawsShouldBeAborted得到的返回值等于true,那么SchedulerStateMachine类的成员函数NextAction的返回值就为ACTION_DRAW_AND_SWAP_ABORT,表示要执行一个空的渲染操作。如前所述,这仅仅是为了推进网页的渲染管线,对应的是SchedulerStateMachine类的成员函数ShouldDraw的返回值为true时的第一种情况。

  1. 如果SchedulerStateMachine类的成员变量forced_redraw_state_的值等于FORCED_REDRAW_STATE_WAITING_FOR_DRAW,也就是调度器内部使用的状态机的ForcedRedrawOnTimeoutState状态等于FORCED_REDRAW_STATE_WAITING_FOR_DRAW,这时候SchedulerStateMachine类的成员函数NextAction的返回值就为ACTION_DRAW_AND_SWAP_FORCED,表示无论如何都要执行一次渲染操作,对应的是SchedulerStateMachine类的成员函数ShouldDraw的返回值为true时的第二种情况。

3. 其余情况下,SchedulerStateMachine类的成员函数NextAction的返回值为ACTION_DRAW_AND_SWAP_IF_POSSIBLE,表示由Compositor线程决定是否要执行渲染操作。

Compositor线程在处理调度器发出的ACTION_DRAW_AND_SWAP_IF_POSSIBLE请求时,会检查当前可见的网页分块是否都已经光栅化完成。对于那些已经光栅化完成的可见网页分块,就将它们的光栅化结果渲染在屏幕中。对于那些还没有光栅化完成的可见网页分块,就会使用Checkboard代替渲染在屏幕中。不过,如果此时被Checkboard代替的网页分块所属的Layer正在显示动画,那么Compositor线程将不会渲染当前帧,避免动画出现闪屏现象。

另外,Compositor线程在处理调度器发出的ACTION_DRAW_AND_SWAP_IF_POSSIBLE请求时,如果发现网页要求按照HIGH RES分辨率进行渲染,但是并不是所有分辨率为HIGH RES的可见分块都已经完成光栅化操作,那么Compositor线程将不会渲染当前帧,也是为了避免网页渲染出现闪屏现象。

回到Scheduler类的成员函数ProcessScheduledActions中,当状态机告知它要执行一个ACTION_DRAW_AND_SWAP_ABORT、ACTION_DRAW_AND_SWAP_FORCED或者ACTION_DRAW_AND_SWAP_IF_POSSIBLE操作时,它首先调用SchedulerStateMachine类的成员函数UpdateState更新状态机的状态,如下所示:

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

        case ACTION_DRAW_AND_SWAP_FORCED:
        case ACTION_DRAW_AND_SWAP_IF_POSSIBLE: {
          bool did_request_swap = true;
          UpdateStateOnDraw(did_request_swap);
          return;
        }

        case ACTION_DRAW_AND_SWAP_ABORT: {
          bool did_request_swap = false;
          UpdateStateOnDraw(did_request_swap);
          return;
        }

        ......
      }
    }

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

从这里可以看到,对于上述三种类型的渲染操作,SchedulerStateMachine类的成员函数UpdateState都是调用另外一个成员函数UpdateStateOnDraw更新状态机的状态,区别只在于传递的参数不一样。对于ACTION_DRAW_AND_SWAP_ABORT操作,传递给SchedulerStateMachine类的成员函数UpdateStateOnDraw的参数为false,而对于ACTION_DRAW_AND_SWAP_FORCED和ACTION_DRAW_AND_SWAP_IF_POSSIBLE操作,传递给SchedulerStateMachine类的成员函数UpdateStateOnDraw的参数为true。

SchedulerStateMachine类的成员函数UpdateStateOnDraw的实现如下所示:

void SchedulerStateMachine::UpdateStateOnDraw(bool did_request_swap) {
      if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)
        forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE;

      if (!has_pending_tree_ &&
          commit_state_ == COMMIT_STATE_WAITING_FOR_FIRST_DRAW) {
        commit_state_ = COMMIT_STATE_IDLE;
      }

      needs_redraw_ = false;
      active_tree_needs_first_draw_ = false;

      if (did_request_swap)
        last_frame_number_swap_requested_ = current_frame_number_;
    }

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

SchedulerStateMachine类的成员函数UpdateStateOnDraw首先检查状态机的ForcedRedrawOnTimeoutState状态是否等于FORCED_REDRAW_STATE_WAITING_FOR_DRAW,即CC Active Layer Tree是否正在等待渲染。如果等于的话,那么就会将状态机的ForcedRedrawOnTimeoutState状态迁移为FORCED_REDRAW_STATE_IDLE,因为接下来马上就会对CC Active Layer Tree执行一个渲染操作。

SchedulerStateMachine类的成员函数UpdateStateOnDraw接下来又检查状态机的CommitState状态是否等于COMMIT_STATE_WAITING_FOR_FIRST_DRAW,即调度器正在等待刚刚激活的CC Active Layer Tree第一次被渲染。如果等于的话,那么就会将状态机的CommitState状态迁移为COMMIT_STATE_IDLE,同样是因为接下来马上就会对CC Active Layer Tree执行一个渲染操作。

关于状态机的ForcedRedrawOnTimeoutState状态和CommitState状态的迁移,可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文。

再接下来,SchedulerStateMachine类的成员函数UpdateStateOnDraw又将成员变量needs_redraw_和active_tree_needs_first_draw_的值设置为false,表示之前请求调度器执行的渲染操作即将得到执行。

最后,在参数did_request_swap的值等于true的情况下,SchedulerStateMachine类的成员函数UpdateStateOnDraw将成员变量current_frame_number_的值保存在另外一个成员变量last_frame_number_swap_requested_中,用来记录上一次真正执行了渲染操作的帧号,也就是上一次执行了ACTION_DRAW_AND_SWAP_FORCED或者ACTION_DRAW_AND_SWAP_IF_POSSIBLE操作的帧号。

这一步执行完成之后,调度器内部使用的状态机的状态就更新完毕。回到前面分析的Scheduler类的成员函数ProcessScheduledActions中,它接下来就会根据状态机返回的Action执行相应的操作:

1. 如果状态返回的Action为ACTION_DRAW_AND_SWAP_IF_POSSIBLE,那么就会调用另外一个成员函数DrawAndSwapIfPossible请求Compositor线程考虑执行一次渲染操作。

  1. 如果状态返回的Action为ACTION_DRAW_AND_SWAP_FORCED,那么就会调用成员变量client_指向的一个ThreadProxy对象的成员函数ScheduledActionDrawAndSwapForced强制要求Compositor线程执行一次渲染操作。

  2. 如果状态返回的Action为ACTION_DRAW_AND_SWAP_ABORT,那么就什么都不用做。这个Action只是为了更新状态机的状态,避免网页的渲染管线不能正常推进。

接下来我们就继续分析Scheduler类的成员函数DrawAndSwapIfPossible和ThreadProxy类的成员函数ScheduledActionDrawAndSwapForced的实现,以便了解ACTION_DRAW_AND_SWAP_IF_POSSIBLE和ACTION_DRAW_AND_SWAP_FORCED这两种渲染操作的执行过程。

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

void Scheduler::DrawAndSwapIfPossible() {
      DrawResult result = client_->ScheduledActionDrawAndSwapIfPossible();
      state_machine_.DidDrawIfPossibleCompleted(result);
    }

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

Scheduler类的成员函数DrawAndSwapIfPossible首先调用成员变量client_指向的一个ThreadProxy对象的成员函数ScheduledActionDrawAndSwapIfPossible请求Compositor考虑执行一个渲染操作,接下来再调用SchedulerStateMachine类的成员函数DidDrawIfPossibleCompleted根据执行的结果更新状态机的状态,如下所示:

void SchedulerStateMachine::DidDrawIfPossibleCompleted(DrawResult result) {
      switch (result) {
        case INVALID_RESULT:
          NOTREACHED() << "Uninitialized DrawResult.";
          break;
        case DRAW_ABORTED_CANT_DRAW:
        case DRAW_ABORTED_CONTEXT_LOST:
          NOTREACHED() << "Invalid return value from DrawAndSwapIfPossible:"
                       << result;
          break;
        case DRAW_SUCCESS:
          ......
          forced_redraw_state_ = FORCED_REDRAW_STATE_IDLE;
          break;
        case DRAW_ABORTED_CHECKERBOARD_ANIMATIONS:
          needs_redraw_ = true;

          // If we're already in the middle of a redraw, we don't need to
          // restart it.
          if (forced_redraw_state_ != FORCED_REDRAW_STATE_IDLE)
            return;

          needs_commit_ = true;
          ......
          break;
        case DRAW_ABORTED_MISSING_HIGH_RES_CONTENT:
          // It's not clear whether this missing content is because of missing
          // pictures (which requires a commit) or because of memory pressure
          // removing textures (which might not).  To be safe, request a commit
          // anyway.
          needs_commit_ = true;
          break;
      }
    }

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

ThreadProxy类的成员函数ScheduledActionDrawAndSwapIfPossible可能会返回以下6种不同的渲染结果:

  1. INVALID_RESULT:表示渲染过程中碰到了异常。

2. DRAW_ABORTED_CANT_DRAW:表示网页当前的CC Active Layer Tree未准备就绪绘制。

  1. DRAW_ABORTED_CONTEXT_LOST:表示网页的绘图表面已经失效。

  2. DRAW_SUCCESS:表示成功执行了一个渲染操作。

  3. DRAW_ABORTED_CHECKERBOARD_ANIMATIONS:表示某些可见的网页分块还没有光栅化,并且这些网页分块所属的Layer正在执行动画。为了避免动画在显示过程出现闪屏现象,Compositor线程没有执行渲染操作。

  4. DRAW_ABORTED_MISSING_HIGH_RES_CONTENT:表示网页被要求按照HIGH RES分辨率进行渲染,但是不是所有可见的分辨为HIGH RES的网页分块都已经光栅化完成。在这种情况下,Compositor线程也没有执行渲染操作,同样是为了避免出现闪屏现象。

前面3种渲染结果INVALID_RESULT、DRAW_ABORTED_CANT_DRAW和DRAW_ABORTED_CONTEXT_LOST,都是属于异常情况,不需要更新状态机的状态。

第4种渲染结果DRAW_SUCCESS,需要将状态机的ForcedRedrawOnTimeoutState状态设置为FORCED_REDRAW_STATE_IDLE,表示已经成功执行了一个渲染操作。

第5种渲染结果DRAW_ABORTED_CHECKERBOARD_ANIMATIONS,需要将SchedulerStateMachine类的成员变量needs_redraw_重新设置为true,表示等到那些缺失的网页分块光栅化完成后,需要再次执行一个渲染操作。如果这时候状态机的ForcedRedrawOnTimeoutState状态不等于FORCED_REDRAW_STATE_IDLE,那么就表示调度器已经在调度执行下一次渲染的过程中,因此就不需要显式地启动下一次渲染操作。否则的话,就需要显式地启动下一次渲染操作。这是通过将SchedulerStateMachine类的成员变量needs_commit_的值设置为true来启动下一次渲染的,也就是从请求CC Layer Tree重新绘制自己这个环节开始启动下一次渲染操作。因为缺失的网页分块的光栅化内容需要从新的CC Layer Tree获得。

第6种渲染结果DRAW_ABORTED_MISSING_HIGH_RES_CONTENT,需要从重新绘制CC Layer Tree这个环节开始,启动下一次渲染操作。产生这种渲染结果有两个可能的原因。第一个可能的原因是那些缺失的网页分块,也就是那些没有光栅化的网页分块,是由于当前的CC Layer Tree没有记录它们的绘制命令造成的。第二个可能的原因是当前的CC Layer Tree虽然记录了那些缺失的网页分块的绘制命令,但是由于当前GPU内存不足,导致它们不能正常进行光栅化。状态机不知道是哪一种原因造成的,因此就按照最保守的原因处理,就是从重新绘制CC Layer Tree这个环节开始启动下一次渲染操作。

回到Scheduler类的成员函数DrawAndSwapIfPossible,接下来我们继续分析它调用成员变量client_指向的一个ThreadProxy对象的成员函数ScheduledActionDrawAndSwapIfPossible请求Compositor考虑执行一个渲染操作的过程,如下所示:

DrawResult ThreadProxy::ScheduledActionDrawAndSwapIfPossible() {
      ......

      bool forced_draw = false;
      return DrawSwapInternal(forced_draw);
    }

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

ThreadProxy类的成员函数ScheduledActionDrawAndSwapIfPossible调用另外一个成员函数DrawSwapInternal请求Compositor线程考虑对当前的CC Active Layer Tree执行一次渲染操作。注意,这时候传递给ThreadProxy类的成员函数DrawSwapInternal的参数值为false,表示不是强制要求Compositor线程执行这次渲染操作。

在前面Chromium硬件加速渲染的UI合成过程分析一文中,我们已经分析过ThreadProxy类的成员函数DrawSwapInternal的执行过程,因此这里不再复述。

回到Scheduler类的成员函数ProcessScheduledActions中,接下来我们继续分析它调用client_指向的一个ThreadProxy对象的成员函数ScheduledActionDrawAndSwapForced强制要求Compositor线程执行一次渲染操作的过程,如下所示:

DrawResult ThreadProxy::ScheduledActionDrawAndSwapForced() {
      ......

      bool forced_draw = true;
      return DrawSwapInternal(forced_draw);
    }

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

ThreadProxy类的成员函数ScheduledActionDrawAndSwapForced同样是通过调用成员函数DrawSwapInternal强制要求Compositor线程执行一次渲染操作的。注意,这时候传递给ThreadProxy类的成员函数DrawSwapInternal的参数值为true,表示强制要求Compositor线程执行这次渲染操作。

这样,结合Chromium硬件加速渲染的UI合成过程分析一文,我们就分析完成了网页的CC Pending Layer Tree激活为CC Active Layer Tree,并且对获得的CC Active Layer Tree进行渲染的过程。这里我们小结一下这个过程:

1. 将网页的CC Pending Layer Tree激活为CC Active Layer Tree的过程类似于将CC Layer Tree同步为CC Pending Layer Tree的过程,涉及到的操作都是在两个Tree之间同步信息,具体可以参考前面Chromium网页Layer Tree同步为Pending Layer Tree的过程分析一文。

2. 渲染CC Active Layer Tree的过程,实际上就是将之前光栅化网页分块得到纹理,再渲染到浏览器窗口去,使它们可以在屏幕上显示。这个过程可以参考前面Chromium硬件加速渲染的UI合成过程分析一文。

至此,我们就学习完成Render进程渲染网页的整个流程和机制,这是Chromium最复杂也是最核心的内容。掌握了它们,在阅读Chromium的源码的时候,就会得心应手了。重新学习这个系列的文章,可以参考前面Chromium网页渲染机制简要介绍和学习计划一文。更多关于Chromium的源码分析,可以关注老罗的新浪微博:http://weibo.com/shengyangluo

Copyright© 2013-2019

京ICP备2023019179号-2