在采用线程化渲染方式渲染网页时,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的情况。
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线程绘制网页的下一帧。
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
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。