在采用线程化渲染方式渲染网页时,Chromium依赖一个调度器协调Main线程和Compositor线程的执行,同时也通过这个调度器决定它们什么时候该执行什么操作。调度器将Main线程和Compositor线程的当前状态记录在一个状态机中,然后通过这个状态机决定下一个要执行的操作。这个操作在满足当前设置条件下是最优的,因此可以使网页渲染更快更流畅。本文接下来就分析Chromium网页调度器的实现。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
调度器实现在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_paiting被设置为false。main_frame_before_draw_enabled设置为true,表示在上一个CC Active Layer Tree被渲染之前,允许Main线程绘制网页的下一帧。impl_side_paiting设置为true表示Main线程在绘制网页时,实际上只是记录了网页的绘制命令。只有在impl_side_paiting设置为true的时候,才会有CC Pending Layer Tree被激活为CC Active Layer Tree的环节。因此,在impl_side_paiting等于false的情况下,main_frame_before_draw_enabled被设置为true等同于main_frame_before_activation_enabled被设置为true的情况。
2. FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION。需要满足的条件是main_frame_before_draw_enabled和impl_side_paiting均被设置为true,并且main_frame_before_activation_enabled被设置为false。这表示在上一个CC Pending Layer Tree被激活为CC Active Layer Tree之后,才允许Main线程绘制网页的下一帧。
3. 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_pending_swaps_。
第三种情况是虽然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_IMPLP_FRAME操作。
第一种情况是调度器之前没有请求过调度执行下一个BEGIN_IMPLP_FRAME操作,这时候cheduler类的成员变量last_set_needs_begin_frame_等于false。此时参数needs_begin_frame的值又等于true,也就是状态机要求执行一个BEGIN_IMPLP_FRAME操作。这时候调度器就必须要调度执行一个BEGIN_IMPLP_FRAME操作。否则的话,就永远不会执行下一个BEGIN_IMPLP_FRAME操作。
第二种情况是调度器之前请求过调度执行下一个BEGIN_IMPLP_FRAME操作,并且这个BEGIN_IMPLP_FRAME操作的Deadline已经到来,以及参数needs_begin_frame的值等于false。试想这种情况,如果调度器不继续请求调度执行下一个BEGIN_IMPLP_FRAME操作的话,网页的渲染管线在上一次请求的BEGIN_IMPLP_FRAME操作执行完成后,就断开了。因此在这种情况下,也需要请求请示调度执行下一个BEGIN_IMPLP_FRAME操作。
如果对上述两种情况做一个总结,就是只有在前两个连续的BEGIN_IMPLP_FRAME操作期间,状态机都表示不需要绘制网页的下一帧的情况下,调度器才会停止请求调度执行下一个BEGIN_IMPLP_FRAME操作。通过这种方式保证网页的渲染管线可以持续地推进到稳定状态,而不会停留在上面提到的中间状态。
一旦决定需要请求调度执行下一个BEGIN_IMPLP_FRAME操作,本地变量should_call_set_needs_begin_frame的值就会被设置为true,这时候如果Scheduler类的成员变量settings_描述的SchedulerSettings对象的成员变量begin_frame_scheduling_enabled的值等于true,那么Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled就会调用成员变量client_指向的一个ThreadProxy对象的成员函数SetNeedsBeginFrame请求调度执行下一个BEGIN_IMPLP_FRAME操作,否则的话,调用另外一个成员变量synthetic_begin_frame_source_指向的一个SyntheticBeginFrameSource对象的成员函数SetNeedsBeginFrame请求调度执行下一个BEGIN_IMPLP_FRAME操作。
SyntheticBeginFrameSource类是通过软件方式调度执行BEGIN_IMPLP_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_IMPLP_FRAME操作即可。如前所述,这是通过调用ThreadProxy类的成员函数SetNeedsBeginFrame实现的。
请求了调度下一个BEGIN_IMPLP_FRAME操作之后,Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled还会调用另外一个成员函数PostBeginRetroFrameIfNeeded检查之前调度的BEGIN_IMPLP_FRAME操作是否都已经得到执行。有时候之前请求调度的BEGIN_IMPLP_FRAME操作在下一个VSync信号到来时,会被延时执行。这些被延时的BEGIN_IMPLP_FRAME操作会被保存在一个队列中,等待Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnabled执行。注意,这个延时与BEGIN_IMPLP_FRAME操作设置的Deadline是无关的。一个BEGIN_IMPLP_FRAME操作只有被执行时,才可以设置Deadline。这一点我们后面再进行分析。
接下来我们继续分析ThreadProxy类的成员函数SetNeedsBeginFrame的实现,以便了解调度器通过VSync信号调度执行BEGIN_IMPLP_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表示是否需要持续地请求VSync信号通知。从前面的调用过程可以知道,当网页需要渲染下一帧时,参数enable的值就会等于true。当参数enable的值等于false的时候,实际上是表示调度器要停止接收VSync信号。
LayerTreeHostImpl类的成员函数SetNeedsBeginFrame的实现如下所示:
void LayerTreeHostImpl::SetNeedsBeginFrame(bool enable) { if (output_surface_) output_surface_->SetNeedsBeginFrame(enable); else DCHECK(!enable); }
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
LayerTreeHostImpl类的成员变量output_surface_描述的就是网页的绘图表面。在接下来一篇文章中,我们会分析这个绘图表面的创建过程。这个绘图表面是通过一个CompositorOutputSurface对象描述的,因此LayerTreeHostImpl类的成员函数SetNeedsBeginFrame接下来会调用CompositorOutputSurface类的成员函数SetNeedsBeginFrame请求接收下一个VSync信号通知。
CompositorOutputSurface类的成员函数SetNeedsBeginFrame的实现如下所示:
void CompositorOutputSurface::SetNeedsBeginFrame(bool enable) { DCHECK(CalledOnValidThread()); Send(new ViewHostMsg_SetNeedsBeginFrame(routing_id_, enable)); }
这个函数定义在文件external/chromium_org/content/renderer/gpu/compositor_output_surface.cc中。
CompositorOutputSurface类的成员函数SetNeedsBeginFrame向Browser进程发送一个类型为ViewHostMsg_SetNeedsBeginFrame的IPC消息。在Android平台上,这个IPC消息由RenderWidgetHostViewAndroid类的成员函数OnMessageReceived接收,如下所示:
bool RenderWidgetHostViewAndroid::OnMessageReceived( const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(RenderWidgetHostViewAndroid, message) ...... IPC_MESSAGE_HANDLER(ViewHostMsg_SetNeedsBeginFrame, OnSetNeedsBeginFrame) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; }
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。
负责接收消息的RenderWidgetHostViewAndroid对象描述的是Browser进程为在Render进程中加载的网页创建一个RenderWidgetHostView控件。这个RenderWidgetHostView控件的创建过程,可以参考前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文。
RenderWidgetHostViewAndroid类的成员函数OnMessageReceived将类型为ViewHostMsg_SetNeedsBeginFrame的IPC消息分发给成员函数OnSetNeedsBeginFrame处理,如下所示:
void RenderWidgetHostViewAndroid::OnSetNeedsBeginFrame(bool enabled) { if (enabled == needs_begin_frame_) return; TRACE_EVENT1("cc", "RenderWidgetHostViewAndroid::OnSetNeedsBeginFrame", "enabled", enabled); if (content_view_core_ && enabled) content_view_core_->GetWindowAndroid()->RequestVSyncUpdate(); needs_begin_frame_ = enabled; }
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。
RenderWidgetHostViewAndroid类的成员变量content_view_core_指向一个ContentViewCoreImpl对象。调用这个ContentViewCoreImpl对象的成员函数GetWindowAndroid可以获得一个WindowAndroid对象。有了这个WindowAndroid对象之后,就可以调用它的成员函数RequestVSyncUpdate请求获得下一个VSync信号通知。不过,在参数enabled等于true的情况下,才会执行这个操作。
WindowAndroid类的成员函数RequestVSyncUpdate通过Java层的Choreographer接口请求系统在下一个VSync信号到来时,给当前进程发送一个通知。当前进程获得这个通知后,会调用RenderWidgetHostViewAndroid类的成员函数OnVSync,这样RenderWidgetHostViewAndroid类就获得了VSync信号通知。
RenderWidgetHostViewAndroid类的成员函数OnVSync的实现如下所示:
void RenderWidgetHostViewAndroid::OnVSync(base::TimeTicks frame_time, base::TimeDelta vsync_period) { ...... base::TimeTicks display_time = frame_time + vsync_period; // TODO(brianderson): Use adaptive draw-time estimation. base::TimeDelta estimated_browser_composite_time = base::TimeDelta::FromMicroseconds( (1.0f * base::Time::kMicrosecondsPerSecond) / (3.0f * 60)); base::TimeTicks deadline = display_time - estimated_browser_composite_time; host_->Send(new ViewMsg_BeginFrame( host_->GetRoutingID(), cc::BeginFrameArgs::Create(frame_time, deadline, vsync_period))); if (needs_begin_frame_) content_view_core_->GetWindowAndroid()->RequestVSyncUpdate(); }
这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_view_android.cc中。
参数frame_time表示当前帧的时间,另外一个参数vsync_period表示屏幕的刷新周期。这两个参数相加,就得到下一帧的显示时间,记录在本地变量display_time中。
RenderWidgetHostViewAndroid类的成员函数OnVSync接下来计算Browser进程合成网页所需要的时间,记录在本地变量estimated_browser_composite_time中。这个时间等于1/3屏幕刷新周期,并且假设屏幕刷新频率为60fps。
用下一帧的显示时间display_time减去Browser进程合成网页所需要的时间estimated_browser_composite_time,就可以得到Render进程将UI提交给Browser进程合成的最迟时间,这个时间记录在本地变量deadline中。
再接下来,RenderWidgetHostViewAndroid类的成员函数OnVSync根据时间frame_time、deadline和vsync_period创建了一个BeginFrameArgs对象中,并且将该BeginFrameArgs对象封装成一个类型为ViewMsg_BeginFrame的IPC消息,发送给Render进程。Render进程接收到该消息后,就会根据上面计算出来的deadline时间调度执行一个BEGIN_IMPL_FRAME操作。
同时,我们还会看到,当RenderWidgetHostViewAndroid类的成员变量needs_begin_frame_等于true的时候,RenderWidgetHostViewAndroid类的成员函数OnVSync会继续请求获得下一个VSync信号通知,这样就能持续地通知Render进程执行BEGIN_IMPL_FRAME操作。
Render进程是通过CompositorOutputSurface类的成员函数OnMessageReceived接收类型为ViewMsg_BeginFrame的IPC消息的,如下所示:
void CompositorOutputSurface::OnMessageReceived(const IPC::Message& message) { ...... IPC_BEGIN_MESSAGE_MAP(CompositorOutputSurface, message) ...... #if defined(OS_ANDROID) IPC_MESSAGE_HANDLER(ViewMsg_BeginFrame, OnBeginFrame); #endif IPC_END_MESSAGE_MAP() }
这个函数定义在文件external/chromium_org/content/renderer/gpu/compositor_output_surface.cc中。
从这里可以看到,CompositorOutputSurface类的成员函数OnMessageReceived将类型为ViewMsg_BeginFrame的IPC消息分发给成员函数OnBeginFrame处理,如下所示:
void CompositorOutputSurface::OnBeginFrame(const cc::BeginFrameArgs& args) { DCHECK(CalledOnValidThread()); client_->BeginFrame(args); }
这个函数定义在文件external/chromium_org/content/renderer/gpu/compositor_output_surface.cc中。
CompositorOutputSurface类的成员变量client_指向的是一个LayerTreeHostImpl对象,CompositorOutputSurface类的成员函数OnBeginFrame调用它的成员函数BeginFrame通知它现在可以执行一个BEGIN_IMPL_FRAME操作了。
LayerTreeHostImpl类的成员函数BeginFrame的实现如下所示:
void LayerTreeHostImpl::BeginFrame(const BeginFrameArgs& args) { client_->BeginFrame(args); }
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
LayerTreeHostImpl类的成员变量client_指向的是一个ThreadProxy对象,LayerTreeHostImpl类的成员函数BeginFrame调用它的成员函数BeginFrame通知执行一个BEGIN_IMPL_FRAME操作。
ThreadProxy类的成员函数BeginFrame的实现如下所示:
void ThreadProxy::BeginFrame(const BeginFrameArgs& args) { impl().scheduler->BeginFrame(args); }
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类的成员函数BeginFrame又通知调度器执行一个BEGIN_IMPL_FRAME操作,也就是调用Scheduler类的成员函数BeginFrame。
Scheduler类的成员函数BeginFrame的实现如下所示:
// BeginFrame is the mechanism that tells us that now is a good time to start // making a frame. Usually this means that user input for the frame is complete. // If the scheduler is busy, we queue the BeginFrame to be handled later as // a BeginRetroFrame. void Scheduler::BeginFrame(const BeginFrameArgs& args) { ...... BeginFrameArgs adjusted_args(args); adjusted_args.deadline -= EstimatedParentDrawTime(); bool should_defer_begin_frame; if (settings_.using_synchronous_renderer_compositor) { should_defer_begin_frame = false; } else { should_defer_begin_frame = !begin_retro_frame_args_.empty() || begin_retro_frame_posted_ || !last_set_needs_begin_frame_ || (state_machine_.begin_impl_frame_state() != SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); } if (should_defer_begin_frame) { begin_retro_frame_args_.push_back(adjusted_args); ...... return; } BeginImplFrame(adjusted_args); }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数BeginFrame首先调用另外一个成员函数EstimatedParentDrawTime得到浏览器合成网页UI所需要的时间,并且从参数args传递进来的Deadline减去这个时间,从而得到新的Deadline。
Scheduler类的成员函数EstimatedParentDrawTime的实现如下所示:
class CC_EXPORT Scheduler { public: ...... base::TimeDelta EstimatedParentDrawTime() { return estimated_parent_draw_time_; } ...... private: ...... base::TimeDelta estimated_parent_draw_time_; ...... };
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.h中。
从这里看到,Scheduler类的成员函数EstimatedParentDrawTime返回的是成员变量estimated_parent_draw_time_的值。这个成员变量的值是通过调用Scheduler类的成员函数SetEstimatedParentDrawTime设置的,如下所示:
void Scheduler::SetEstimatedParentDrawTime(base::TimeDelta draw_time) { DCHECK_GE(draw_time.ToInternalValue(), 0); estimated_parent_draw_time_ = draw_time; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数SetEstimatedParentDrawTime是被ThreadProxy类的成员函数SetEstimatedParentDrawTime调用的,如下所示:
void ThreadProxy::SetEstimatedParentDrawTime(base::TimeDelta draw_time) { impl().scheduler->SetEstimatedParentDrawTime(draw_time); }
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类的成员函数SetEstimatedParentDrawTime又是被LayerTreeHostImpl类的成员函数InitializeRenderer调用的,如下所示:
bool LayerTreeHostImpl::InitializeRenderer( scoped_ptr<OutputSurface> output_surface) { ...... // TODO(brianderson): Don‘t use a hard-coded parent draw time. base::TimeDelta parent_draw_time = (!settings_.begin_frame_scheduling_enabled && output_surface_->capabilities().adjust_deadline_for_parent) ? BeginFrameArgs::DefaultEstimatedParentDrawTime() : base::TimeDelta(); client_->SetEstimatedParentDrawTime(parent_draw_time); ...... }
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
LayerTreeHostImpl类的成员变量settings_指向的是一个LayerTreeSettings对象。当这个LayerTreeSettings对象的成员变量begin_frame_scheduling_enabled等于false的时候,表示调度器是利用前面提到的软件方式产生的VSync信号来调度执行BEGIN_IMPL_FRAME操作的。在这种情况下,前面分析的Scheduler类的成员函数BeginFrame通过参数args获得的Deadline还没有减去Browser进程合成网页UI所需要的时间。
LayerTreeHostImpl类的另外一人成员变量output_surface_描述的是网页的绘图表面。当网页的绘图表面有调整Browser进程合成网页UI所需要的时间的能力,并且调度器是利用前面提到的软件方式产生的VSync信号来调度执行BEGIN_IMPL_FRAME操作时,LayerTreeHostImpl类的成员函数InitializeRenderer就会将Browser进程合成网页UI所需要的时间设置为调用BeginFrameArgs类的静态成员函数DefaultEstimatedParentDrawTime获得的值。否则的话,就会将Browser进程合成网页UI所需要的时间为0。不过,从前面的分析可以知道,当调度器是利用前面提到的硬件方式产生的VSync信号来调度执行BEGIN_IMPL_FRAME操作时,在计算下一个BEGIN_IMPL_FRAME操作的Deadline时,已经将Browser进程合成网页UI所需要的时间考虑进去了,如前面分析的RenderWidgetHostViewAndroid类的成员函数OnVSync所示。
BeginFrameArgs类的静态成员函数DefaultEstimatedParentDrawTime的实现如下所示:
base::TimeDelta BeginFrameArgs::DefaultEstimatedParentDrawTime() { return base::TimeDelta::FromMicroseconds(16666 / 3); }
这个函数定在文件external/chromium_org/cc/output/begin_frame_args.cc中。
BeginFrameArgs类的静态成员函数DefaultEstimatedParentDrawTime也是按照屏幕刷新频率为60fps计算Browser进程合成网页UI所需要的时间的,计算出来的时间等于1/3刷新周期,也就是(16.666 / 3)毫秒。
这意味着无论是利用硬件方式还是软件方式产生的VSync信号来调度BEGIN_IMPL_FRAME操作,Browser进程合成网页UI所需要的时间都是设置为1/3屏幕刷新周期,并且假定屏幕的刷新频率为60fps。
回到Scheduler类的成员函数BeginFrame中,它计算好下一个BEGIN_IMPL_FRAME操作的Deadline之后,接下来再看看是否要延迟执行下一个BEGIN_IMPL_FRAME操作。如果网页使用的是非同步合成器,那么有四种情况下需要延迟执行下一个BEGIN_IMPL_FRAME操作。
第一种情况是之前请求执行的BEGIN_IMPL_FRAME操作还没有全部被调度执行,这时候Scheduler类的成员变量begin_retro_frame_args_描述的一个std::deque不等于空。
第二种情况是在下一个VSync信号到来之前,已经有一个BEGIN_IMPL_FRAME操作在调度执行,这时候Scheduler类的成员变量begin_retro_frame_posted_的值等于true。
第三种情况是网页的下一帧实际上是不需要重新绘制的,这时候Scheduler类的成员变量last_set_needs_begin_frame_的值等于false。
第四种情况是状态机的BeginImplFrameState状态不等于BEGIN_IMPL_FRAME_STATE_IDLE,这意味有一个BEGIN_IMPL_FRAME操作正在执行。
此外,如果网页使用的是同步合成器,那么是不允许延迟执行BEGIN_IMPL_FRAME操作的。
对于需要延迟执行的BEGIN_IMPL_FRAME操作,与之对应的一个BeginFrameArgs对象都被保存在Scheduler类的成员变量begin_retro_frame_args_描述的一个std::deque中,而对于不需要延迟执行的BEGIN_IMPL_FRAME操作,Scheduler类的成员函数BeginFrame会马上调用另外一个成员函数BeginImplFrame执行它。
前面在分析Scheduler类的成员函数SetupNextBeginFrameWhenVSyncThrottlingEnable时提到,它在请求调度执行下一个BEGIN_IMPL_FRAME操作后,会马上调用另外一个成员函数PostBeginRetroFrameIfNeeded检查之前请求的BEGIN_IMPL_FRAME操作是否都已经得到执行,如下所示:
void Scheduler::PostBeginRetroFrameIfNeeded() { if (!last_set_needs_begin_frame_) return; if (begin_retro_frame_args_.empty() || begin_retro_frame_posted_) return; // begin_retro_frame_args_ should always be empty for the // synchronous compositor. DCHECK(!settings_.using_synchronous_renderer_compositor); if (state_machine_.begin_impl_frame_state() != SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE) return; begin_retro_frame_posted_ = true; impl_task_runner_->PostTask(FROM_HERE, begin_retro_frame_closure_); }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
从前面的分析可以知道,当Scheduler类的成员变量begin_retro_frame_args_描述的一个std::deque不等于空的时候,意味着有BEGIN_IMPL_FRAME操作被延迟执行,而现在就是时候去执行它们了,不过要满足另外三个条件:
1. 网页的下一帧是需要重新绘制的,也就是Scheduler类的成员变量last_set_needs_begin_frame_的值等于true。
2. 当前没有延迟的BEGIN_IMPL_FRAME操作加入到Compositor线程的消息队列中等待执行,也就是Scheduler类的成员变量begin_retro_frame_posted_的值等于false。
3. 状态机的BeginImplFrameState状态不等于BEGIN_IMPL_FRAME_STATE_IDLE,这也意味着有目前有一个BEGIN_IMPL_FRAME正在执行过程中。
一旦Scheduler类的成员函数PostBeginRetroFrameIfNeeded决定要执行一个之前被延迟执行的BEGIN_IMPL_FRAME操作,它就会向Compositor线程的消息队列发送一个Task,这个Task通过Scheduler类的成员变量begin_retro_frame_closure_绑定了Scheduler类的成员函数BeginRetroFrame,并且会将Scheduler类的成员变量begin_retro_frame_posted_的值设置为true。
在上述情况下,Scheduler类的成员函数BeginRetroFrame很快就会在Compositor线程中执行,执行过程如下所示:
void Scheduler::BeginRetroFrame() { ...... begin_retro_frame_posted_ = false; // If there aren‘t any retroactive BeginFrames, then we‘ve lost the // OutputSurface and should abort. if (begin_retro_frame_args_.empty()) return; // Discard expired BeginRetroFrames // Today, we should always end up with at most one un-expired BeginRetroFrame // because deadlines will not be greater than the next frame time. We don‘t // DCHECK though because some systems don‘t always have monotonic timestamps. // TODO(brianderson): In the future, long deadlines could result in us not // draining the queue if we don‘t catch up. If we consistently can‘t catch // up, our fallback should be to lower our frame rate. base::TimeTicks now = gfx::FrameTime::Now(); base::TimeDelta draw_duration_estimate = client_->DrawDurationEstimate(); while (!begin_retro_frame_args_.empty() && now > AdjustedBeginImplFrameDeadline(begin_retro_frame_args_.front(), draw_duration_estimate)) { ..... begin_retro_frame_args_.pop_front(); } if (begin_retro_frame_args_.empty()) { DCHECK(settings_.throttle_frame_production); ...... } else { BeginImplFrame(begin_retro_frame_args_.front()); begin_retro_frame_args_.pop_front(); } }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数BeginRetroFrame首先将成员变量begin_retro_frame_posted_的值设置为false,表示之前发送Compositor线程的消息队列中的执行延迟BEGIN_IMPL_FRAME操作的Task已经得到执行,这样调度器后面就可以继续执行下一个被延迟的BEGIN_IMPL_FRAME操作了。
接下来Scheduler类的成员函数BeginRetroFrame通过一个while循环将那些Deadline小于等于当前时间的BEGIN_IMPL_FRAME操作丢弃。这也就是说,那些被延迟的BEGIN_IMPL_FRAME操作,只有在它们的Deadline到来之前,才会被继续执行。一旦存在这样的BEGIN_IMPL_FRAME操作,它就会从Scheduler类的成员变量begin_retro_frame_args_描述的std::queue中移除,并且通过调用Scheduler类的成员函数BeginImplFrame执行。
Scheduler类的成员函数BeginImplFrame的实现如下所示:
void Scheduler::BeginImplFrame(const BeginFrameArgs& args) { ...... DCHECK(state_machine_.begin_impl_frame_state() == SchedulerStateMachine::BEGIN_IMPL_FRAME_STATE_IDLE); DCHECK(state_machine_.HasInitializedOutputSurface()); ...... base::TimeDelta draw_duration_estimate = client_->DrawDurationEstimate(); begin_impl_frame_args_ = args; begin_impl_frame_args_.deadline -= draw_duration_estimate; if (!state_machine_.smoothness_takes_priority() && state_machine_.MainThreadIsInHighLatencyMode() && CanCommitAndActivateBeforeDeadline()) { state_machine_.SetSkipNextBeginMainFrameToReduceLatency(); } ...... state_machine_.OnBeginImplFrame(begin_impl_frame_args_); ...... ProcessScheduledActions(); state_machine_.OnBeginImplFrameDeadlinePending(); ScheduleBeginImplFrameDeadline( AdjustedBeginImplFrameDeadline(args, draw_duration_estimate)); }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数BeginImplFrame首先是调用成员变量client_指向的一个ThreadProxy对象的成员函数DrawDurationEstimate根据以往网页每一帧的绘制时间评估出下一帧的绘制时间,并且会从参数args描述的Deadline中减去该时间,从而得到新的Deadline。新的Deadline记录在Scheduler类的成员变量begin_impl_frame_args_中。
Scheduler类的成员函数BeginImplFrame接下来检查是否要在即将要执行的BEGIN_IMPL_FRAME操作期间,跳过执行BEGIN_MAIN_FRAME操作。BEGIN_MAIN_FRAME操作指的就是图2所示的第2个操作ACTION_SEND_BEGIN_MAIN_FRAME,也就是Main线程绘制CC Layer Tree的操作。
在满足以下三个条件的情况下,在即将要执行的BEGIN_IMPL_FRAME操作期间,BEGIN_MAIN_FRAME操作会被禁止执行:
1. 网页新内容优化于现有内容渲染,也就是如果CC Layer Tree的内容发生了变化,那么就需要尽快渲染。这时候调用Scheduler类的成员变量state_machine_指向的一个SchedulerStateMachine对象的成员函数smoothness_takes_priority得到的返回值等于false。
2. Main线程当前处于高延时模式。这时候调用Scheduler类的成员变量state_machine_指向的一个SchedulerStateMachine对象的成员函数MainThreadIsInHighLatencyMode得到的返回值等于true。
3. Main线程绘制CC Layer Tree的所需要的时间,加上将CC Layer Tree同步到CC Pending Layer Tree所需要的时间,小于即将要执行的BEGIN_IMPL_FRAME操作设置的Deadline。这时候调用Scheduler类的成员函数CanCommitAndActivateBeforeDeadline得到的返回值等于true。
对于上述3个条件,简单一点理解就是:如果Main线程当前处于高延时模式,那么就是在即将要执行的BEGIN_IMPL_FRAME操作期间,禁止执行BEGIN_MAIN_FRAME操作。
接下来我们先分析SchedulerStateMachine类的成员函数MainThreadIsInHighLatencyMode的实现,以便了解什么情况下Main线程会被认为处于高延时模式,它的实现如下所示:
bool SchedulerStateMachine::MainThreadIsInHighLatencyMode() const { // If a commit is pending before the previous commit has been drawn, we // are definitely in a high latency mode. if (CommitPending() && (active_tree_needs_first_draw_ || has_pending_tree_)) return true; // If we just sent a BeginMainFrame and haven‘t hit the deadline yet, the main // thread is in a low latency mode. if (HasSentBeginMainFrameThisFrame() && (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING || begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME)) return false; // If there‘s a commit in progress it must either be from the previous frame // or it started after the impl thread‘s deadline. In either case the main // thread is in high latency mode. if (CommitPending()) return true; // Similarly, if there‘s a pending tree the main thread is in high latency // mode, because either // it‘s from the previous frame // or // we‘re currently drawing the active tree and the pending tree will thus // only be drawn in the next frame. if (has_pending_tree_) return true; if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) { // Even if there‘s a new active tree to draw at the deadline or we‘ve just // swapped it, it may have been triggered by a previous BeginImplFrame, in // which case the main thread is in a high latency mode. return (active_tree_needs_first_draw_ || HasSwappedThisFrame()) && !HasSentBeginMainFrameThisFrame(); } // If the active tree needs its first draw in any other state, we know the // main thread is in a high latency mode. return active_tree_needs_first_draw_; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数MainThreadIsInHighLatencyMode是在即将要执行下一个BEGIN_IMPL_FRAME操作时调用的,也就是在即将要渲染网页的下一帧时调用的,这时候状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_IDLE,它判断Main线程是否处理高延时模式是通过观察网页前两两个帧的渲染情况进行的。在这种情况下,如果在网页之前两帧或者之前一帧的渲染过程中Main线程都有参与,并且到目前为止,渲染工作还没有完成,那么就认为Main线程耗费的时间太多了。从这个判断逻辑可以看出,渲染网页一帧所需要的时间,主要取决于Main线程所做的工作。如果Main线程所做的工作不多,那么Compositor线程做的工作也不多,这是一个正相关的关系。
当SchedulerStateMachine类的成员变量has_pending_tree_等于true的时候,表示前一帧的CC Pending Layer Tree还没有完成光栅化操作。这种情况就认为是前一帧的渲染还没有完成。因此,这时候Main线程处于高延时模式。
当SchedulerStateMachine类的成员变量active_tree_needs_first_draw_等于true的时候,表示前一帧的CC Pending Layer Tree刚刚被激活为CC Active Layer Tree,并且正在等待第一次渲染。这种情况也认为是前一帧的渲染还没有完成。因此,这时候Main线程处于高延时模式。
当调用SchedulerStateMachine类的成员函数CommitPending得到的返回值等于true的时候,表示状态机的CommitState状态等COMMIT_STATE_BEGIN_MAIN_FRAME_SENT、COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED或者COMMIT_STATE_READY_TO_COMMIT,这时候表示Main线程正在将CC Layer Tree同步到CC Pending Layer Tree去。这种情况也认为是前一帧的渲染还没有完成。因此,这时候Main线程处于高延时模式。
由于只有在Main线程将CC Layer Tree同步到CC Pending Layer Tree去之后,才会得到一个新的CC Pending Layer Tree。因此,当调用SchedulerStateMachine类的成员函数CommitPending得到的返回值等于true的时候,如果SchedulerStateMachine类的成员变量has_pending_tree_active_tree_needs_first_draw_也等于true,那么就会认为网页之前的两帧都还没有完成渲染工作。换句话就说,第三帧马上要渲染的时候,第一帧还没有光栅化操作或者刚刚完成光栅化操作但是还未来得及第一次渲染,第二帧正在将CC Layer Tree同步到新的CC Pending Layer Tree去。这时候就很明确地确定Main线程处于高延时模式。
如果SchedulerStateMachine类的成员函数MainThreadIsInHighLatencyMode不是在即将要执行下一个BEGIN_IMPL_FRAME操作时调用的,也就是状态机的BeginImplFrameState状态不等于BEGIN_IMPL_FRAME_STATE_IDLE。我们分两种情况讨论。
第一种情况是状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING或者BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME。这时候是允许Main线程对CC Layer Tree进行重绘的,也就是这时候允许调度器调度执行一个ACTION_SEND_MAIN_BEGIN_FRAME操作。在这种情况下,如果调度器确实也调度执行了一个ACTION_SEND_MAIN_BEGIN_FRAME操作,也就是调用SchedulerStateMachine类的成员函数HasSentBeginMainFrameThisFrame得到的返回值等于true,那么可以确定Main线程不是处于高延时模式。这是因为调度器在前一帧的渲染工作完成之后才会调度执行一个ACTION_SEND_MAIN_BEGIN_FRAME操作。
第二种情况是状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE。这时候如果有一个刚刚激活的CC Active Layer Tree,并且它还没有被渲染,也就是SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值等于true,这也是说明前一帧还没有渲染完成。因此,这时候会认为Main线程处于高延时模式。另一方面,这时候如果调用SchedulerStateMachine类的成员函数HasSwappedThisFrame得到的返回值等于true,那么就说明有一帧刚刚被渲染。但是这一帧有可能不是当前帧,而是前一帧。这一点可以通过调用SchedulerStateMachine类的成员函数HasSentBeginMainFrameThisFrame判断。如果得到的返回值等于false,那么就说明在当前的BEGIN_IMPL_FRAME操作期间,没有执行过ACTION_SEND_MAIN_BEGIN_FRAME操作。这意味着刚刚被渲染的是上一帧的内容。由于上一帧的内容是在当前的BEGIN_IMPL_FRAME操作的Deadline到来时才渲染完成,Main线程就会认为处于高延时模式。理想的情况是,上一帧在下一帧的Deadline到来之前就渲染完成。
回到Scheduler类的成员函数BeginImplFrame中,如果这时候Main线程被认为处于高延时模式,那么调度器就会给状态机就会设置一个标记,禁止它在接下来的BEGIN_IMPL_FRAME操作中,执行ACTION_SEND_MAIN_BEGIN_FRAME操作,也就是禁止Main线程提交新的内容给Compositor线程渲染。这是通过调用SchedulerStateMachine类的成员函数etSkipNextBeginMainFrameToReduceLatency实现的,如下所示:
void SchedulerStateMachine::SetSkipNextBeginMainFrameToReduceLatency() { skip_next_begin_main_frame_to_reduce_latency_ = true; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数etSkipNextBeginMainFrameToReduceLatency将成员变量skip_next_begin_main_frame_to_reduce_latency_的值设置为true,这样在接下来的BEGIN_IMPL_FRAME操作期间,状态机就不会通知调度器执行ACTION_SEND_MAIN_BEGIN_FRAME操作。
继续回到Scheduler类的成员函数BeginImplFrame中,它接下来就分四个阶段执行一个BEGIN_IMPL_FRAME操作。
第一阶段是将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING,然后调用Scheduler类的成员函数ProcessScheduledActions允许调度器调度执行其它操作。我们结合图1看,这实际上就是允许在当前的BEGIN_IMPL_FRAME操作的Deadline到来前,执行其它操作。如果这些操作能在当前的BEGIN_IMPL_FRAME操作的Deadline到来前完成,那么就不但不会影响当前帧的渲染,还可以让调度器提前去做下一帧的工作。例如,这时候调度器可以通知Main线程去绘制下一帧的内容,也就是执行一个ACTION_SEND_MAIN_BEGIN_FRAME操作。
将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING是通过调用SchedulerStateMachine类的成员函数OnBeginImplFrame实现的,如下所示:
void SchedulerStateMachine::OnBeginImplFrame(const BeginFrameArgs& args) { AdvanceCurrentFrameNumber(); begin_impl_frame_args_ = args; DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_IDLE) << *AsValue(); begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数OnBeginImplFrame除了将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING,还会增加状态机内部维护的一个帧号,这是通过调用另外一个成员函数AdvanceCurrentFrameNumber实现的,如下所示:
void SchedulerStateMachine::AdvanceCurrentFrameNumber() { current_frame_number_++; // "Drain" the ManageTiles funnel. if (manage_tiles_funnel_ > 0) manage_tiles_funnel_--; skip_begin_main_frame_to_reduce_latency_ = skip_next_begin_main_frame_to_reduce_latency_; skip_next_begin_main_frame_to_reduce_latency_ = false; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数AdvanceCurrentFrameNumber将成员变量current_frame_number_的值增加1,表示调度器要执行一个新的BEGIN_IMPL_FRAME操作。这个成员变量描述的是当前执行的BEGIN_IMPL_FRAME操作的顺序号。通过这个顺序号可以判断状态机的某些状态是与上一帧相关的,还是与当前帧相关的。
除了设置当前执行的BEGIN_IMPL_FRAME操作的顺序号,SchedulerStateMachine类的成员函数AdvanceCurrentFrameNumber还会判断另外一个成员变量manage_tiles_funnel_的值是否大于0。如果大于0,那么就将它的值减少1。这个成员变量用来控制每一个BEGIN_IMPL_FRAME操作只执行一次分块光栅化操作。
SchedulerStateMachine类的成员函数AdvanceCurrentFrameNumber最后将成员变量skip_next_begin_main_frame_to_reduce_latency_的值设置到另外一个成员变量skip_next_begin_main_frame_to_reduce_latency_中去,使得前者的值只在当前帧中生效。
回到Scheduler类的成员函数BeginImplFrame中,从另外一个成员函数ProcessScheduledActions返回来后,第一阶段工作就执行完成。接着开始执行第二阶段工作,也就是等待当前BEGIN_IMPL_FRAME操作的Deadline的到来。
第二阶段工作由三部分组成。第一部分是调用SchedulerStateMachine类的成员函数OnBeginImplFrameDeadlinePending将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME。第二部分是调用Scheduler类的成员函数AdjustedBeginImplFrameDeadline检查是否需要调整Deadline。第三部分是调用Scheduler类的成员函数ScheduleBeginImplFrameDeadline等待Deadline的到来。
SchedulerStateMachine类的成员函数OnBeginImplFrameDeadlinePending的实现如下所示:
void SchedulerStateMachine::OnBeginImplFrameDeadlinePending() { DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_BEGIN_FRAME_STARTING) << *AsValue(); begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
Scheduler类的成员函数AdjustedBeginImplFrameDeadline的实现如下所示:
base::TimeTicks Scheduler::AdjustedBeginImplFrameDeadline( const BeginFrameArgs& args, base::TimeDelta draw_duration_estimate) const { if (settings_.using_synchronous_renderer_compositor) { // The synchronous compositor needs to draw right away. return base::TimeTicks(); } else if (state_machine_.ShouldTriggerBeginImplFrameDeadlineEarly()) { // We are ready to draw a new active tree immediately. return base::TimeTicks(); } else if (state_machine_.needs_redraw()) { // We have an animation or fast input path on the impl thread that wants // to draw, so don‘t wait too long for a new active tree. return args.deadline - draw_duration_estimate; } else { // The impl thread doesn‘t have anything it wants to draw and we are just // waiting for a new active tree, so post the deadline for the next // expected BeginImplFrame start. This allows us to draw immediately when // there is a new active tree, instead of waiting for the next // BeginImplFrame. // TODO(brianderson): Handle long deadlines (that are past the next frame‘s // frame time) properly instead of using this hack. return args.frame_time + args.interval; } }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
如前所述,如果网页使用的是同步合成器,也就是Scheduler类的成员变量settings_指向的SchedulerSettings对象的成员变量using_synchronous_renderer_compositor的值等于true,那么是不允许当前BEGIN_IMPL_FRAME操作设置Deadline的,因此这里将Deadline调整为0。
如果状态机表示现在马上渲染网页,也就是调用SchedulerStateMachine类的成员函数ShouldTriggerBeginImplFrameDeadlineEarly得到的返回值等于true,也需要将Deadline调整为0,以便可以马上执行渲染网页的操作。前面我们已经分析过SchedulerStateMachine类的成员函数ShouldTriggerBeginImplFrameDeadlineEarly的实现。
如果状态机表示网页现在需要马上进行渲染,也就是调用SchedulerStateMachine类的成员函数needs_redraw得到的返回值为true,那么需要减小Deadline的值。减小的值刚好就等于一次渲染所需要的时间。这个时间由参数draw_duration_estimate指定。这种情况一般是由动画或者用户输入触发的。
最后,如果没有特殊情况,那么当前BEGIN_IMPL_FRAME操作就是按照前面计算好的Deadline执行。
Scheduler类的成员函数ScheduleBeginImplFrameDeadline的实现如下所示:
void Scheduler::ScheduleBeginImplFrameDeadline(base::TimeTicks deadline) { if (settings_.using_synchronous_renderer_compositor) { // The synchronous renderer compositor has to make its GL calls // within this call. // TODO(brianderson): Have the OutputSurface initiate the deadline tasks // so the sychronous renderer compositor can take advantage of splitting // up the BeginImplFrame and deadline as well. OnBeginImplFrameDeadline(); return; } begin_impl_frame_deadline_task_.Cancel(); begin_impl_frame_deadline_task_.Reset(begin_impl_frame_deadline_closure_); base::TimeDelta delta = deadline - gfx::FrameTime::Now(); if (delta <= base::TimeDelta()) delta = base::TimeDelta(); impl_task_runner_->PostDelayedTask( FROM_HERE, begin_impl_frame_deadline_task_.callback(), delta); }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数ScheduleBeginImplFrameDeadline首先检查网页使用的是否是同步合成器。如果是的话,它就会忽略掉正在执行的BEGIN_IMPL_FRAME操作的Deadline,也就是马上调用Scheduler类的成员函数OnBeginImplFrameDeadline执行一个BEGIN_IMPL_FRAME操作。
如果网页使用的不是同步合成器,那么Scheduler类的成员函数ScheduleBeginImplFrameDeadline就会计算当前时间与参数deadline指定的时间的差值delta,并且向Compositor线程的消息队列发送一个Task,指定该Task在delta时间后执行。向Compositor线程的消息队列发送的Task通过Scheduler类的成员变量begin_impl_frame_deadline_closure_绑定绑定了Scheduler类的成员函数OnBeginImplFrameDeadline。因此,在delta时间后,Scheduler类的成员函数OnBeginImplFrameDeadline就会在Compositor线程中执行。
Scheduler类的成员函数OnBeginImplFrameDeadline执行的BEGIN_IMPL_FRAME操作的第三阶段和第四阶段的工作,如下所示:
void Scheduler::OnBeginImplFrameDeadline() { ...... // We split the deadline actions up into two phases so the state machine // has a chance to trigger actions that should occur durring and after // the deadline separately. For example: // * Sending the BeginMainFrame will not occur after the deadline in // order to wait for more user-input before starting the next commit. // * Creating a new OuputSurface will not occur during the deadline in // order to allow the state machine to "settle" first. state_machine_.OnBeginImplFrameDeadline(); ProcessScheduledActions(); state_machine_.OnBeginImplFrameIdle(); ProcessScheduledActions(); ...... }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
第三阶段和第四阶段的工作都是通过调用Scheduler类的成员函数ProcessScheduledActions完成的。不过,第三阶段的工作是在将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE的情况下执行的,而第四阶段的工作是在将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_IDLE的情况下执行的。
通过调用SchedulerStateMachine类的成员函数OnBeginImplFrameDeadline可以将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME,如下所示:
void SchedulerStateMachine::OnBeginImplFrameDeadline() { DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME) << *AsValue(); begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
通过调用SchedulerStateMachine类的成员函数OnBeginImplFrameIdle可以将状态机的BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_IDLE,如下所示:
void SchedulerStateMachine::OnBeginImplFrameIdle() { DCHECK_EQ(begin_impl_frame_state_, BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE) << *AsValue(); begin_impl_frame_state_ = BEGIN_IMPL_FRAME_STATE_IDLE; }
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE的时候,调度器主要是调度Compositor线程执行图2所示的第6个操作ACTION_DRAW_AND_SWAP_FORCED。但是它也允许执行另外的一些操作,例如BEGIN_MAIN_FRAME操作,即图2所示的第2个操作ACTION_SEND_MAIN_FRAME。我们知道,状态机的BeginImplFrameState状态在等于BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE之前,等于BEGIN_IMPL_FRAME_STATE_INSIDE_BEGIN_FRAME,并且这两个状态之间是存在一个时间差的。如果在这个时间差内,用户产生了一个输入事件,改变了CC Layer Tree的内容。等到状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE的时候,如果允许执行一个BEGIN_MAIN_FRAME操作,那么就能够重新绘制CC Layer Tree的内容,并且提交给Compositor线程渲染,那么就会让用户觉得网页的响应是很快。否则的话,用户的这个输入,至少要等到下一帧才得到响应。
同时,状态机在BEGIN_IMPL_FRAME操作执行完成后,会禁止执行BEGIN_MAIN_FRAME操作。也就是说,在同一帧内,BEGIN_MAIN_FRAME操作只可以在BEGIN_IMPL_FRAME操作之前执行。这样做是出于这样的一个考虑。如果在同一帧内,BEGIN_MAIN_FRAME操作在BEGIN_IMPL_FRAME操作后执行,那么执行的结果要等到接下来的第2个VSync信号到来时才能表现出来。可是,从现在起到接下来的第1个VSync信号到来期间,用户可能会产生输入事件。由于前面已经执行了一个BEGIN_MAIN_FRAME操作,那么上述用户产生的输入事件需要通过另外一个BEGIN_MAIN_FRAME操作才能反映出来。但是,另外这个BEGIN_MAIN_FRAME操作要等到接下来的第3个VSync信号到来时才能表现出来。这样的结果就会让用户觉得网页的响应不够快。如果禁止在同一帧内,BEGIN_MAIN_FRAME操作不能在BEGIN_IMPL_FRAME操作之前执行,那么上述用户产生的输入事件就可以纳入下一个BEGIN_MAIN_FRAME操作中。注意,这个BEGIN_MAIN_FRAME是在接下来的第1个VSync信号到来时执行的,因此它绘制出来的内容在接下来的第2个VSync信号到来时就会得到显示。这种方式就比前面一种方式提前了一个VSync信号,因此能让用户觉得网页的响应很快。
BEGIN_IMPL_FRAME操作执行完成之后,需要将状态机的BeginImplFrameState状态从BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE修改为BEGIN_IMPL_FRAME_STATE_IDLE。这是因为当状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE的时候,有些操作是不允许执行的,例如为网页创建绘图表面的操作,即图2所示的第1个操作ACTION_BEGIN_OUTPUT_SURFACE_CREATION。但是这时候实际上是可以执行这个操作的,主要将BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_IDLE即可。
将BeginImplFrameState状态设置为BEGIN_IMPL_FRAME_STATE_IDLE之后,之所以又要再次调用Scheduler类的成员函数ProcessScheduledActions,就是为了检查是否有诸如ACTION_BEGIN_OUTPUT_SURFACE_CREATION之类的操作需要执行。如果有的话,就可以马上执行它们。
至此,我们就分析完成调度器的实现了。虽然与调度器相关的类只有Scheduler和SchedulerStateMachine两个,并且它们的代码加起来也只有2000行左右,不过它们的实现逻辑还是挺复杂的,理解起来会有一点的困难。这需要我们反复地阅读和消化。另外,这篇文章分析的也仅仅是调度器的实现框架,它内部还会涉及到图2所示的每一个操作都是如何调度执行的。在接下来的一系列文章中,我们就会具体地分析它们。等到全部分析完成后,我们再回过头来看这篇文章,就会容易理解很多。在接下来的一篇文章中,我们就先分析图2所示的第1个操作ACTION_BEGIN_OUTPUT_SURFACE_CREATION的执行过程,即网页绘图表面的创建过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。