Chromium网页渲染调度器(Scheduler)
实现分析
在采用线程化渲染方式渲染网页时,Chromium依赖一个调度器协调Main线程和Compositor线程的执行,同时也通过这个调度器决定它们什么时候该执行什么操作。调度器将Main线程和Compositor线程的当前状态记录在一个状态机中,然后通过这个状态机决定下一个要执行的操作。这个操作在满足当前设置条件下是最优的,因此可以使网页渲染更快更流畅。本文接下来就分析Chromium网页调度器的实现。
调度器实现在Chromium的CC模块中,并且运行在Compositor线程中。Compositor线程的职责是将网页的内容渲染出来。从这个角度看,调度器只不过是在调度Compositor线程的执行。不过由于要渲染的网页内容是由Main线程提供给Compositor线程的,因此调度器也会在必要的时候调度Main线程执行,使得它可以提供最新的网页内容给Compositor线程渲染。
网页是一帧一帧地渲染出来的。从前面这个系列的文章我们学习到,Android应用程序UI的每一帧的最佳渲染时机是下一个屏幕VSync信号到来时。Chromium也不例外,它在渲染网页的时候,也是利用了屏幕的VSync信号。这一点在调度器的时间轴中可以得到体现,如图1所示:
从图1可以看到,调度器并没有严格在VSync到来时就去渲染网页的下一帧,而是为网页的下一帧渲染时机设置了一个Deadline。在Deadline到来前,调度器可以调度执行其它的渲染操作。
在继续分析上述的Deadline机制之前,我们要先搞清楚网页的一帧渲染涉及到哪些操作。这些操作如图2所示:
图2的完整分析可以参考前面一文。我们前面说的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状态,如下所示:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片 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所示:
下一个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描述的是网页绘图表面的状态,如下所示:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片 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所示: