Chromium网页渲染调度器(Scheduler)实现分析

Android社区 收藏文章

在采用线程化渲染方式渲染网页时,Chromium依赖一个调度器协调Main线程和Compositor线程的执行,同时也通过这个调度器决定它们什么时候该执行什么操作。调度器将Main线程和Compositor线程的当前状态记录在一个状态机中,然后通过这个状态机决定下一个要执行的操作。这个操作在满足当前设置条件下是最优的,因此可以使网页渲染更快更流畅。本文接下来就分析Chromium网页调度器的实现。

调度器实现在Chromium的CC模块中,并且运行在Compositor线程中。Compositor线程的职责是将网页的内容渲染出来。从这个角度看,调度器只不过是在调度Compositor线程的执行。不过由于要渲染的网页内容是由Main线程提供给Compositor线程的,因此调度器也会在必要的时候调度Main线程执行,使得它可以提供最新的网页内容给Compositor线程渲染。

网页是一帧一帧地渲染出来的。从前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划这个系列的文章我们学习到,Android应用程序UI的每一帧的最佳渲染时机是下一个屏幕VSync信号到来时。Chromium也不例外,它在渲染网页的时候,也是利用了屏幕的VSync信号。这一点在调度器的时间轴中可以得到体现,如图1所示:

图1 调度器时间轴

从图1可以看到,调度器并没有严格在VSync到来时就去渲染网页的下一帧,而是为网页的下一帧渲染时机设置了一个Deadline。在Deadline到来前,调度器可以调度执行其它的渲染操作。

在继续分析上述的Deadline机制之前,我们要先搞清楚网页的一帧渲染涉及到哪些操作。这些操作如图2所示:

图2 调试器调度执行的操作

图2的完整分析可以参考前面Chromium网页渲染机制简要介绍和学习计划一文。我们前面说的Deadline,是针对第6个操作ACTION_DRAW_AND_SWAP_FORCED而言的。也就是说,当VSync信号到来时,ACTION_DRAW_AND_SWAP_FORCED操作最迟必须在设置的Deadline到来时执行。

这个Deadline是怎么计算出来的呢?我们先来看网页的渲染过程。首先是Render进程进行渲染,然后交给Browser进程进行合成。因此,网页的渲染过程可以看作由两部分时间组成:estimated_draw_duration + estimated_browser_composite_time。其中,estimated_draw_duration表示Render进程的渲染时间,estimated_browser_composite_time表示Browser进程的合成时间。

假设下一个VSync到来的时间为frame_time,VSync信号时间周期为interval,那么就可以计算出Deadline = frame_time + (interval - estimated_draw_duration - estimated_browser_composite_time)。剩下来的时间区间[frame_time, deadline)可以用做其它事情,例如执行图2所示的第2个操作ACTION_SEND_BEGIN_MAIN_FRAME,也就是通知Main线程对CC Layer Tree进行绘制。

时间区间[frame_time, deadline)称为BEGIN_IMPL_FRAME时间。在BEGIN_IMPL_FRAME时间内,存在四个BeginImplFrameState状态,如下所示:

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

      // Note: BeginImplFrameState will always cycle through all the states in
      // order. Whether or not it actually waits or draws, it will at least try to
      // wait in BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME and try to draw in
      // BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE
      enum BeginImplFrameState {
        BEGIN_IMPL_FRAME_STATE_IDLE,
        BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING,
        BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME,
        BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE,
      };

      ......

     protected:
      ......

      BeginImplFrameState begin_impl_frame_state_;

      ......
    };

这个状态定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.h中。

调度器通过类SchedulerStateMachine描述内部的状态机。状态机的BeginImplFrameState状态记录在SchedulerStateMachine类的成员变量begin_impl_frame_state_中。

四个BeginImplFrameState状态分别为:

1. BEGIN_IMPL_FRAME_STATE_IDLE

2. BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING

3. BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME

4. BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE

它们的变迁关系如图3所示:

图3 BeginImplFrameState状态变迁

下一个VSync信号到来之前,状态机处于BEGIN_IMPL_FRAME_STATE_IDLE状态。

下一个VSync信号到来之时,调度器调用SchedulerStateMachine类的成员函数OnBeginImplFrame将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING。这时候调度器通过调用Scheduler类的成员函数ProcessScheduledActions调度计算网页中的动画,或者执行图2所示的第2个操作ACTION_SEND_BEGIN_MAIN_FRAME,也就是通知Main线程对网页内容进行绘制。

从Scheduler类的成员函数ProcessScheduledActions返回后,BeginImplFrameState状态就从BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING变为BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME。这时候调度器等待Deadline的到来。

Deadline到来之时,调度器调用SchedulerStateMachine类的成员函数OnBeginImplFrameDeadline将BeginImplFrameState状态从BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME设置为BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE。这时候调度器就会通过调用Scheduler类的成员函数ProcessScheduledActions调度执行网页的渲染操作,也就是图2所示的第6个操作ACTION_DRAW_AND_SWAP_FORCED。

从Scheduler类的成员函数ProcessScheduledActions返回后,BeginImplFrameState状态就从BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE重新变为BEGIN_IMPL_FRAME_STATE_IDLE。这时候调度器等待再下一个VSync信号的到来。

状态机除了有BeginImplFrameState状态,还有其它三个状态,分别是OutputSurfaceState、CommitState和ForcedRedrawOnTimeoutState。

OutputSurfaceState描述的是网页绘图表面的状态,如下所示:

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

      enum OutputSurfaceState {
        OUTPUT_SURFACE_ACTIVE,
        OUTPUT_SURFACE_LOST,
        OUTPUT_SURFACE_CREATING,
        OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT,
        OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION,
      };

      ......

     protected:
      ......

      OutputSurfaceState output_surface_state_;

      ......
    };

这个状态定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.h中。

网页绘图表面的状态记录在SchedulerStateMachine类的成员变量output_surface_state_中。网页绘图表面的状态有五个状态,分别是:

1. OUTPUT_SURFACE_LOST

2. OUTPUT_SURFACE_CREATING

3. OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT

4. OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION

5. OUTPUT_SURFACE_ACTIVE

它们的变迁关系如图4所示:

图4 OutputSurfaceState状态变迁

网页绘图表面最初处于OUTPUT_SURFACE_LOST状态,等到CC Layer Tree创建之后,调度器会调度图2所示的第2个操作ACTION_BEGIN_OUTPUT_SURFACE_CREATION,也就是请求Compositor线程为网页创建绘图表面,这时候网页绘图表面的状态变为OUTPUT_SURFACE_CREATING。

Compositor线程为网页创建好了绘图表面之后,就会调用SchedulerStateMachine类的成员函数DidCreateAndInitializeOutputSurface将绘图表面的状态设置为OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT。这将会触发调度器尽快调度执行图2所示的第2个操作ACTION_SEND_BEGIN_MAIN_FRAME和第3个操作ACTION_COMMIT,也就是请求Main线程对CC Layer Tree进行绘制,并且将其同步到CC Pending Layer Tree中去。这时候绘图表面的状态变为OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION,表示要尽快将CC Pending Layer Tree激活为CC Active Layer Tree。

CC Pending Layer Tree被激活之后,也就是图2所示的第5个操作ACTION_ACTIVATE_PENDING_TREE执行之后,绘图表面以后就会一直处于OUTPUT_SURFACE_ACTIVE状态。不过,在绘图表面处于OUTPUT_SURFACE_ACTIVE状态期间,如果Render进程与GPU进程之间的GPU通道断开了连接,或者GPU进程在解析Render进程发送来的GPU命令时发生了错误,那么SchedulerStateMachine类的成员函数DidLoseOutputSurface会被调用。这时候绘图表面的状态就会被设置为OUTPUT_SURFACE_LOST状态。这将会触发调度器调度执行ACTION_BEGIN_OUTPUT_SURFACE_CREATION操作,以便为网页重新创建绘图表面。

CommitState描述的是CC Layer Tree的提交状态,包括同步到CC Pending Layer Tree,以及CC Pending Layer Tree激活为CC Active Layer Tree的过程,如下所示:

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

      enum CommitState {
        COMMIT_STATE_IDLE,
        COMMIT_STATE_BEGIN_MAIN_FRAME_SENT,
        COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED,
        COMMIT_STATE_READY_TO_COMMIT,
        COMMIT_STATE_WAITING_FOR_ACTIVATION,
        COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
      };

      ......

     private:
      ......

      CommitState commit_state_;

      ......
    };

这个状态定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.h中。

CC Layer Tree的提交状态记录在SchedulerStateMachine类的成员变量commit_state_中。CC Layer Tree有六个提交状态,分别是:

1. COMMIT_STATE_IDLE

2. COMMIT_STATE_BEGIN_MAIN_FRAME_SENT

3. COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED

4. COMMIT_STATE_READY_TO_COMMIT

5. COMMIT_STATE_WAITING_FOR_ACTIVATION

6. COMMIT_STATE_WAITING_FOR_FIRST_DRAW

它们的变迁关系如图5所示:

图5 CommitState状态变迁

CC Layer Tree的提交状态最开始时被设置为COMMIT_STATE_IDLE。当调度器调度执行图2所示的第2个操作ACTION_BEGIN_MAIN_FRAME时,CC Layer Tree的提交状态被设置为COMMIT_STATE_BEGIN_MAIN_FRAME_SENT,表示调度器已经请求Main线程对CC Layer Tree进行绘制。

在Main线程执行ACTION_BEGIN_MAIN_FRAME操作期间,CC Layer Tree有可能变为不可见,这时候调度器就会调用SchedulerStateMachine类的成员函数BeginMainFrameAborted重新设置为COMMIT_STATE_IDLE。

Main线程执行完成ACTION_BEGIN_MAIN_FRAME操作之后,调度器就会调用SchedulerStateMachine类的成员函数NotifyReadyToCommit将CC Layer Tree的提交状态设置为COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED。这时候Main线程会通知Compositor线程对网页中的图片资源以纹理方式上传到GPU去,以便后面进行渲染显示。

图片资源上传完毕,调度器就会调用SchedulerStateMachine类的成员函数NotifyReadyToCommit将CC Layer Tree的提交状态设置为COMMIT_STATE_READY_TO_COMMIT,表示CC Layer Tree需要同步到CC Pending Layer Tree中去。这将会触发调度器调度执行图2所示的第三个操作ACTION_COMMIT,也就是将CC Layer Tree同步到CC Pending Layer Tree中去。

ACTION_COMMIT操作执行完成之后,CC Layer Tree的提交状态会从COMMIT_STATE_READY_TO_COMMIT变为以下三个状态之一:

1. 在满足以下两个条件之一时,变为COMMIT_STATE_IDLE:

A. main_frame_before_activation_enabled被设置为true。这表示在上一个CC Pending Layer Tree被激活为CC Active Layer Tree之前,允许Main线程绘制网页的下一帧。

B. main_frame_before_draw_enabled被设置为true,但是impl_side_painting被设置为false。main_frame_before_draw_enabled设置为true,表示在上一个CC Active Layer Tree被渲染之前,允许Main线程绘制网页的下一帧。impl_side_painting设置为true表示Main线程在绘制网页时,实际上只是记录了网页的绘制命令。只有在impl_side_painting设置为true的时候,才会有CC Pending Layer Tree被激活为CC Active Layer Tree的环节。因此,在impl_side_painting等于false的情况下,main_frame_before_draw_enabled被设置为true等同于main_frame_before_activation_enabled被设置为true的情况。

  1. FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION。需要满足的条件是main_frame_before_draw_enabled和impl_side_painting均被设置为true,并且main_frame_before_activation_enabled被设置为false。这表示在上一个CC Pending Layer Tree被激活为CC Active Layer Tree之后,才允许Main线程绘制网页的下一帧。

  2. COMMIT_STATE_WAITING_FOR_FIRST_DRAW。需要满足的条件是main_frame_before_activation_enabled和main_frame_before_draw_enabled均被设置为true。这表示在上一个CC Active Layer Tree第一次渲染之后,才允许Main线程绘制网页的下一帧。这实际上是给予CC Active Layer Tree更高的优先级,使得它一激活就马上进行渲染。

如果CC Layer Tree的提交状态处于FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION,那么当CC Pending Layer Tree被激活为CC Active Layer Tree之后,也就是图2所示的第5个操作ACTION_ACTIVATE_PENDING_TREE执行之后,CC Layer Tree的提交状态就变为COMMIT_STATE_IDLE。

如果CC Layer Tree的提交状态处于COMMIT_STATE_WAITING_FOR_FIRST_DRAW,那么当CC Active Layer Tree被渲染之后,也就是图2所示的第6个操作ACTION_DRAW_AND_SWAP_FORCED,或者另外一个操作ACTION_DRAW_AND_SWAP_IF_POSSIBLE执行之后,CC Layer Tree的提交状态就变为COMMIT_STATE_IDLE。

注意,只有CC Layer Tree的提交状态处于COMMIT_STATE_IDLE时,Main线程才可以绘制网页的下一帧。

ForcedRedrawOnTimeoutState描述的是网页的渲染状态,如下所示:

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

      enum ForcedRedrawOnTimeoutState {
        FORCED_REDRAW_STATE_IDLE,
        FORCED_REDRAW_STATE_WAITING_FOR_COMMIT,
        FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION,
        FORCED_REDRAW_STATE_WAITING_FOR_DRAW,
      };

      ......

     private:
      ......

      ForcedRedrawOnTimeoutState forced_redraw_state_;

      ......
    };

这个状态定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.h中。

网页的渲染状态记录在SchedulerStateMachine类的成员变量forced_redraw_state_中。网页的渲染状态有四个,分别是:

1. FORCED_REDRAW_STATE_IDLE,

2. FORCED_REDRAW_STATE_WAITING_FOR_COMMIT

3. FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION

4. FORCED_REDRAW_STATE_WAITING_FOR_DRAW

它们的变迁关系如图6所示:

图6 ForcedRedrawOnTimeoutState状态变迁

网页渲染状态最开始处于FORCED_REDRAW_STATE_IDLE状态。在渲染动画的过程中,如果某些分块(Tile)还没有光栅化好,那么CC模块就会用棋盘(Checkboard)来代替这些缺失的分块。这时候的网页渲染结果被视为DRAW_ABORTED_CHECKERBOARD_ANIMATIONS。如果网页连续渲染结果都是DRAW_ABORTED_CHECKERBOARD_ANIMATIONS的次数超出预设值,那么网页渲染状态就会被设置为FORCED_REDRAW_STATE_WAITING_FOR_COMMIT,表示要尽快执行一次图2所示的第3个操作ACTION_COMMIT,以便补充缺失的分块。

ACTION_COMMIT操作执行完成之后,如果Main线程在绘制网页时,仅仅记录了CC Layer Tree的绘制命令,也就是前面提到的impl_side_painting等于true,那么就意味着存在一个CC Pending Layer Tree,这时候网页渲染状态会被设置为FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION,表示等待CC Pending Layer Tree被激活为CC Active Layer Tree。

另一方面,ACTION_COMMIT操作执行完成之后,如果Main线程在绘制网页时,直接进行光栅化,也就是前面提到的impl_side_painting等于false,那么就意味着不会存在一个CC Pending Layer Tree,这时候网页渲染状态会被设置为FORCED_REDRAW_STATE_WAITING_FOR_DRAW,表示等待CC Active Layer Tree被渲染。

如果网页渲染状态被设置为FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION,那么当图2所示的第5个操作ACTION_ACTIVATE_PENDING_TREE执行完成之后。也就是CC Pending Layer Tree被激活为CC Active Layer Tree之后,网页渲染状态会被设置为FORCED_REDRAW_STATE_WAITING_FOR_DRAW,表示等待CC Active Layer Tree被渲染。

当CC Active Layer Tree被渲染之后,网页渲染状态就会从FORCED_REDRAW_STATE_WAITING_FOR_DRAW变为FORCED_REDRAW_STATE_IDLE状态。

理解了网页的BeginImplFrameState、OutputSurfaceState、CommitState和ForcedRedrawOnTimeoutState状态之后,接下来我们就可以分析调度器的实现了,也就是调度器的执行过程。

调度器通过Scheduler类实现,调度器的执行过程就表现为Scheduler类的成员函数ProcessScheduledActions不断地被调用,如下所示:

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

      SchedulerStateMachine::Action action;
      do {
        action = state_machine_.NextAction();
        ......
        state_machine_.UpdateState(action);
        base::AutoReset<SchedulerStateMachine::Action>
            mark_inside_action(&inside_action_, action);
        switch (action) {
          case SchedulerStateMachine::ACTION_NONE:
            break;
          case SchedulerStateMachine::ACTION_ANIMATE:
            client_->ScheduledActionAnimate();
            break;
          case SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME:
            client_->ScheduledActionSendBeginMainFrame();
            break;
          case SchedulerStateMachine::ACTION_COMMIT:
            client_->ScheduledActionCommit();
            break;
          case SchedulerStateMachine::ACTION_UPDATE_VISIBLE_TILES:
            client_->ScheduledActionUpdateVisibleTiles();
            break;
          case SchedulerStateMachine::ACTION_ACTIVATE_PENDING_TREE:
            client_->ScheduledActionActivatePendingTree();
            break;
          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;
          case SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_CREATION:
            client_->ScheduledActionBeginOutputSurfaceCreation();
            break;
          case SchedulerStateMachine::ACTION_MANAGE_TILES:
            client_->ScheduledActionManageTiles();
            break;
        }
      } while (action != SchedulerStateMachine::ACTION_NONE);

      SetupNextBeginFrameIfNeeded();
      ......

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

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

Scheduler类的成员函数ProcessScheduledActions在一个while循环中不断地调用成员变量state_machine_指向的SchedulerStateMachine对象的成员函数NextAction询问状态机下一个要执行的操作,直到状态机告知当前没有操作要执行为止。这些操作大概就对应于图2所示的操作。

每一个操作都是通过调用成员变量client_指向的ThreadProxy对象对应的成员函数执行的。例如,ACTION_SEND_BEGIN_MAIN_FRAME操作是通过调用成员变量client_指向的ThreadProxy对象的成员函数ScheduledActionSendBeginMainFrame执行的。ThreadProxy类的这些函数我们在后面的文章中再详细分析。每一个操作在执行之前,Scheduler类的成员函数ProcessScheduledActions会先调用state_machine_指向的SchedulerStateMachine对象的成员函数UpdateState更新状态机的状态。

跳出while循环之后,Scheduler类的成员函数ProcessScheduledActions调用另外一个成员函数SetupNextBeginFrameIfNeeded根据状态机的当前状态决定是否要发起下一个BEGIN_IMPL_FRAME操作。如果需要的话,就会在下一个VSync信号到来时,通过调用Scheduler类的成员函数ProcessScheduledActions渲染网页的下一帧。

Scheduler类的成员函数ProcessScheduledActions最后还会调用成员变量state_machine_指向的SchedulerStateMachine对象的成员函数ShouldTriggerBeginImplFrameDeadlineEarly检查是否提前执行渲染网页的操作。如果需要提前的话,那么就不会等待上一个VSync信号到来时设置的Deadline到期,而是马上调用成员函数ScheduleBeginImplFrameDeadline假设该Deadline已经到期,于是就可以马上渲染网页。

为了更好地理解调度器的执行过程,接下来我们继续前面提到的SchedulerStateMachine类的成员函数NextAction、UpdateState和ShouldTriggerBeginImplFrameDeadlineEarly以及Scheduler类的成员函数SetupNextBeginFrameIfNeeded和ScheduleBeginImplFrameDeadline的实现。

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通过调用一系列的成员函数ShouldXXX决定当前需要执行的操作。例如,如果调用成员函数ShouldCommit得到的返回值为true,那么就SchedulerStateMachine类的成员函数NextAction就会返回一个ACTION_COMMIT值表示要执行一个ACTION_COMMIT操作。这些ShouldXXX成员函数我们在后面的文章中再详细分析。

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

void SchedulerStateMachine::UpdateState(Action action) {
      switch (action) {
        case ACTION_NONE:
          return;

        case ACTION_UPDATE_VISIBLE_TILES:
          last_frame_number_update_visible_tiles_was_called_ =
              current_frame_number_;
          return;

        case ACTION_ACTIVATE_PENDING_TREE:
          UpdateStateOnActivation();
          return;

        case ACTION_ANIMATE:
          last_frame_number_animate_performed_ = current_frame_number_;
          needs_animate_ = false;
          // TODO(skyostil): Instead of assuming this, require the client to tell
          // us.
          SetNeedsRedraw();
          return;

        case ACTION_SEND_BEGIN_MAIN_FRAME:
          DCHECK(!has_pending_tree_ ||
                 settings_.main_frame_before_activation_enabled);
          DCHECK(!active_tree_needs_first_draw_ ||
                 settings_.main_frame_before_draw_enabled);
          DCHECK(visible_);
          commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_SENT;
          needs_commit_ = false;
          last_frame_number_begin_main_frame_sent_ =
              current_frame_number_;
          return;

        case ACTION_COMMIT: {
          bool commit_was_aborted = false;
          UpdateStateOnCommit(commit_was_aborted);
          return;
        }

        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;
        }

        case ACTION_BEGIN_OUTPUT_SURFACE_CREATION:
          DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_LOST);
          output_surface_state_ = OUTPUT_SURFACE_CREATING;

          // The following DCHECKs make sure we are in the proper quiescent state.
          // The pipeline should be flushed entirely before we start output
          // surface creation to avoid complicated corner cases.
          DCHECK_EQ(commit_state_, COMMIT_STATE_IDLE);
          DCHECK(!has_pending_tree_);
          DCHECK(!active_tree_needs_first_draw_);
          return;

        case ACTION_MANAGE_TILES:
          UpdateStateOnManageTiles();
          return;
      }
    }

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

参数action表示调度器即将要调度执行的操作,SchedulerStateMachine类的成员函数UpdateState根据这个操作相应地更新状态机的状态。例如,如果调度器即将要调度执行的操作为ACTION_COMMIT,那么SchedulerStateMachine类的成员函数UpdateState就会调用另外一个成员函数UpdateStateOnCommit更新状态机的CommitState状态。这些状态的更新过程我们同样是在后面的文章再详细分析。

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

bool SchedulerStateMachine::ShouldTriggerBeginImplFrameDeadlineEarly() const {
      // TODO(brianderson): This should take into account multiple commit sources.

      if (begin_impl_frame_state_ != BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME)
        return false;

      // If we've lost the output surface, end the current BeginImplFrame ASAP
      // so we can start creating the next output surface.
      if (output_surface_state_ == OUTPUT_SURFACE_LOST)
        return true;

      // SwapAck throttle the deadline since we wont draw and swap anyway.
      if (pending_swaps_ >= max_pending_swaps_)
        return false;

      if (active_tree_needs_first_draw_)
        return true;

      if (!needs_redraw_)
        return false;

      // This is used to prioritize impl-thread draws when the main thread isn't
      // producing anything, e.g., after an aborted commit. We also check that we
      // don't have a pending tree -- otherwise we should give it a chance to
      // activate.
      // TODO(skyostil): Revisit this when we have more accurate deadline estimates.
      if (commit_state_ == COMMIT_STATE_IDLE && !has_pending_tree_)
        return true;

      // Prioritize impl-thread draws in smoothness mode.
      if (smoothness_takes_priority_)
        return true;

      return false;
    }

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

如前所述,当SchedulerStateMachine类的成员函数ShouldTriggerBeginImplFrameDeadlineEarly返回true的时候,就表示要马上渲染网页的下一帧,而不要等待上一个VSync到来时所设置的Deadline。这隐含着SchedulerStateMachine类的成员函数ShouldTriggerBeginImplFrameDeadlineEarly返回true的一个必要条件,那就是调度器当前必须是在等待Deadline的到来。

从前面的分析可以知道,当SchedulerStateMachine类的成员变量begin_impl_frame_state_的值等于BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME的时候,就表示调度器正在等待Deadline的到来。因此,当SchedulerStateMachine类的成员变量begin_impl_frame_state_的值不等于BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME的时候,可以马上断定SchedulerStateMachine类的成员函数ShouldTriggerBeginImplFrameDeadlineEarly返回true的必要条件不满足。

接下来有四种情况会导致SchedulerStateMachine类的成员函数ShouldTriggerBeginImplFrameDeadlineEarly返回true。

第一种情况是网页的绘图表面表还没有创建,或者之前已经创建过,但是现在丢失了。在这种情况下,SchedulerStateMachine类的成员变量output_surface_state_的值等于OUTPUT_SURFACE_LOST。

第二种情况是上一个CC Pending Layer Tree刚刚激活为CC Active Layer Tree。在这种情况下,SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值等于true。CC Pending Layer Tree被激活为CC Active Layer Tree时,说明网页很可能发生了较大的变化,也就是它有可能需要花费更多的渲染时间,因此需要提前进行渲染,而不是等到Deadline到来时再渲染。不过,这种情况还需要满足另外一个条件,就是Render进程当前请求GPU进程执行的SwapBuffers操作未完成的次数pending_swaps_不能大于等于预先设置的阀值max_pendingswaps

第三种情况是虽然CC Layer Tree没有发生新的变化需要同步给CC Pending Layer Tree,但是网页当前被要求重新进行渲染,也就是对当前的CC Active Layer Tree进行渲染。这时候SchedulerStateMachine类的成员变量commit_state_的值等于COMMIT_STATE_IDLE,并且成员变量needs_redraw_的值等于true。这种情况出现在CC Layer Tree上一次将变化同步给CC Pending Layer Tree时还没有完成就被取消。不过,如果这时候存在一个CC Pending Layer Tree,那么SchedulerStateMachine类的成员函数ShouldTriggerBeginImplFrameDeadlineEarly就不会返回true,目的是让已经存在的CC Pending Layer Tree优先渲染出来,而不是继续渲染当前的CC Active Layer Tree。

第四种情况是对第三种情况的补充,也就是网页当前的状态为:

1. 被要求重新渲染,也就是成员变量needs_redraw_的值等于true。

2. CC Layer Tree有发生了新的变化,并且正在同步到CC Pending Layer Tree,或者上一次同步CC Layer Tree得到的CC Pending Layer Tree还未完成光栅化。

这时候本应该让已经存在的CC Pending Layer Tree优先渲染,或者让CC Layer Tree同步后得到的新CC Pending Layer Tree优先渲染。但是如果SchedulerStateMachine类的成员变量smoothness_takes_priority_的值等于true,那么优先渲染的是当前的CC Active Layer Tree。也就是说,当网页被要求重新渲染时,不要等待新的内容准备就绪,要马上对现有的就绪内容进行渲染。因此,SchedulerStateMachine类的成员变量smoothness_takes_priority_的值等于true时,表示的意思是网页的现有内容显示优先于新内容显示,即使现有内容是较旧的。这种体验带来的好处是让用户觉得网页显示很流畅,不是每次都一卡一顿地显示最新的内容。

接下来我们重点分析调度器准备下一个BEGIN_IMPL_FRAME操作的过程,也就是Scheduler类的成员函数SetupNextBeginFrameIfNeeded的实现,如下所示:

void Scheduler::SetupNextBeginFrameIfNeeded() {
      bool needs_begin_frame = state_machine_.BeginFrameNeeded();

      if (settings_.throttle_frame_production) {
        SetupNextBeginFrameWhenVSyncThrottlingEnabled(needs_begin_frame);
      } else {
        SetupNextBeginFrameWhenVSyncThrottlingDisabled(needs_begin_frame);
      }
      SetupPollingMechanisms(needs_begin_frame);
    }

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

Scheduler类的成员函数SetupNextBeginFrameIfNeeded首先调用成员变量state_machine_指向的SchedulerStateMachine对象的成员函数BeginFrameNeeded询问状态机是否需要重新绘制网页。在需要的情况下,才可能会请求在下一个VSync信号到来时对网页进行重绘。这一点容易理解,如果网页不需要重绘,那么下一帧就继续显示上一帧的内容即可。

并不是所有的平台都支持VSync信号。如果当前平台不支持VSync信号,那么Scheduler类的成员变量settings_描述的一个SchedulerSettings对象的成员变量throttle_frame_production的值会等于false,这时候Scheduler类的成员函数SetupNextBeginFrameIfNeeded调用另外一个成员函数SetupNextBeginFrameWhenVSyncThrottlingDisabled简单地根据帧率来决定下一个BEGIN_IMPL_FRAME操作的时间点。例如,假设屏幕的刷新频率为60fps,那么Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingDisabled就会在1000 / 60 = 16.67毫秒后发起下一个BEGIN_IMPL_FRAME操作。

这里我们只考虑平台支持VSync信号,这时候Scheduler类的成员函数SetupNextBeginFrameIfNeeded就会调用另外一个成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled设置在下一个VSync信号到来时执行一个BEGIN_IMPL_FRAME操作。

BEGIN_IMPL_FRAME操作是通过调用Scheduler类的成员函数ProcessScheduledActions执行的,它的职责对网页进行渲染,特指图2所示的ACTION_DRAW_AND_SWAP_FORCED操作。但是,Scheduler类的成员函数ProcessScheduledActions在执行的时候,根据当时状态机的状态,也可能会执行其它类型的操作。这样BEGIN_IMPL_FRAME操作就会起到向前推进网页的渲染管线的作用,也就是使得网页不会停留在一个中间状态的作用。例如,状态机的CommitState状态不等于COMMIT_STATE_IDLE的时候,就是一个中间状态。假如这时候Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled通过计算发现不需要在下一个VSync信号到来时发起一个BEGIN_IMPL_FRAME操作。这就会意味着Scheduler类的成员函数ProcessScheduledActions很可能在接下来一段时间里不会被调用。这将会造成状态机停留在CommitState中间状态。

为了避免状态机长时间停留在中间状态,调度器提供了一种Polling机制,它会定时地调用Scheduler类的成员函数ProcessScheduledActions,这样就可以不断地将网页的渲染管线向前推进,直到到达稳定状态。这种Polling机制是通过调用Scheduler类的成员函数SetupPollingMechanisms运作的。Scheduler类的成员函数SetupPollingMechanisms的实现与前面提到的另外一个成员函数SetupNextBeginFrameWhenVSyncThrottlingDisabled类似,都是根据屏幕的刷新频率来定时调用Scheduler类的成员函数ProcessScheduledActions的。

接下来我们主要分析SchedulerStateMachine类的成员函数BeginFrameNeeded和Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled的实现,Scheduler类的另外两个成员函数SetupNextBeginFrameWhenVSyncThrottlingDisabled和SetupPollingMechanisms读者可以根据前面的解释进行自行分析。

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

bool SchedulerStateMachine::BeginFrameNeeded() const {
      // Proactive BeginFrames are bad for the synchronous compositor because we
      // have to draw when we get the BeginFrame and could end up drawing many
      // duplicate frames if our new frame isn't ready in time.
      // To poll for state with the synchronous compositor without having to draw,
      // we rely on ShouldPollForAnticipatedDrawTriggers instead.
      if (!SupportsProactiveBeginFrame())
        return BeginFrameNeededToAnimateOrDraw();

      return BeginFrameNeededToAnimateOrDraw() || ProactiveBeginFrameWanted();
    }

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

SchedulerStateMachine类的成员函数BeginFrameNeeded分两种情况决定当前是否是发起一个BEGIN_IMPL_FRAME操作。第一种情况是网页使用的合成器不支持主动发起BEGIN_IMPL_FRAME操作。第二种情况与第一种情况相反。

网页使用的合成器是否支持主动发起BEGIN_IMPL_FRAME操作,可以通过调用SchedulerStateMachine类的成员函数SupportsProactiveBeginFrame判断,它的实现如下所示:

// Note: If SupportsProactiveBeginFrame is false, the scheduler should poll
    // for changes in it's draw state so it can request a BeginFrame when it's
    // actually ready.
    bool SchedulerStateMachine::SupportsProactiveBeginFrame() const {
      // It is undesirable to proactively request BeginFrames if we are
      // using a synchronous compositor because we *must* draw for every
      // BeginFrame, which could cause duplicate draws.
      return !settings_.using_synchronous_renderer_compositor;
    }

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

当SchedulerStateMachine类的成员变量settings_描述的SchedulerSettings对象的成员变量using_synchronous_renderer_compositor等于false的时候,SchedulerStateMachine类的成员函数SupportsProactiveBeginFrame的返回值就等于true,表示网页使用的合成器支持主动发起BEGIN_IMPL_FRAME操作。

当SchedulerStateMachine类的成员变量settings_描述的SchedulerSettings对象的成员变量using_synchronous_renderer_compositor等于true的时候,表示网页使用的合成器是同步合成器(Synchronous Renderer Compositor)。同步合成器适用在WebView的情景。WebView是嵌入在应用程序窗口中显示网页的,它渲染网页的方式与独立的浏览器应用有所不同。简单来说,就是每当WebView需要重绘网页,它需要向应用程序窗口发送一个Invalidate消息。应用程序窗口接下来就会调用WebView的onDraw函数。一旦WebView的onDraw函数被调用,它就必须准备好要合成的内容。也就是它不能设置一个Deadline,等到Deadline到期时再去合成的内容。这就是所谓的"同步",而设置Deadline的方式是"异步"的。

回到SchedulerStateMachine类的成员函数BeginFrameNeeded中,如果网页使用的是同步合成器,那么就只有在调用另外一个成员函数BeginFrameNeededToAnimateOrDraw得到的返回值等于true的时候,SchedulerStateMachine类的成员函数BeginFrameNeeded的返回才会等于true。

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

// These are the cases where we definitely (or almost definitely) have a
    // new frame to animate and/or draw and can draw.
    bool SchedulerStateMachine::BeginFrameNeededToAnimateOrDraw() const {
      // The output surface is the provider of BeginImplFrames, so we are not going
      // to get them even if we ask for them.
      if (!HasInitializedOutputSurface())
        return false;

      // If we can't draw, don't tick until we are notified that we can draw again.
      if (!can_draw_)
        return false;

      // The forced draw respects our normal draw scheduling, so we need to
      // request a BeginImplFrame for it.
      if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)
        return true;

      // There's no need to produce frames if we are not visible.
      if (!visible_)
        return false;

      // We need to draw a more complete frame than we did the last BeginImplFrame,
      // so request another BeginImplFrame in anticipation that we will have
      // additional visible tiles.
      if (swap_used_incomplete_tile_)
        return true;

      if (needs_animate_)
        return true;

      return needs_redraw_;
    }

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

SchedulerStateMachine类的成员函数BeginFrameNeededToAnimateOrDraw返回true需要满足两个必要条件:

1. 网页当前的绘图表面可用,也就是网页的绘图表面已经创建,并且没有失效。这可以通过调用SchedulerStateMachine类的成员函数HasInitializedOutputSurface判断。

2. 网页的上一个CC Pending Layer Tree已经被激活为CC Active Layer Tree。这时候SchedulerStateMachine类的成员变量can_draw_的值会等于true。

满足了以上两个必要条件后,有四种情况会使得SchedulerStateMachine类的成员函数BeginFrameNeededToAnimateOrDraw返回true。

第一种情况是状态机的ForcedRedrawOnTimeoutState状态等于FORCED_REDRAW_STATE_WAITING_FOR_DRAW。从图6可以知道,这时候网页正在等待CC Pending Layer Tree激活为CC Active Layer Tree,以便得到的CC Active Layer Tree。现在既然CC Pending Layer Tree已经激活,因此就需要对网页执行一个渲染操作。

后面三种情况需要满足第三个必要条件,就是网页当前是可见的。这时候SchedulerStateMachine类的成员变量visible_会等于true。

第二种情况是网页上次渲染时,有些分块(Tile)还没有准备就绪,也就是还没有光栅化完成。这时候SchedulerStateMachine类的成员变量swap_used_incomplete_tile_会等于true。这种情况也要求执行一个渲染操作。在执行这个渲染操作的时候,调度器会检查之前未准备就绪的分块是否已经就准备就绪。如果已经准备就绪,那么就可以对它们进行渲染。

第三种情况是网页现在正处于动画显示过程中。这时候SchedulerStateMachine类的成员变量needs_animate_的值会等于true。这时候要求执行一个渲染操作,就可以使得动画持续执行下去。

第四种情况是网页被要求进行重新绘制,或者是因为CC Pending Layer Tree刚刚激活为CC Active Layer Tree,或者网页的CC Layer Tree上一次同步到CC Pending Layer Tree的过程中还没有完成就被取消了。这时候要求执行一个渲染操作,就可以使得刚刚激活得到的CC Active Layer Tree可以马上进行渲染,或者恢复CC Layer Tree同步到CC Pending Layer Tree的操作。

回到SchedulerStateMachine类的成员函数BeginFrameNeeded中,如果网页使用的不是同步合成器,那么除了调用成员函数BeginFrameNeededToAnimateOrDraw得到的返回值等于true的情况,还有另外一种情况也会使得SchedulerStateMachine类的成员函数BeginFrameNeeded的返回值等于true,就是调用另外一个成员函数ProactiveBeginFrameWanted得到的返回值也等于true。

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

// Proactively requesting the BeginImplFrame helps hide the round trip latency
    // of the SetNeedsBeginFrame request that has to go to the Browser.
    bool SchedulerStateMachine::ProactiveBeginFrameWanted() const {
      // The output surface is the provider of BeginImplFrames,
      // so we are not going to get them even if we ask for them.
      if (!HasInitializedOutputSurface())
        return false;

      // Do not be proactive when invisible.
      if (!visible_)
        return false;

      // We should proactively request a BeginImplFrame if a commit is pending
      // because we will want to draw if the commit completes quickly.
      if (needs_commit_ || commit_state_ != COMMIT_STATE_IDLE)
        return true;

      // If the pending tree activates quickly, we'll want a BeginImplFrame soon
      // to draw the new active tree.
      if (has_pending_tree_)
        return true;

      // Changing priorities may allow us to activate (given the new priorities),
      // which may result in a new frame.
      if (needs_manage_tiles_)
        return true;

      // If we just sent a swap request, it's likely that we are going to produce
      // another frame soon. This helps avoid negative glitches in our
      // SetNeedsBeginFrame requests, which may propagate to the BeginImplFrame
      // provider and get sampled at an inopportune time, delaying the next
      // BeginImplFrame.
      if (HasRequestedSwapThisFrame())
        return true;

      return false;
    }

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

SchedulerStateMachine类的成员函数ProactiveBeginFrameWanted返回true需要满足两个必要条件:

1. 网页的绘图表面已经创建好,并且没有失效。

2. 网页当前是可见的。

满足了以上两个必要条件后,有五种情况会使得SchedulerStateMachine类的成员函数ProactiveBeginFrameWanted返回true。

第一种情况是Main线程通知调度器网页的CC Layer Tree有新的变化,需要同步到CC Pending Layer Tree去。这时候SchedulerStateMachine类的成员变量needs_commit_会等于true。

第二种情况是状态机的CommitState状态不等于COMMIT_STATE_IDLE。这意味着Compositor线程正在执行同步CC Layer Tree和CC Pending Layer Tree的操作。为了正常推进这个操作,需要一个BEGIN_IMPL_FRAME操作,以便触发Scheduler类的成员函数ProcessScheduledActions的调用。这样就可以保证正常推进CC Layer Tree和CC Pending Layer Tree的同步操作。

第三种情况是网页存在一个CC Pending Layer Tree。这时候SchedulerStateMachine类的成员变量has_pending_tree_会等于true。这时候同样需要通过一个BEGIN_IMPL_FRAME操作推进这个CC Pending Layer Tree激活为CC Active Layer Tree。

第四种情况是Compositor线程需要对网页的分块进行光栅化操作。这时候SchedulerStateMachine类的成员变量needs_manage_tiles_会等于true。这种情况同样需要通过一个BEGIN_IMPL_FRAME操作推进光栅化操作的执行。

第五种情况是网页的当前帧已经渲染好,并且Render进程也已经向GPU发起了一个SwapBuffers操作,也就是请求Browser进程将网页的UI显示出来。这时候调用SchedulerStateMachine类的成员函数HasRequestedSwapThisFrame得到的返回值为true。这种情况请求执行下一个BEGIN_IMPL_FRAME操作,可以尽快地检查网页是否需要绘制下一帧,也就是让Main线程或者Compositor线程尽快准备好下一个帧。

对比SchedulerStateMachine类的成员函数ProactiveBeginFrameWanted和BeginFrameNeededToAnimateOrDraw的实现可以发现,前者比后者更激进地请求执行下一个BEGIN_IMPL_FRAME操作。后者在被动要求重绘网页下一帧的时候才会返回true,而前者会主动去准备网页的下一帧的绘制操作。

回到Scheduler类的成员函数SetupNextBeginFrameIfNeeded中,它通过调用SchedulerStateMachine类的成员函数SupportsProactiveBeginFrame获知是否要发起下一个BEGIN_IMPL_FRAME操作的信息后,如果平台支持VSync信号,那么接下来它会调用另外一个成员函数SetupNextBeginFrameWhenVSyncThrottlingEnable根据VSync信号来准备下一个BEGIN_IMPL_FRAME操作。

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

// When we are throttling frame production, we request BeginFrames
    // from the OutputSurface.
    void Scheduler::SetupNextBeginFrameWhenVSyncThrottlingEnabled(
        bool needs_begin_frame) {
      bool at_end_of_deadline =
          state_machine_.begin_impl_frame_state() ==
              SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE;

      bool should_call_set_needs_begin_frame =
          // Always request the BeginFrame immediately if it wasn't needed before.
          (needs_begin_frame && !last_set_needs_begin_frame_) ||
          // Only stop requesting BeginFrames after a deadline.
          (!needs_begin_frame && last_set_needs_begin_frame_ && at_end_of_deadline);

      if (should_call_set_needs_begin_frame) {
        if (settings_.begin_frame_scheduling_enabled) {
          client_->SetNeedsBeginFrame(needs_begin_frame);
        } else {
          synthetic_begin_frame_source_->SetNeedsBeginFrame(
              needs_begin_frame, &begin_retro_frame_args_);
        }
        last_set_needs_begin_frame_ = needs_begin_frame;
      }

      PostBeginRetroFrameIfNeeded();
    }

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

在两种情况下,Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnable会请求执行下一个BEGIN_IMPL_FRAME操作。

第一种情况是调度器之前没有请求过调度执行下一个BEGIN_IMPL_FRAME操作,这时候Scheduler类的成员变量last_set_needs_begin_frame_等于false。此时参数needs_begin_frame的值又等于true,也就是状态机要求执行一个BEGIN_IMPL_FRAME操作。这时候调度器就必须要调度执行一个BEGIN_IMPL_FRAME操作。否则的话,就永远不会执行下一个BEGIN_IMPL_FRAME操作。

第二种情况是调度器之前请求过调度执行下一个BEGIN_IMPL_FRAME操作,并且这个BEGIN_IMPL_FRAME操作的Deadline已经到来,以及参数needs_begin_frame的值等于false。试想这种情况,如果调度器不继续请求调度执行下一个BEGIN_IMPL_FRAME操作的话,网页的渲染管线在上一次请求的BEGIN_IMPL_FRAME操作执行完成后,就断开了。因此在这种情况下,也需要请求请示调度执行下一个BEGIN_IMPL_FRAME操作。

如果对上述两种情况做一个总结,就是只有在前两个连续的BEGIN_IMPL_FRAME操作期间,状态机都表示不需要绘制网页的下一帧的情况下,调度器才会停止请求调度执行下一个BEGIN_IMPL_FRAME操作。通过这种方式保证网页的渲染管线可以持续地推进到稳定状态,而不会停留在上面提到的中间状态。

一旦决定需要请求调度执行下一个BEGIN_IMPL_FRAME操作,本地变量should_call_set_needs_begin_frame的值就会被设置为true,这时候如果Scheduler类的成员变量settings_描述的SchedulerSettings对象的成员变量begin_frame_scheduling_enabled的值等于true,那么Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled就会调用成员变量client_指向的一个ThreadProxy对象的成员函数SetNeedsBeginFrame请求调度执行下一个BEGIN_IMPL_FRAME操作,否则的话,调用另外一个成员变量synthetic_begin_frame_source_指向的一个SyntheticBeginFrameSource对象的成员函数SetNeedsBeginFrame请求调度执行下一个BEGIN_IMPL_FRAME操作。

SyntheticBeginFrameSource类是通过软件方式调度执行BEGIN_IMPL_FRAME操作的,实际上就是通过定时器,根据屏幕的刷新频率模拟生成VSync信号。这种方式产生的VSync信号会受到定时器精度的影响。例如,假设屏幕刷新频率为60fps,那么就应该每16.67ms生成一个VSync信号。如果定时器只能精确到整数毫秒,那么就意味着定时器只能在16ms或者17ms后触发定时器。如果一直都是使用16ms,那么就会导致VSync信号的产生频率大于 60fps。如果一直都使用17ms,那么就会导致VSync信号的产生频率小于 60fps。为了弥补定时精度带来的缺陷,SyntheticBeginFrameSource类会自动调整定时器的Timeout时间,使得VSync信号的平均周期等于16.67ms,也就是第一个VSync信号16ms后产生,第二个VSync信号17ms后产生,第三个VSync信号17ms后产生,第四个VSync信号16ms后产生......,依次类推。这一点是SyntheticBeginFrameSource类通过使用另外一个类DelayBasedTimeSource实现的。有兴趣的读者可以自行分析DelayBasedTimeSource类的实现。

我们假设Scheduler类的成员变量settings_描述的SchedulerSettings对象的成员变量begin_frame_scheduling_enabled的值等于true,这表示屏幕支持以硬件方式产生VSync信号。在这种情况下,就直接利用屏幕产生的VSync信号调度BEGIN_IMPL_FRAME操作即可。如前所述,这是通过调用ThreadProxy类的成员函数SetNeedsBeginFrame实现的。

请求了调度下一个BEGIN_IMPL_FRAME操作之后,Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled还会调用另外一个成员函数PostBeginRetroFrameIfNeeded检查之前调度的BEGIN_IMPL_FRAME操作是否都已经得到执行。有时候之前请求调度的BEGIN_IMPL_FRAME操作在下一个VSync信号到来时,会被延时执行。这些被延时的BEGIN_IMPL_FRAME操作会被保存在一个队列中,等待Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled执行。注意,这个延时与BEGIN_IMPL_FRAME操作设置的Deadline是无关的。一个BEGIN_IMPL_FRAME操作只有被执行时,才可以设置Deadline。这一点我们后面再进行分析。

接下来我们继续分析ThreadProxy类的成员函数SetNeedsBeginFrame的实现,以便了解调度器通过VSync信号调度执行BEGIN_IMPL_FRAME操作的过程。

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

void ThreadProxy::SetNeedsBeginFrame(bool enable) {
      ......
      impl().layer_tree_host_impl->SetNeedsBeginFrame(enable);
      ......
    }

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

ThreadProxy类的成员函数SetNeedsBeginFrame实现是调用负责管理网页的CC Pending Layer Tree和CC Active Layer Tree的LayerTreeHostImpl对象的成员函数SetNeedsBeginFrame请求在下一个VSync信号到来时获得通知。

参数enable

相关标签

扫一扫

在手机上阅读