网页绘图表面创建完成之后,调度器就会请求绘制CC Layer Tree,这样网页在加载完成之后就能快速显示出来。通过CC Layer Tree可以依次找到Graphics Layer Tree、Render Layer Tree和Render Object Tree。有了Render Object Tree之后,就可以执行具体的绘制工作了。接下来我们就分析网页CC Layer Tree的绘制过程。
CC Layer Tree是在Main线程中进行绘制的。这个绘制操作对应于网页渲染过程中的第2个步骤ACTION_SEND_BEGIN_MAIN_FRAME,如下所示:
图1 CC Layer Tree的绘制时机
Main线程实际上并没有对CC Layer Tree执行真正的绘制,它只是将每一个Layer的绘制命令收集起来。这些绘制命令在对网页分块进行光栅化时才会被执行,也就是在图1所示的第4个操作ACTION_MANAGE_TILES中执行。
CC Layer Tree中的每一个Layer都是按照分块进行绘制的。每一个分块的绘制命令都收集在一个SkPicture中。这个SkPicture就类似于Android应用程序UI硬件加速渲染过程形成的Display List。关于Android应用程序UI硬件加速渲染的详细分析,可以参考前面Android应用程序UI硬件加速渲染技术简要介绍和学习计划一文。
Layer分块并不是简单的区域划分。简单的区域划分在网页缩放过程中会有问题。我们通过图2来说明这个问题,以及针对这个问题的解决方案,如下所示:
图2 Layer分块机制
在图2中,左上角的图显示的是对一个Layer进行的简单的区域划分,也就是将Layer划分为四分块,每一块都紧挨着的,没有发生重叠。当我们对网页进行缩放的时候,是通过对每一个Layer中的每一个分块进行缩放实现的。
问题就出现在分块的边界上,如图2右上角的图所示。图2右上角的图对Layer的四个分块分别执行一个缩小的操作。我们的目的是对整个网页执行一个缩小的操作。缩小后的网页的每一个点都是通过原来网页的若干个相邻点计算出来的。例如,我们要将网页缩小为原来的1/4,那么原网页每相邻的4个点都会通过加权平均合成1个点。但是对网页进行分块之后,分块边界的点就会失去原来与它相邻的但是在另外一个分块的点的信息,这样就会导致这些边界点无法进行正确的缩放计算。
针对上述问题的解决方案是将Layer划分成相互重叠的区域。重叠的区域多大才合适的呢?这与网页的最小缩放因子有关。假设网页最小可以缩小原来的1/16,那么重叠的区域就至少需要15个点。有了这额外的15个字,原来边界上的点就可以找到它所有的相邻的点进行加权平均计算,从而得到正确的缩小后的点。图2下面的三个图显示的就是左上角的Layer的分块情况,原本Layer只需要划分为四个分块,现在被划分为六个分块。为了清楚地看到这个六个分块的重叠情况,我们用了三个图来显示这些分块,每一个图显示其中的两块,并且用不同的颜色标记。
有了上面描述的简单背景知识之后 ,接下来我们结合源码详细分析CC Layer Tree的绘制过程。我们从调度器调度执行ACTION_SEND_BEGIN_MAIN_FRAME操作说起这个绘制过程。
从前面Chromium网页渲染调度器(Scheduler)实现分析一文可以知道,当调度器调用SchedulerStateMachine类的成员函数NextAction询问状态机下一步要执行的操作时,SchedulerStateMachine类的成员函数NextAction会调用另外一个成员函数ShouldSendBeginMainFrame。当SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame返回值等于true的时候,状态机就会提示调度器接下来需要执行ACTION_SEND_BEGIN_MAIN_FRAME操作,也就是对CC Layer Tree进行绘制。
SchedulerStateMachine类的成员函数NextAction的实现如下所示:
SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
......
if (ShouldSendBeginMainFrame())
return ACTION_SEND_BEGIN_MAIN_FRAME;
......
return ACTION_NONE;
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
接下来我们就继续分析SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame什么情况下会返回true,它的实现如下所示:
bool SchedulerStateMachine::ShouldSendBeginMainFrame() const {
if (!needs_commit_)
return false;
// Only send BeginMainFrame when there isn't another commit pending already.
if (commit_state_ != COMMIT_STATE_IDLE)
return false;
// Don't send BeginMainFrame early if we are prioritizing the active tree
// because of smoothness_takes_priority.
if (smoothness_takes_priority_ &&
(has_pending_tree_ || active_tree_needs_first_draw_)) {
return false;
}
// We do not need commits if we are not visible.
if (!visible_)
return false;
// We want to start the first commit after we get a new output surface ASAP.
if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT)
return true;
// We should not send BeginMainFrame while we are in
// BEGIN_IMPL_FRAME_STATE_IDLE since we might have new
// user input arriving soon.
// TODO(brianderson): Allow sending BeginMainFrame while idle when the main
// thread isn't consuming user input.
if (begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_IDLE &&
BeginFrameNeeded())
return false;
// We need a new commit for the forced redraw. This honors the
// single commit per interval because the result will be swapped to screen.
if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT)
return true;
// After this point, we only start a commit once per frame.
if (HasSentBeginMainFrameThisFrame())
return false;
// We shouldn't normally accept commits if there isn't an OutputSurface.
if (!HasInitializedOutputSurface())
return false;
// SwapAck throttle the BeginMainFrames unless we just swapped.
// TODO(brianderson): Remove this restriction to improve throughput.
bool just_swapped_in_deadline =
begin_impl_frame_state_ == BEGIN_IMPL_FRAME_STATE_INSIDE_DEADLINE &&
HasSwappedThisFrame();
if (pending_swaps_ >= max_pending_swaps_ && !just_swapped_in_deadline)
return false;
if (skip_begin_main_frame_to_reduce_latency_)
return false;
return true;
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame返回true需要满足三个必要条件:
1. 网页的CC Layer Tree在当前帧中发生了新的变化。这时候SchedulerStateMachine类的成员变量needs_commit_的值等于true。
2. 网页的CC Layer Tree在上一帧中的变化已经同步到CC Pending Layer Tree去了。这时候状态机的CommitState状态等于COMMIT_STATE_IDLE。
3. 网页当前是可见的。这时候SchedulerStateMachine类的成员变量visible_的值等于true。
如果SchedulerStateMachine类的成员变量smoothness_takes_priority_的值等于true,那么就表示CC Active Layer的渲染优先级比CC Layer Tree的绘制优先级高。在这种情况下,SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame返回true还需要满足第四个必要条件:
4. 上一个CC Pending Layer Tree已经被激活为CC Active Layer Tree,并且这个CC Active Layer Tree在激活后已经至少被渲染过一次了。这时候SchedulerStateMachine类的成员变量has_pending_tree_和active_tree_needs_first_draw_的值均等于false。
当SchedulerStateMachine类的成员变量has_pending_tree_的值等于true时,就表明在上一个CC Pending Layer Tree还未被激活,也就是它的光栅化操作还未完成。这时候如果又去绘制新的CC Layer Tree,那么就会进一步拖延到上一个CC Pending Layer Tree的光栅化操作,从而导致这个CC Pending Layer Tree不能尽快地激活为CC Active Layer Tree进行渲染。这就违反了CC Active Layer的渲染优先级比CC Layer Tree的绘制优先级高的原则。
当SchedulerStateMachine类的成员变量active_tree_needs_first_draw_的值等于true时,就表明上一个CC Pending Layer Tree刚刚激活为CC Active Layer Tree,但是这个CC Active Layer Tree还未被渲染过。这时候如果又去绘制新的CC Layer Tree,那么就会进一步拖延刚刚激活的CC Active Layer Tree的渲染时间。这同样是违反了CC Active Layer的渲染优先级比CC Layer Tree的绘制优先级高的原则。
满足了上述4个必要条件之后,SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame在三种情况下会返回true。
第一种情况是网页的绘图表面刚刚创建和初始化完成。从前面Chromium网页绘图表面(Output Surface)创建过程分析一文可以知道,这时候状态机的OutputSurfaceState状态为OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT。这种情况一般发生在网页加载完成时。这时候最重要的事情就是尽快得到一个CC Active Layer Tree,以便Compositor线程有东西可渲染。由于CC Layer Tree绘制完成后,才可以得到CC Active Layer Tree,因此这种情况就要求马上对CC Layer Tree进行绘制。
第二种情况是状态机的ForcedRedrawOnTimeoutState状态等于FORCED_REDRAW_STATE_WAITING_FOR_COMMIT。从前面Chromium网页渲染调度器(Scheduler)实现分析一文可以知道,这时候Compositor线程正在等待Main线程将CC Layer Tree的变化同步到新的CC Pending Layer Tree中去,以便可以执行下一帧的渲染操作。因此这时候就要求马上对CC Layer Tree进行绘制。
在第二种情况中,如果状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_IDLE,那么还需要满足一个额外条件,SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame的返回值才会等于true。这个额外条件就是当前不是处于动画过程中,并且当前的CC Active Layer Tree也不要求重新渲染。如果能满足这个额外条件,那么从前面Chromium网页渲染调度器(Scheduler)实现分析一文可以知道,这时候调用SchedulerStateMachine类的成员函数BeginFrameNeeded得到的返回值就会等于false。这一点应该怎么理解呢?
调度器原则上不会在状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_IDLE时,请求Main线程对CC Layer Tree进行绘制。从前面Chromium网页渲染调度器(Scheduler)实现分析一文可以知道,从上一个BEGIN_IMPL_FRAME操作执行完成至下一个VSync信号到来之前的这段时间里,状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_IDLE。这时候如果去绘制CC Layer Tree,那么就会导致这段时间里的用户输入要等到下一次绘制CC Layer Tree时才会得到反应,也就是再下一个VSync信号到来时才会得到反应。这会让用户觉得网页对用户的输入响应得不够快。相反,如果将CC Layer Tree的绘制延迟到下一个VSync信号到来时再执行,那么在上述时间段内发生的用户输入,就可以在接下来的CC Layer Tree得到反应,也就是在下一个VSync信号到来得到反应,而不必等待再一个VSync信号的到来。
不过调度器有时候也会放宽要求,允许在状态机的BeginImplFrameState状态等于BEGIN_IMPL_FRAME_STATE_IDLE时,让Main线程对CC Layer Tree进行绘制,前提是当前不是处于动画过程中,并且当前的CC Active Layer Tree也不要求重新渲染。网页在显示动画时,往往用户也正在输入。典型的情景就是用户正在滑动网页,这时候Chromium需要根据用户的输入滚动网页,也就是执行网页的滚动动画。反过来,如果网页当前不需要显示动画,那么很大程度上也表明当前没有用户输入。这时候马上对CC Layer Tree进行绘制,既可以提前对CC Layer Tree进行绘制,又不会引起上述的用户输入响应问题。
第三种情况需要满足以下四个条件:
1. 在当前帧时间里,调度器还没有执行过ACTION_SEND_BEGIN_MAIN_FRAME操作。这时候调用SchedulerStateMachine类的成员函数HasSentBeginMainFrameThisFrame得到的返回值等于false。这个条件表明每一个VSync周期只允许执行一次ACTION_SEND_BEGIN_MAIN_FRAME操作,也就是一个VSync周期只允许绘制一次CC Layer Tree。
3. 未完成的swapBuffers操作的个数小于预设的阀值。这个预设的阀值保存在SchedulerStateMachine类的成员变量max_pending_swaps_中。同时,未完成的swapBuffers操作的个数记录在SchedulerStateMachine类的另外一个成员变量pending_swaps_中。Compositor线程每次渲染完成CC Active Layer Tree之后,都会执行一个swapBuffers操作,并且将SchedulerStateMachine类的成员变量pending_swaps_的值增加1。这个操作就是请求Browser进程对已经渲染好的网页内容进行合成。当Browser进程合成好网页的内容后,它就会通知Render进程将SchedulerStateMachine类的成员变量pending_swaps_的值减少1。当未完成的swapBuffers操作超过预设阀值时,就说明Browser进程太忙了,跟不上Render进程渲染网页的速度,因此这时候就要求Render进程慢下来。在这里就表现为暂停绘制CC Layer Tree。
4. Main线程当前不是处于高延时模式。这时候SchedulerStateMachine类的成员变量skip_begin_main_frame_to_reduce_latency_等于false。当Main线程当前处于高延时模式时,要降低Main线程绘制CC Layer Tree的频率,否则延时就会进一步加大。关于Main线程的高延时模式判断,可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文。
其中,第3个条件又可以进一步放宽,就是即使未完成的swapBuffers操作的个数大于等于预设的阀值,SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame也可以返回true。前提条件是此时有一个之前提交的swapBuffers操作在当前的BEGIN_IMPL_FRAME操作执行期间刚刚执行完成。这时候实际上是需要将SchedulerStateMachine类的成员变量pending_swaps_的值减少1的,但是还没有来得及减,因此就允许Compositor再提交一个新的swapBuffers操作,以抵消刚刚完成的swapBuffers操作。提交新的swapBuffers操作就意味要先绘制新的CC Layer Tree,因此这时候就允许SchedulerStateMachine类的成员函数ShouldSendBeginMainFrame返回true。
回到SchedulerStateMachine类的成员函数NextAction中,当它调用成员函数ShouldSendBeginMainFrame得到的返回值等于true,它就会返回一个ACTION_SEND_BEGIN_MAIN_FRAME给Scheduler类的成员函数ProcessScheduledActions。这时候Scheduler类的成员函数ProcessScheduledActions就会请求Main线程对CC Layer Tree进行绘制,如下所示:
void Scheduler::ProcessScheduledActions() {
......
SchedulerStateMachine::Action action;
do {
action = state_machine_.NextAction();
......
state_machine_.UpdateState(action);
......
switch (action) {
......
case SchedulerStateMachine::ACTION_SEND_BEGIN_MAIN_FRAME:
client_->ScheduledActionSendBeginMainFrame();
break;
......
}
} while (action != SchedulerStateMachine::ACTION_NONE);
SetupNextBeginFrameIfNeeded();
......
if (state_machine_.ShouldTriggerBeginImplFrameDeadlineEarly()) {
......
ScheduleBeginImplFrameDeadline(base::TimeTicks());
}
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数ProcessScheduledActions的详细分析可以参考前面Chromium网页渲染调度器(Scheduler)实现分析一文。这时候Scheduler类的成员函数ProcessScheduledActions首先调用SchedulerStateMachine类的成员函数UpdateState更新状态机的状态,接着再调用成员变量client_指向的一个ThreadProxy对象的成员函数ScheduledActionSendBeginMainFrame请求Main线程绘制CC Layer Tree。
SchedulerStateMachine类的成员函数UpdateState的实现如下所示:
void SchedulerStateMachine::UpdateState(Action action) {
switch (action) {
......
case ACTION_SEND_BEGIN_MAIN_FRAME:
......
commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_SENT;
needs_commit_ = false;
last_frame_number_begin_main_frame_sent_ =
current_frame_number_;
return;
......
}
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数UpdateState这时候做三件事情:
1. 将状态机的CommitState状态设置为COMMIT_STATE_BEGIN_MAIN_FRAME_SENT,表示调度器正在请求Main线程绘制CC Layer Tree。等到Main线程绘制完成CC Layer Tree之后,就需要将它同步到CC Pending Layer Tree去。
2. 将成员变量needs_commit_的值设置为false,表示此前CC Layer Tree发生的变化已经得到处理。在此之后如果CC Layer Tree又再发生了变化,成员变量needs_commit_的值才会再次设置为true。
3. 将成员变量last_frame_number_begin_main_frame_sent_的值设置为成员变量current_framenumber,表示当前绘制的CC Layer Tree是属于哪一帧。以后通过这两个成员变量就可以判断上一次执行的BEGIN_MAIN_FRAME操作是否在当前帧内。
ThreadProxy类的成员函数ScheduledActionSendBeginMainFrame的实现如下所示:
void ThreadProxy::ScheduledActionSendBeginMainFrame() {
......
scoped_ptr<BeginMainFrameAndCommitState> begin_main_frame_state(
new BeginMainFrameAndCommitState);
......
Proxy::MainThreadTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ThreadProxy::BeginMainFrame,
main_thread_weak_ptr_,
base::Passed(&begin_main_frame_state)));
......
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类的成员函数ScheduledActionSendBeginMainFrame向Main线程的消息队列发送一个Task,这个Task绑定的函数是ThreadProxy类的成员函数BeginMainFrame。因此,接下来ThreadProxy类的成员函数BeginMainFrame会在Main线程中执行,执行过程如下所示:
void ThreadProxy::BeginMainFrame(
scoped_ptr<BeginMainFrameAndCommitState> begin_main_frame_state) {
......
if (!layer_tree_host()->visible()) {
......
bool did_handle = false;
Proxy::ImplThreadTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ThreadProxy::BeginMainFrameAbortedOnImplThread,
impl_thread_weak_ptr_,
did_handle));
return;
}
if (layer_tree_host()->output_surface_lost()) {
......
bool did_handle = false;
Proxy::ImplThreadTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ThreadProxy::BeginMainFrameAbortedOnImplThread,
impl_thread_weak_ptr_,
did_handle));
return;
}
......
layer_tree_host()->AnimateLayers(
begin_main_frame_state->monotonic_frame_begin_time);
......
if (begin_main_frame_state->evicted_ui_resources)
layer_tree_host()->RecreateUIResources();
layer_tree_host()->Layout();
......
bool can_cancel_this_commit =
main().can_cancel_commit && !begin_main_frame_state->evicted_ui_resources;
......
scoped_ptr<resourceupdatequeue> queue =
make_scoped_ptr(new ResourceUpdateQueue);
bool updated = layer_tree_host()->UpdateLayers(queue.get());
......
if (!updated && can_cancel_this_commit) {
......
bool did_handle = true;
Proxy::ImplThreadTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ThreadProxy::BeginMainFrameAbortedOnImplThread,
impl_thread_weak_ptr_,
did_handle));
.......
return;
}
{
......
CompletionEvent completion;
Proxy::ImplThreadTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ThreadProxy::StartCommitOnImplThread,
impl_thread_weak_ptr_,
&completion,
queue.release()));
completion.Wait();
......
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类在内部保存了一个LayerTreeHost对象,通过调用成员函数layer_tree_host可以获得这个LayerTreeHost对象。这个LayerTreeHost对象就是用来管理网页的CC Layer Tree的,因此调用LayerTreeHost类的成员函数可以对CC Layer Tree执行相应的操作。
ThreadProxy类的成员函数BeginMainFrame主要是做三件事情:
1. 计算CC Layer Tree的动画。这是通过调用LayerTreeHost类的成员函数AnimateLayers实现的。
2. 计算CC Layer Tree的布局。这是通过调用LayerTreeHost类的成员函数Layout实现的。
3. 绘制CC Layer Tree。这是通过调用LayerTreeHost类的成员函数UpdateLayers实现的。
不过,在做这三件事情之前,ThreadProxy类的成员函数BeginMainFrame会先检查网页当前是否可见的,以及它的绘图表面是否是有效的。如果网页当前不可见,或者网页的绘图表面已经失效,那么ThreadProxy类的成员函数BeginMainFrame就不会做无用功了。它直接向Compositor线程的消息队列发送一个Task,这个Task绑定了ThreadProxy类的成员函数BeginMainFrameAbortedOnImplThread。当ThreadProxy类的成员函数BeginMainFrameAbortedOnImplThread被调用的时候,就会告诉调度器,它之前调度的ACTION_SEND_BEGIN_MAIN_FRAME操作还没有执行就被取消了。
第三件事情执行完成后,LayerTreeHost类的成员函数UpdateLayers的返回值表示CC Layer Tree是否有发生变化。如果CC Layer Tree没有发生变化,并且接下来允许不执行ACTION_COMMIT操作,那么ThreadProxy类的成员函数BeginMainFrame就不会请求Compositor线程将CC Layer Tree的内容同步到一个新的CC Pending Layer Tree中去,它会直接向Compositor线程的消息队列发送一个Task,这个Task也是绑定了ThreadProxy类的成员函数BeginMainFrameAbortedOnImplThread。当ThreadProxy类的成员函数BeginMainFrameAbortedOnImplThread被调用的时候,就会告诉调度器,它之前调度的ACTION_SEND_BEGIN_MAIN_FRAME操作已经执行但是接下来的ACTION_COMMIT操作被取消了。
一般来说,ACTION_COMMIT操作是紧跟在ACTION_SEND_BEGIN_MAIN_FRAME操作后面执行的。但是当ACTION_SEND_BEGIN_MAIN_FRAME操作不是由CC Layer Tree的变化触发的时候,就可能会允许不在ACTION_SEND_BEGIN_MAIN_FRAME操作之后执行一个ACTION_COMMIT操作。这里说可能,是因为还需要满足另外一个条件,才真的允许不在ACTION_SEND_BEGIN_MAIN_FRAME操作之后执行一个ACTION_COMMIT操作。这个条件就是网页正在使用的资源没有被回收。当网页从可见变为不可见的时候(用户切换Tab的时候就会发生这种情况),网页正在使用的资源就会被回收。等到网页重新变为可见的时候,之前回收的资源需要重新创建。这些资源的重建工作是在计算CC Layer Tree的布局之前做的,也就是通过调用LayerTreeHost类的成员函数RecreateUIResources完成的。这些资源重新创建之后,需要通过一个ACTION_COMMIT操作才能渲染出来。因此,在这种情况下,就要求在ACTION_SEND_BEGIN_MAIN_FRAME操作之后紧接着执行一个ACTION_COMMIT操作。
调用ThreadProxy类的成员函数main可以获得一个MainThreadOnly对象。当这个MainThreadOnly对象的成员变量can_cancel_commit等于false的时候,就表示ACTION_SEND_BEGIN_MAIN_FRAME操作是由CC Layer Tree的变化触发的,因此这时候不允许不在ACTION_SEND_BEGIN_MAIN_FRAME操作之后执行一个ACTION_COMMIT操作。此外,当参数begin_main_frame_state指向的BeginMainFrameAndCommitState对象的成员变量evicted_ui_resources的值等于true的时候,就表示网页正在使用的资源被回收了,因此这时候也不允许不在ACTION_SEND_BEGIN_MAIN_FRAME操作之后执行一个ACTION_COMMIT操作。
如果LayerTreeHost类的成员函数UpdateLayers的返回值表示CC Layer Tree发生了变化,或者没有发生变化,但是要求接下来执行一个ACTION_COMMIT操作,那么ThreadProxy类的成员函数BeginMainFrame接下来就会向Compositor线程的消息队列发送一个Task,这个Task绑定了ThreadProxy类的成员函数StartCommitOnImplThread。因此,接下来ThreadProxy类的成员函数StartCommitOnImplThread就会在Compositor线程中执行。
ThreadProxy类的成员函数StartCommitOnImplThread主要是做两件事情。第一件事情是处理网页中的图片资源,也就是将它们作为纹理上传到GPU中去,以便后面可以进行渲染。这些需要当作纹理上传到GPU去的图片资源是在绘制CC Layer Tree的时候收集的。收集后会保存在一个队列中,然后这个队列会传递给ThreadProxy类的成员函数StartCommitOnImplThread处理。第二件事情是将CC Layer Tree同步到一个新的CC Pending Layer Tree中去。本文只分析第一件事情,第二件事情在接下来一篇文章再详细分析。
注意,ThreadProxy类的成员函数BeginMainFrame是在Main线程执行的,当它请求Compositor线程执行ThreadProxy类的成员函数StartCommitOnImplThread时,Main线程会通过一个CompletionEvent对象进入等待状态。等到ThreadProxy类的成员函数StartCommitOnImplThread将CC Layer Tree同步到一个新的CC Pending Layer Tree去之后,Compositor线程才会通过上述CompletionEvent对象唤醒Main线程。在网页的渲染过程中,就只有这一个环节需要Main线程和Compositor线程串行执行。在其它时候,Main线程和Compositor线程都是可以并行执行的。
接下来我们首先分析ThreadProxy类的成员函数BeginMainFrameAbortedOnImplThread的实现,接着再分析LayerTreeHost类的成员函数AnimateLayers、Layout和UpdateLayers的实现,最后分析ThreadProxy类的成员函数StartCommitOnImplThread的实现。
ThreadProxy类的成员函数BeginMainFrameAbortedOnImplThread的实现如下所示:
void ThreadProxy::BeginMainFrameAbortedOnImplThread(bool did_handle) {
......
impl().scheduler->BeginMainFrameAborted(did_handle);
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类的成员函数BeginMainFrameAbortedOnImplThread调用Scheduler类的成员函数BeginMainFrameAborted通知调度器它之前请求执行的ACTION_SEND_BEGIN_MAIN_FRAME操作被取消了。
Scheduler类的成员函数BeginMainFrameAborted的实现如下所示:
void Scheduler::BeginMainFrameAborted(bool did_handle) {
......
state_machine_.BeginMainFrameAborted(did_handle);
ProcessScheduledActions();
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数BeginMainFrameAborted会调用SchedulerStateMachine类的成员函数BeginMainFrameAborted修改状态机的状态。由于状态机的状态发生变化之后,有可能需要触发新的操作,因此Scheduler类的成员函数BeginMainFrameAborted就会调用另外一个成员函数ProcessScheduledActions进行检查是否需要触发新的操作。
SchedulerStateMachine类的成员函数BeginMainFrameAborted的实现如下所示:
void SchedulerStateMachine::BeginMainFrameAborted(bool did_handle) {
DCHECK_EQ(commit_state_, COMMIT_STATE_BEGIN_MAIN_FRAME_SENT);
if (did_handle) {
bool commit_was_aborted = true;
UpdateStateOnCommit(commit_was_aborted);
} else {
commit_state_ = COMMIT_STATE_IDLE;
SetNeedsCommit();
}
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数BeginMainFrameAborted主要是修改状态机的CommitState状态。
从前面的分析可以知道,当参数did_handle的值等于false的时候,就表示之前调度的ACTION_SEND_BEGIN_MAIN_FRAME操作还没开始执行就被取消了。在这种情况下,SchedulerStateMachine类的成员函数BeginMainFrameAborted将状态机的CommitState状态恢复为COMMIT_STATE_IDLE,并且调用另外一个成员函数SetNeedsCommit将成员变量needs_commit_的值设置为true。这样就可以在下一个VSync信号到来时,重新执行一个ACTION_SEND_BEGIN_MAIN_FRAME操作。
为什么要在下一个VSync信号到来时,重新执行一个ACTION_SEND_BEGIN_MAIN_FRAME操作呢?从前面的分析可以知道,参数did_handle的值等于false时,有可能是因为网页从可见变为不可见引起的。在下一个VSync信号到来时,有可能网页会从不可见变为可见,因此这时候就需要重新执行一个ACTION_SEND_BEGIN_MAIN_FRAME操作,以便处理CC Layer Tree之前发生的变化。
当参数did_handle的值等于true的时候,就表示不必在ACTION_SEND_BEGIN_MAIN_FRAME操作之后执行一个ACTION_COMMIIT操作,这时候SchedulerStateMachine类的成员函数BeginMainFrameAborted就会调用另外一个成员函数UpdateStateOnCommit修改状态机的状态,并且传递参数true给它。
注意,状态机在调度执行ACTION_COMMIIT操作的时候,也会调用SchedulerStateMachine类的成员函数UpdateStateOnCommit修改状态机的状态,不过这时候传递的参数为false。这一点我们在接下来一篇文章分析CC Layer Tree和CC Pending Layer Tree的同步过程时就会看到。
SchedulerStateMachine类的成员函数UpdateStateOnCommit的实现如下所示:
void SchedulerStateMachine::UpdateStateOnCommit(bool commit_was_aborted) {
commit_count_++;
if (commit_was_aborted || settings_.main_frame_before_activation_enabled) {
commit_state_ = COMMIT_STATE_IDLE;
} else if (settings_.main_frame_before_draw_enabled) {
commit_state_ = settings_.impl_side_painting
? COMMIT_STATE_WAITING_FOR_ACTIVATION
: COMMIT_STATE_IDLE;
} else {
commit_state_ = COMMIT_STATE_WAITING_FOR_FIRST_DRAW;
}
// If we are impl-side-painting but the commit was aborted, then we behave
// mostly as if we are not impl-side-painting since there is no pending tree.
has_pending_tree_ = settings_.impl_side_painting && !commit_was_aborted;
// Update state related to forced draws.
if (forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_COMMIT) {
forced_redraw_state_ = has_pending_tree_
? FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION
: FORCED_REDRAW_STATE_WAITING_FOR_DRAW;
}
// Update the output surface state.
DCHECK_NE(output_surface_state_, OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION);
if (output_surface_state_ == OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT) {
if (has_pending_tree_) {
output_surface_state_ = OUTPUT_SURFACE_WAITING_FOR_FIRST_ACTIVATION;
} else {
output_surface_state_ = OUTPUT_SURFACE_ACTIVE;
needs_redraw_ = true;
}
}
// Update state if we have a new active tree to draw, or if the active tree
// was unchanged but we need to do a forced draw.
if (!has_pending_tree_ &&
(!commit_was_aborted ||
forced_redraw_state_ == FORCED_REDRAW_STATE_WAITING_FOR_DRAW)) {
needs_redraw_ = true;
active_tree_needs_first_draw_ = true;
}
// This post-commit work is common to both completed and aborted commits.
pending_tree_is_ready_for_activation_ = false;
if (continuous_painting_)
needs_commit_ = true;
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数UpdateStateOnCommit不单只会修改状态机的CommitState状态,还可能会修改ForcedRedrawOnTimeoutState状态和OutputSurfaceState状态,以及其它的状态。
我们首先看CommitState状态的修改逻辑:
1. 如果参数commit_was_aborted等于true,也就是接下来不需要执行一个ACTION_COMMIT操作,这时候状态机的CommitState状态就会被恢复为COMMIT_STATE_IDLE。注意,在此之前,CommitState状态为COMMIT_STATE_BEGIN_MAIN_FRAME_SENT。另外,如果SchedulerStateMachine类的成员变量settings_描述的一个SchedulerSettings对象的成员变量main_frame_before_activation_enabled等于true,状态机的CommitState状态就会被恢复为COMMIT_STATE_IDLE。这种情况表示允许在下一个CC Pending Layer Tree激活为CC Active Layer Tree之前,执行另外一个BEGIN_MAIN_FRAME操作。由于接下来不会执行ACTION_COMMIT操作,也就是不会有新的CC Pending Layer Tree,于是就不存在CC Pending Layer激活的问题,因此就可以将状态机的CommitState状态恢复为COMMIT_STATE_IDLE。
2. 如果不执行第1点所示的逻辑,并且SchedulerStateMachine类的成员变量settings_描述的一个SchedulerSettings对象的成员变量main_frame_before_draw_enabled的值等于true(这表示允许在新激活的CC Active Layer Tree被渲染之前,执行另外一个BEGIN_MAIN_FRAME操作),那么状态机的CommitState状态可能会被设置为COMMIT_STATE_IDLE。为什么只是可能呢?这是因为如果网页的分块还没有被光栅化,那么就要等到网页的分块光栅化完成之后,才会得到一个激活的CC Active Layer Tree。也就是现在还轮不到执行Active Layer Tree的渲染操作。在这种情况下,是禁止执行另外一个BEGIN_MAIN_FRAME操作的,也就是不能将状态机的CommitState状态设置为COMMIT_STATE_IDLE。另一方面,如果网页的分块已经被光栅化,那么接下来的操作就是对Active Layer Tree进行渲染了,因此这时候就可以执行另外一个BEGIN_MAIN_FRAME操作,也就是可以将状态机的CommitState状态设置为COMMIT_STATE_IDLE。注意,现在刚刚执行完成的工作是在Main线程中绘制CC Layer Tree。当SchedulerStateMachine类的成员变量settings_描述的一个SchedulerSettings对象的成员变量impl_side_painting的值等于false的时候,表示网页分块的光栅化操作在Main线程绘制CC Layer Tree的时候就顺带完成了,因此这种情况就可以将状态机的CommitState状态设置为COMMIT_STATE_IDLE,否则的话,要将状态机的CommitState状态设置为COMMIT_STATE_WAITING_FOR_ACTIVATION。
3. 如果不执行第1点和第2点的逻辑,那么这时候状态机的CommitState状态就会被设置为COMMIT_STATE_WAITING_FOR_FIRST_DRAW。这样设置有两个含义。一个含义表示接下来不能执行另外一个BEGIN_MAIN_FRAME操作,另一个含义表示状态机正在等待CC Active Layer Tree被激活后的第一次渲染。
我们接下来看ForcedRedrawOnTimeoutState状态的的修改逻辑。注意,在当前ForcedRedrawOnTimeoutState状态等于FORCED_REDRAW_STATE_WAITING_FOR_COMMIT的时候,才需要对它进行修改。这是因为当ForcedRedrawOnTimeoutState状态等于FORCED_REDRAW_STATE_WAITING_FOR_COMMIT时,表示状态机正在等待调度器调度一个ACTION_COMMIT操作。既然现在这个ACTION_COMMIT已经被调度了,因此就可以迁移状态机的ForcedRedrawOnTimeoutState状态了:
1. 如果前一个CC Pending Layer Tree还未完成光栅化操作,那么状态机的ForcedRedrawOnTimeoutState状态被设置为FORCED_REDRAW_STATE_WAITING_FOR_ACTIVATION,表示状态机正在等这个CC Pending Layer Tree完成光栅化操作。
2. 如果前一个CC Pending Layer Tree已经完成光栅化操作,也就是它已经激活为一个新的CC Active Layer Tree,那么状态机的ForcedRedrawOnTimeoutState状态被设置为FORCED_REDRAW_STATE_WAITING_FOR_DRAW,表示状态机正在等待渲染新激活的CC Active Layer Tree。
我们接下来继续看OutputSurfaceState状态的修改逻辑。注意,在当前OutputSurfaceState状态等于OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT的时修,才需要对它进行修改。这是因为当OutputSurfaceState状态等于OUTPUT_SURFACE_WAITING_FOR_FIRST_COMMIT时,表示状态机正在等待调度器调度一个ACTION_COMMIT操作。既然现在这个ACTION_COMMIT已经被调度了,因此就可以迁移状态机的OutputSurfaceState状态了:
2. 如果前一个CC Pending Layer Tree已经完成光栅化操作,也就是它已经激活为一个新的CC Active Layer Tree,那么状态机的OutputSurfaceState状态被设置为OUTPUT_SURFACE_ACTIVE,表示状态机正在等待渲染新激活的CC Active Layer Tree。
除了修改状态机的CommitState状态,还可能会修改ForcedRedrawOnTimeoutState状态和OutputSurfaceState状态,SchedulerStateMachine类的成员函数UpdateStateOnCommit还会做以下四件事情:
1. 检查当前是否存在一个新的CC Pending Layer Tree。如果存在,就会将成员变量has_pending_tree_的值设置为true。当SchedulerStateMachine类的成员变量settings_描述的一个SchedulerSettings对象的成员变量impl_side_painting的值等于true的时候,就表示接下来可能会产生一个新的CC Pending Layer Tree。但是也有可能产生,因为接下来可能不会执行ACTION_COMMIT操作,取决于参数commit_was_aborted的值。当参数commit_was_aborted的值等于false的时候,就表示接下来会执行一个ACTION_COMMIT操作,因此接下来就一定会产生一个新的CC Pending Layer Tree。
2. 检查当前是否存在一个新的CC Active Layer Tree。如果存在,就会将成员变量active_tree_needs_first_draw_的值设置为true,表示这个新的CC Active Layer Tree需要执行一次渲染。存在一个新激活的CC Active Layer Tree需要满足一个必要条件,就是上一个CC Pending Layer Tree已经完成光栅化操作,也就是这时候成员变量has_pending_tree_的值会等于false。满足这个条件的时候,新激活的CC Active Layer Tree的第一次渲染也许已经执行过了。如果这时候状态机的ForcedRedrawOnTimeoutState状态为FORCED_REDRAW_STATE_WAITING_FOR_DRAW,那么就一定说明新激活的CC Active Layer Tree还没有被渲染过。否则的话,状态机的ForcedRedrawOnTimeoutState状态是不会等于FORCED_REDRAW_STATE_WAITING_FOR_DRAW的。另外,如果这时候参数commit_was_aborted的值等于false,那么就表示虽然上一个CC Pending Layer Tree已经完成光栅化操作,但是接下来很快又会有一个新的CC Pending Layer Tree等待光栅化。当这个新的CC Pending Layer Tree光栅化操作执行完成后,又会得到一个新激活的CC Active Layer Tree。因此这时候也需要将成员变量active_tree_needs_first_draw_的值设置为true,表示接下来很快就会有一个新的CC Active Layer Tree等待第一次渲染。
3. 将成员变量pending_tree_is_ready_for_activation_的值设置为false,表示下一个CC Pending Layer Tree还没有光栅化完成,也就是它不可以激活为CC Active Layer Tree。当前只存在两种情况。一种情况是参数commit_was_aborted的值等于true,表示之前调度的ACTION_SEND_BEGIN_MAIN_FRAME操作还没开始执行就被取消了。这时候肯定不会产生新的CC Pending Layer Tree,因此无从谈起激活它了。另一种情况是参数commit_was_aborted的值等于false,表示接下来马上就要产生一个新的CC Pending Layer Tree。新产生的CC Pending Layer Tree肯定是还没有完成光栅化的,因此就不能被激活。
4. 在成员变量continuous_painting_的值等于true的情况下,将另外一个成员变量needs_commit_的值也设置为true,表示不管CC Layer Tree有没有发生新的变化,在下一个VSync信号到来时,都执行一个ACTION_SEND_BEGIN_MAIN_FRAME操作。
接下来我们分析LayerTreeHost类的成员函数AnimateLayers的实现,以便了解CC Layer Tree的动画计算过程。
LayerTreeHost类的成员函数AnimateLayers的实现如下所示:
void LayerTreeHost::AnimateLayers(base::TimeTicks monotonic_time) {
if (!settings_.accelerated_animation_enabled ||
animation_registrar_->active_animation_controllers().empty())
return;
......
AnimationRegistrar::AnimationControllerMap copy =
animation_registrar_->active_animation_controllers();
for (AnimationRegistrar::AnimationControllerMap::iterator iter = copy.begin();
iter != copy.end();
++iter) {
(*iter).second->Animate(monotonic_time);
......
}
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。
LayerTreeHost类的成员变量animation_registrar_指向的是一个AnimationRegistrar对象。这个AnimationRegistrar负责管理CC Layer Tree中的动画。调用这个AnimationRegistrar对象的成员函数active_animation_controllers可以获得CC Layer Tree当前激活的动画控制器,如下所示:
class CC_EXPORT AnimationRegistrar {
public:
typedef base::hash_map<int, LayerAnimationController*> AnimationControllerMap;
......
const AnimationControllerMap& active_animation_controllers() const {
return active_animation_controllers_;
}
......
private:
......
AnimationControllerMap active_animation_controllers_;
AnimationControllerMap all_animation_controllers_;
......
};
这个函数定义在文件external/chromium_org/cc/animation/animation_registrar.h中。
动画控制器通过类LayerAnimationController描述。CC Layer Tree中的每一个Layer都对应有一个动画控制器。这些动画控制器注册在AnimationRegistrar类的成员变量all_animation_controllers_描述的是一个map中,其中键值为对应的Layer的ID。
当一个Layer有动画需要显示时,它注册在AnimationRegistrar类的动画控制器就会再被保存到另外一个成员变量active_animation_controllers_描述的一个map中,这样通过这个成员变量就知道一个Layer当前有哪些动画是需要执行的。
回到LayerTreeHost类的成员函数AnimateLayers中,它调用当前激活的每一个动画控制器的成员函数Animate执行CC Layer Tree中的动画,如下所示:
void LayerAnimationController::Animate(base::TimeTicks monotonic_time) {
......
TickAnimations(monotonic_time);
......
}
这个函数定义在文件external/chromium_org/cc/animation/layer_animation_controller.cc中。
LayerAnimationController类的成员函数Animate主要是调用另外一个成员函数TickAnimations执行注册在它里面的动画,如下所示:
void LayerAnimationController::TickAnimations(base::TimeTicks monotonic_time) {
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->run_state() == Animation::Starting ||
animations_[i]->run_state() == Animation::Running ||
animations_[i]->run_state() == Animation::Paused) {
double trimmed =
animations_[i]->TrimTimeToCurrentIteration(monotonic_time);
switch (animations_[i]->target_property()) {
case Animation::Transform: {
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
......
break;
}
case Animation::Opacity: {
const FloatAnimationCurve* float_animation_curve =
animations_[i]->curve()->ToFloatAnimationCurve();
......
break;
}
case Animation::Filter: {
const FilterAnimationCurve* filter_animation_curve =
animations_[i]->curve()->ToFilterAnimationCurve();
......
break;
}
case Animation::BackgroundColor: {
// Not yet implemented.
break;
}
case Animation::ScrollOffset: {
const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
animations_[i]->curve()->ToScrollOffsetAnimationCurve();
......
break;
}
// Do nothing for sentinel value.
case Animation::TargetPropertyEnumSize:
NOTREACHED();
}
}
}
}
这个函数定义在文件external/chromium_org/cc/animation/layer_animation_controller.cc中。
注册在LayerAnimationController类中的动画保存在其成员变量animations_描述的一个Vector中,每一个动画都是通过一个Animation对象描述。不同类型的动画有不同的执行方式。例如,对于类型Animation::Transform的动画来说,主要是通过调用它内部的一个AnimationCurve对象的成员函数ToTransformAnimationCurve来执行的。
这些动画是从WebKit里面注册到LayerAnimationController类的,接下来我们就分析动画的注册过程。
当网页的DOM Tree中的某一个Element需要创建动画时,WebKit就会调用WebKit层的Animation类的静态成员函数create为其创建一个动画,如下所示:
PassRefPtrWillBeRawPtr<Animation> Animation::create(Element* target, PassRefPtrWillBeRawPtr<AnimationEffect> effect, const Timing& timing, Priority priority, PassOwnPtr<EventDelegate> eventDelegate)
{
return adoptRefWillBeNoop(new Animation(target, effect, timing, priority, eventDelegate));
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/animation/Animation.cpp中。
其中,参数target描述的就是要执行动画的Element。这个动画通过WebKit层的一个Animation对象描述。
WebKit在更新网页的Graphics Layer Tree的时候,就会将DOM Tree中的动画注册到CC模块中去。从前面Chromium网页Graphics Layer Tree创建过程分析一文可以知道,网页的Graphics Layer Tree是在RenderLayerCompositor类的成员函数updateIfNeededRecursive中更新的,如下所示:
void RenderLayerCompositor::updateIfNeededRecursive()
{
......
updateIfNeeded();
......
DocumentAnimations::startPendingAnimations(m_renderView.document());
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/RenderLayerCompositor.cpp中。
RenderLayerCompositor类的成员函数updateIfNeededRecursive首先调用成员函数updateIfNeeded更新网页的Graphics Layer Tree,接着又会调用DocumentAnimations类的静态成员函数startPendingAnimations执行网页的DOM Tree中的动画。
DocumentAnimations类的静态成员函数startPendingAnimations的实现如下所示:
void DocumentAnimations::startPendingAnimations(Document& document)
{
ASSERT(document.lifecycle().state() == DocumentLifecycle::CompositingClean);
if (document.compositorPendingAnimations().startPendingAnimations()) {
ASSERT(document.view());
document.view()->scheduleAnimation();
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/animation/DocumentAnimations.cpp中。
参数document描述的是网页的文档对象,调用这个文档对象的成员函数compositorPendingAnimations可以获得一个CompositorPendingAnimations对象,有了这个CompositorPendingAnimations对象之后,就可以调用它的成员函数startPendingAnimations检查是否有动画需要执行。一旦需要,CompositorPendingAnimations类的成员函数startPendingAnimations的返回值就等于true,这时候DocumentAnimations类的静态成员函数startPendingAnimations就会调用另外一个成员函数scheduleAnimation调度执行这些动画。
接下来我们继续分析CompositorPendingAnimations类的成员函数startPendingAnimations的实现,以便了解WebKit层的动画是如何注册到CC模块去的,如下所示:
bool CompositorPendingAnimations::startPendingAnimations()
{
bool startedSynchronizedOnCompositor = false;
for (size_t i = 0; i < m_pending.size(); ++i) {
if (!m_pending[i]->hasActiveAnimationsOnCompositor() && m_pending[i]->maybeStartAnimationOnCompositor() && !m_pending[i]->hasStartTime())
startedSynchronizedOnCompositor = true;
}
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/animation/CompositorPendingAnimations.cpp中。
网页的DOM Tree中的动画注册在CompositorPendingAnimations类的成员变量m_pending描述的一个AnimationPlayer向量中,CompositorPendingAnimations类的成员函数startPendingAnimations依次调用这些AnimationPlayer对象的成员函数maybeStartAnimationOnCompositor检查它们是否有动画需要执行。
AnimationPlayer类的成员函数maybeStartAnimationOnCompositor的实现如下所示:
bool AnimationPlayer::maybeStartAnimationOnCompositor()
{
......
return toAnimation(m_content.get())->maybeStartAnimationOnCompositor(timeline()->zeroTime() + startTimeInternal() + timeLagInternal());
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/animation/AnimationPlayer.cpp中。
AnimationPlayer类是负责播放动画的,它所要播放的动画可以通过调用成员函数toAnimation获得,也就是获得一个Animation对象。获得了这个Animation对象后,就可以调用它的成员函数maybeStartAnimationOnCompositor检查它是否有动画需要执行,如下所示:
bool Animation::maybeStartAnimationOnCompositor(double startTime)
{
......
if (!CompositorAnimations::instance()->canStartAnimationOnCompositor(*m_target))
return false;
if (!CompositorAnimations::instance()->startAnimationOnCompositor(*m_target, startTime, specifiedTiming(), *effect(), m_compositorAnimationIds))
return false;
......
return true;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/animation/Animation.cpp中。
Animation类的成员函数maybeStartAnimationOnCompositor首先调用WebKit中的一个CompositorAnimations单例对象的成员函数canStartAnimationOnCompositor检查当前正在处理的Animation对象描述的动画是否可以执行。如果可以执行,再调用上述CompositorAnimations单例对象的成员函数startAnimationOnCompositor执行该动画。
CompositorAnimations类的成员函数startAnimationOnCompositor的实现如下所示:
bool CompositorAnimations::startAnimationOnCompositor(const Element& element, double startTime, const Timing& timing, const AnimationEffect& effect, Vector<int>& startedAnimationIds)
{
......
const KeyframeEffectModelBase& keyframeEffect = *toKeyframeEffectModelBase(&effect);
RenderLayer* layer = toRenderBoxModelObject(element.renderer())->layer();
......
Vector<OwnPtr<blink::WebAnimation> > animations;
CompositorAnimationsImpl::getAnimationOnCompositor(timing, startTime, keyframeEffect, animations);
......
for (size_t i = 0; i < animations.size(); ++i) {
int id = animations[i]->id();
if (!layer->compositedLayerMapping()->mainGraphicsLayer()->addAnimation(animations[i].release())) {
......
return false;
}
......
}
......
return true;
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/animation/CompositorAnimations.cpp中。
参数element描述的是要执行动画的一个Element。这个Element对应的是网页的DOM Tree中的一个节点。通过调用CompositorAnimations类的成员函数toRenderBoxModelObject可以获得它在网页的Render Layer Tree中对应的节点,也就是一个RenderLayer对象。
参数effect描述了上述Element要执行的动画,每一个动画会被重新封装成一个WebAnimation对象。这是通过调用CompositorAnimationsImpl类的静态成员函数getAnimationOnCompositor实现的,如下所示:
void CompositorAnimationsImpl::getAnimationOnCompositor(const Timing& timing, double startTime, const KeyframeEffectModelBase& effect, Vector<OwnPtr<blink::WebAnimation> >& animations)
{
......
PropertySet properties = effect.properties();
......
for (PropertySet::iterator it = properties.begin(); it != properties.end(); ++it) {
PropertySpecificKeyframeVector values;
getKeyframeValuesForProperty(&effect, *it, compositorTiming.scaledDuration, compositorTiming.reverse, values);
blink::WebAnimation::TargetProperty targetProperty;
OwnPtr<blink::WebAnimationCurve> curve;
switch (*it) {
case CSSPropertyOpacity: {
targetProperty = blink::WebAnimation::TargetPropertyOpacity;
blink::WebFloatAnimationCurve* floatCurve = blink::Platform::current()->compositorSupport()->createFloatAnimationCurve();
addKeyframesToCurve(*floatCurve, values, compositorTiming.reverse);
curve = adoptPtr(floatCurve);
break;
}
case CSSPropertyWebkitFilter: {
targetProperty = blink::WebAnimation::TargetPropertyFilter;
blink::WebFilterAnimationCurve* filterCurve = blink::Platform::current()->compositorSupport()->createFilterAnimationCurve();
addKeyframesToCurve(*filterCurve, values, compositorTiming.reverse);
curve = adoptPtr(filterCurve);
break;
}
case CSSPropertyTransform: {
targetProperty = blink::WebAnimation::TargetPropertyTransform;
blink::WebTransformAnimationCurve* transformCurve = blink::Platform::current()->compositorSupport()->createTransformAnimationCurve();
addKeyframesToCurve(*transformCurve, values, compositorTiming.reverse);
curve = adoptPtr(transformCurve);
break;
}
default:
ASSERT_NOT_REACHED();
continue;
}
......
OwnPtr<blink::WebAnimation> animation = adoptPtr(blink::Platform::current()->compositorSupport()->createAnimation(*curve, targetProperty));
......
animations.append(animation.release());
}
ASSERT(!animations.isEmpty());
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/animation/CompositorAnimations.cpp中。
每一个动画都对应有一个WebAnimationCurve对象。这些WebAnimationCurve对象描述了动画是如何执行的。不同类型的动画对应不同的WebAnimationCurve对象。例如,类型为CSSPropertyTransform的动画对应的WebAnimationCurve对象实际上是一个WebTransformAnimationCurve对象。
动画对应的WebAnimationCurve对象最终会通过Content层提供的一个WebCompositorSupport接口的成员函数createAnimation封装在一个WebAnimation对象中。这些WebAnimation对象最后保存在参数animations描述的一个向量中。
回到CompositorAnimations类的成员函数startAnimationOnCompositor,它获得了要执行的动画对应的WebAnimation对象之后,接下来就会将这些WebAnimation对象注册到它们所关联的Element所对应的Graphics Layer中去。
这个Graphics Layer是怎么得到的呢?从前面Chromium网页加载过程简要介绍和学习计划这个系列的文章可以知道,网页的DOM Tree中的一个Element在Render Object Tree中对应有一个Render Object。Render Object Tree中中的一个Render Object在Render Layer Tree中对应有一个Render Layer。Render Layer Tree中一个Render Layer在Graphics Layer Tree中又对应有一个Graphics Layer。通过这种对应关系,给出DOM Tree中的一个Element,就可以在Graphics Layer Tree中找到一个对应的Graphics Layer。
前面我们已经获得了要执行动画的Element所对应的Render Layer,通过调用这个Render Layer的成员函数compositedLayerMapping可以获得一个Composited Layer Mapping。从前面Chromium网页Graphics Layer Tree创建过程分析一文可以知道,Render Layer实际上对应的是一个Sub Graphics Layer Tree。这个Sub Graphics Layer Tree就是通过一个Composited Layer Mapping描述的。属于一个Render Layer的动画,需要映射到它对应的Sub Graphics Layer Tree中的Main Grapics Layer去执行。这个Main Grapics Layer可以通过调用CompositedLayerMapping类的成员函数mainGraphicsLayer获得了。
得到了Main Graphics Layer之后,就可以将前面获得的WebAnimation对象注册到它里面去了。这是通过调用GraphicsLayer类的成员函数addAnimation实现的,如下所示:
bool GraphicsLayer::addAnimation(PassOwnPtr<WebAnimation> popAnimation)
{
OwnPtr<WebAnimation> animation(popAnimation);
......
return platformLayer()->addAnimation(animation.leakPtr());
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp中。
从前面Chromium网页Layer Tree创建过程分析一文可以知道,WebKit层中的每一个Graphics Layer在Content层都对应有一个WebLayerImpl对象。这个WebLayerImpl对象可以通过调用GraphicsLayer类的成员函数platformLayer获得。有了这个WebLayerImpl对象之后,就可以调用它的成员函数addAnimation将参数popAnimation描述的动画注册到CC Layer Tree中去。
WebLayerImpl类的成员函数addAnimation的实现如下所示:
bool WebLayerImpl::addAnimation(blink::WebAnimation* animation) {
bool result = layer_->AddAnimation(
static_cast<WebAnimationImpl*>(animation)->PassAnimation());
......
return result;
}
这个函数定义在文件external/chromium_org/content/renderer/compositor_bindings/web_layer_impl.cc中。
从前面Chromium网页Layer Tree创建过程分析一文可以知道,WebLayerImpl类的成员变量layer_指向的是一个PictureLayer对象。WebLayerImpl类的成员函数addAnimation将参数animation描述的一个WebAnimation对象注册到它里面去。这是通过调用PictureLayer类的父类Layer的成员函数AddAnimation实现的,如下所示:
bool Layer::AddAnimation(scoped_ptr <Animation> animation) {
......
layer_animation_controller_->AddAnimation(animation.Pass());
SetNeedsCommit();
return true;
}
这个函数定义在文件external/chromium_org/cc/layers/layer.cc中。
Layer类的成员变量layer_animation_controller_指向的是一个LayerAnimationController对象。这个LayerAnimationController对象就是我们前面提到的CC Layer Tree中的每一个Layer所对应的动画控制器。有了这个LayerAnimationController对象之后,就可以调用它的成员函数AddAnimation将参数animation描述的动画注册到它里面去。注册完成之后,Layer类的成员函数AddAnimation还会调用另外一个成员函数SetNeedsCommit通知CC模块中的调度器重新绘制CC Layer Tree,因为CC Layer Tree现在有动画需要执行。
LayerAnimationController类的成员函数AddAnimation的实现如下所示:
void LayerAnimationController::AddAnimation(scoped_ptr<Animation> animation) {
animations_.push_back(animation.Pass());
needs_to_start_animations_ = true;
......
}
这个函数定义在文件external/chromium_org/cc/animation/layer_animation_controller.cc中。
LayerAnimationController类的成员函数AddAnimation将参数animation描述的动画保存在成员变量animations_描述的一个向量中,并且将另外一个成员变量needs_to_start_animations_的值设置为true,这样在下一次绘制CC Layer Tree时,我们前面分析的LayerAnimationController类的成员函数TickAnimations就会通过成员变量animations_计算参数animation描述的动画了。
这样,我们就分析完成了CC Layer Tree的动画的计算过程,回到ThreadProxy类的成员函数BeginMainFrame中,接下来我们继续分析CC Layer Tree的布局的计算过程,也就是LayerTreeHost类的成员函数Layout的实现,如下所示:
void LayerTreeHost::Layout() {
client_->Layout();
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。
从前面Chromium网页Layer Tree创建过程分析一文可以知道,LayerTreeHost类的成员变量client_指向的是一个RenderWidgetCompositor对象。LayerTreeHost类的成员函数Layout通过调用这个RenderWidgetCompositor对象的成员函数Layout计算CC Layer Tree的布局,实际上是通过CC Layer Tree对应的Render Object Tree进行计算的,因为后者知道网页所有元素渲染有关的信息。
RenderWidgetCompositor类的成员函数Layout的实现如下所示:
void RenderWidgetCompositor::Layout() {
widget_->webwidget()->layout();
}
这个函数定义在文件external/chromium_org/content/renderer/gpu/render_widget_compositor.cc中。
从前面Chromium网页Layer Tree创建过程分析一文可以知道,RenderWidgetCompositor类的成员变量widget_指向的是一个RenderViewImpl对象。调用这个RenderViewImpl对象的成员函数webwidget可以获得一个WebViewImpl对象。有了这个WebViewImpl对象之后,就可以调用它的成员函数layout对网页元素进行布局了。
WebViewImpl类的成员函数layout的实现如下所示:
void WebViewImpl::layout()
{
......
PageWidgetDelegate::layout(m_page.get());
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebViewImpl.cpp中。
WebViewImpl类的成员函数layout调用PageWidgetDelegate类的静态成员函数layout计算网页的布局,如下所示:
void PageWidgetDelegate::layout(Page* page)
{
......
page->animator().updateLayoutAndStyleForPainting();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/PageWidgetDelegate.cpp中。
PageWidgetDelegate类的静态成员函数layout首先调用参数page指向的一个Page对象的成员函数获得一个PageAnimator对象,然后再调用这个PageAnimator对象的成员函数updateLayoutAndStyleForPainting计算网页的布局,如下所示:
void PageAnimator::updateLayoutAndStyleForPainting()
{
......
RefPtr<FrameView> view = m_page->deprecatedLocalMainFrame()->view();
......
view->updateLayoutAndStyleForPainting();
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/page/PageAnimator.cpp中。
PageAnimator类的成员函数updateLayoutAndStyleForPainting首先调用成员变量m_page指向的一个Page对象的成员函数deprecatedLocalMainFrame获得一个LocalFrame对象。这个LocalFrame对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文,它描述的是一个在当前Render进程中加载的网页。有了这个LocalFrame对象之后,再调用它的成员函数view获得一个FrameView对象,最后调用这个FrameView对象的成员函数updateLayoutAndStyleForPainting计算网页的布局,如下所示:
void FrameView::updateLayoutAndStyleForPainting()
{
// Updating layout can run script, which can tear down the FrameView.
RefPtr<FrameView> protector(this);
updateLayoutAndStyleIfNeededRecursive();
if (RenderView* view = renderView()) {
......
view->compositor()->updateIfNeededRecursive();
......
}
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。
FrameView类的成员函数updateLayoutAndStyleForPainting首先调用成员函数updateLayoutAndStyleIfNeededRecursive计算网页的布局,接下来再调用成员函数renderView获得一个RenderView对象。从前面Chromium网页DOM Tree创建过程分析一文可以知道,网页的DOM Tree的根节点对应的Render Object就是一个RenderView对象。因此,这里获得的RenderView对象描述的就是正在加载的网页的Render Object Tree的根节点。
得到了描述Render Object Tree的根节点的RenderView对象之后,FrameView类的成员函数updateLayoutAndStyleForPainting就调用它的成员函数compositor获得一个RenderLayerCompositor对象,然后调用这个RenderLayerCompositor对象的成员函数updateIfNeededRecursive创建或者更新网页的Graphics Layer Tree。这个过程可以参考前面Chromium网页Graphics Layer Tree创建过程分析一文。
接下来我们继续分析网页布局的计算过程,也就是FrameView类的成员函数updateLayoutAndStyleIfNeededRecursive的实现,如下所示:
void FrameView::updateLayoutAndStyleIfNeededRecursive()
{
......
m_frame->document()->updateRenderTreeIfNeeded();
if (needsLayout())
layout();
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。
FrameView类的成员变量m_frame指向的是一个LocalFrame对象,FrameView类的成员函数updateLayoutAndStyleIfNeededRecursive调用这个LocalFrame对象的成员函数document可以获得一个Document对象。这个Document对象描述的就是网页的文档对象。有了这个Document对象之后,就可以调用它的成员函数updateRenderTreeIfNeeded更新网页的Render Object Tree。这个过程可以参考前面Chromium网页Render Object Tree创建过程分析一文。从前面Chromium网页Render Layer Tree创建过程分析一文又可以知道,在网页的Render Object Tree的更新过程中,Render Layer Tree也会得到更新。
更新了网页的Render Object Tree和Render Layer Tree,FrameView类的成员函数updateLayoutAndStyleIfNeededRecursive就调用成员函数needsLayout检查网页的布局是否需要重新计算。如果需要的话,就会调用另外一个成员函数layout进行计算,如下所示:
void FrameView::layout(bool allowSubtree)
{
......
Document* document = m_frame->document();
bool inSubtreeLayout = isSubtreeLayout();
RenderObject* rootForThisLayout = inSubtreeLayout ? m_layoutSubtreeRoot : document->renderView();
......
{
......
performLayout(rootForThisLayout, inSubtreeLayout);
......
}
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。
当FrameView类的成员变量m_layoutSubtreeRoot的值不等于NULL的时候,它指向一个RenderObject对象,表示以这个RenderObject对象为根节点的一个Sub Render Object Tree需要进行重新布局。另一方面,当这个成员变量的值等于NULL的时候,就表示需要对整个网页进行重新布局,这时候FrameView类的成员函数layout就会通过成员变量m_frame指向的一个LocalFrame对象获得网页的Render Object Tree的根节点,也就是一个RenderView对象。
不管是要对Sub Render Object Tree进行重新布局,还是对整个网页进行重新布局,FrameView类的成员函数layout最后都是通过调用另外一个成员函数performLayout执行具体的布局工作的,如下所示:
void FrameView::performLayout(RenderObject* rootForThisLayout, bool inSubtreeLayout)
{
......
rootForThisLayout->layout();
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/frame/FrameView.cpp中。
FrameView类的成员函数layout要做的工作就是调用参数rootForThisLayout指向的RenderObject对象的成员函数layout更新它的布局。这个RenderObject对象又会递归计算它的子RenderObject对象的布局,从而完成以参数rootForThisLayout指向的RenderObject对象为根节点的Render Object Tree的布局更新过程。
从前面Chromium网页Render Object Tree创建过程分析一文可以知道,在Render Object Tree中,每一个节点都对应一个特定类型的Render Object。不过这些Render Object都是从RenderBox类继承下来的。每一个Render Object在对自己的内容进行布局的过程中,都会通过调用父类RenderBox的成员函数layout对自己的子Render Object进行归递布局,如下所示:
void RenderBox::layout()
{
......
RenderObject* child = slowFirstChild();
......
while (child) {
child->layoutIfNeeded();
......
child = child->nextSibling();
}
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBox.cpp中。
从这里我们就可以看到,RenderBox类的成员函数layout通过一个while循环依次调用当前正在处理的Render Object的所有子Render Object的成员函数layoutIfNeeded检查它们是否需要重新布局,如下所示:
class RenderObject : public ImageResourceClient {
......
public:
......
/* This function performs a layout only if one is needed. */
void layoutIfNeeded() { if (needsLayout()) layout(); }
......
};
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderObject.h中。
如果一个Render Object需要重新布局,那么它就会调用由其子类重写的成员函数layout执行具体的布局工作,这是一个递归的过程。
所有的Render Object都是按照CSS Box Model来计算自己的布局的,如图3所示:
图3 CSS Box Model
关于CSS Box Model的详细描述,可以参考这篇文章:CSS Box Model and Positioning。简单来说,就是一个CSS Box Model由margin、border、padding和content四部分组成。其中,margin、border和padding又分为top、bottom、left和right四个值。一个Render Object在绘制之前,会先进行Layout。Layout的目的就是确定一个Render Object的CSS Box Model的margin、border和padding值。一旦这些值确定之后,再结合Content值,就可以对一个Render Object进行绘制了。
这样,我们就分析完成了CC Layer Tree的布局的计算过程,回到ThreadProxy类的成员函数BeginMainFrame中,接下来我们继续分析CC Layer Tree的绘制过程,也就是LayerTreeHost类的成员函数UpdateLayers的实现,如下所示:
bool LayerTreeHost::UpdateLayers(ResourceUpdateQueue* queue) {
......
bool result = UpdateLayers(root_layer(), queue);
......
return result || next_commit_forces_redraw_;
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。
LayerTreeHost类的成员函数UpdateLayers首先调用另外一个成员函数root_layer获得CC Layer Tree的根节点,然后再调用另外一个重载版本的成员函数UpdateLayers对CC Layer Tree进行绘制,如下所示:
bool LayerTreeHost::UpdateLayers(Layer* root_layer,
ResourceUpdateQueue* queue) {
......
RenderSurfaceLayerList update_list;
{
......
bool can_render_to_separate_surface = true;
......
int render_surface_layer_list_id = 0;
LayerTreeHostCommon::CalcDrawPropsMainInputs inputs(
root_layer,
device_viewport_size(),
gfx::Transform(),
device_scale_factor_,
page_scale_factor_,
page_scale_layer,
GetRendererCapabilities().max_texture_size,
settings_.can_use_lcd_text,
can_render_to_separate_surface,
settings_.layer_transforms_should_scale_layer_contents,
&update_list,
render_surface_layer_list_id);
LayerTreeHostCommon::CalculateDrawProperties(&inputs);
......
}
bool did_paint_content = false;
bool need_more_updates = false;
PaintLayerContents(
update_list, queue, &did_paint_content, &need_more_updates);
if (need_more_updates) {
......
prepaint_callback_.Reset(base::Bind(&LayerTreeHost::TriggerPrepaint,
base::Unretained(this)));
static base::TimeDelta prepaint_delay =
base::TimeDelta::FromMilliseconds(100);
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE, prepaint_callback_.callback(), prepaint_delay);
}
return did_paint_content;
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。
LayerTreeHost类的成员函数UpdateLayers主要是做两件事情。第一件事情是调用LayerTreeHostCommon类静态成员函数CalculateDrawProperties计算以参数root_layer指向的Layer对象为根节点的CC Layer Tree的每一个Layer的绘图属性,例如变换矩阵、背景色等等。此外,LayerTreeHostCommon类静态成员函数CalculateDrawProperties还会根据上述CC Layer Tree创建一个Render Surface Tree。CC Layer Tree中的节点与Render Surface Tree中的节点是多对一的关系。也就是只有CC Layer Tree的某些节点在Render Surface Tree中才拥有Render Surface。
从前面Chromium网页Layer Tree创建过程分析一文可以知道,CC Layer Tree与Graphics Layer Tree中的节点是一一对应关系,并且Graphics Layer Tree的每一个Layer代表的都是一个图层。这个图层在硬件加速渲染条件下,就是一个FBO。但是,这只是WebKit的一厢情愿。到底要不要为Graphics Layer Tree中的Layer分配一个图层最终是由CC模块决定的。如果CC模块决定要为一个Graphics Layer分配一个图层,那么就会为它创建一个Render Surface。Render Surface才是真正表示一个图层。
LayerTreeHostCommon类静态成员函数CalculateDrawProperties根据CC Layer Tree创建出来的Render Surface Tree虽然在结构上也是一个Tree,不过它的节点是以列表的形式储存的,也就是储存在本地变量update_list描述的一个RenderSurfaceLayerList中。
有了上述RenderSurfaceLayerList之后,LayerTreeHost类的成员函数UpdateLayers再调用另外一个成员函数PaintLayerContents对保存在RenderSurfaceLayerList中的Render Surface进行绘制,实际上就是对CC Layer Tree进行绘制。
LayerTreeHost类的成员函数PaintLayerContents的返回值表示它是否对CC Layer Tree执行了绘制工作。如果执行了,这个返回值就会等于true。另外,LayerTreeHost类的成员函数PaintLayerContents的最后一个参数是一个输出参数。当这个输出参数的值等于true的时候,就表示CC Layer Tree接下来还需要再绘制一次。在这种情况下,LayerTreeHost类的成员函数UpdateLayers在设置一个定时器,在100毫秒后在Main线程中通过另外一个成员函数TriggerPrepaint触发一次CC Layer Tree的绘制工作。
接下来,我们首先分析LayerTreeHostCommon类静态成员函数CalculateDrawProperties的实现,以便了解网页的Render Surface Tree的创建过程,接着再分析LayerTreeHost类的成员函数PaintLayerContents的实现,以便了解CC Layer Tree的绘制过程。
LayerTreeHostCommon类静态成员函数CalculateDrawProperties的实现如下所示:
void LayerTreeHostCommon::CalculateDrawProperties(
CalcDrawPropsMainInputs* inputs) {
LayerList dummy_layer_list;
SubtreeGlobals<Layer> globals;
DataForRecursion<Layer> data_for_recursion;
ProcessCalcDrawPropsInputs(*inputs, &globals, &data_for_recursion);
PreCalculateMetaInformationRecursiveData recursive_data;
PreCalculateMetaInformation(inputs->root_layer, &recursive_data);
std::vector<AccumulatedSurfaceState<Layer> > accumulated_surface_state;
CalculateDrawPropertiesInternal<Layer>(
inputs->root_layer,
globals,
data_for_recursion,
inputs->render_surface_layer_list,
&dummy_layer_list,
&accumulated_surface_state,
inputs->current_render_surface_layer_list_id);
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_common.cc中。
LayerTreeHostCommon类的静态成员函数CalculateDrawProperties主要是调用另外一个静态模板成员函数CalculateDrawPropertiesInternal计算CC Layer Tree的绘制属性以及为其创建Render Surface Tree。其中,模板参数设置为Layer。
LayerTreeHostCommon类的静态模板成员函数CalculateDrawPropertiesInternal的实现如下所示:
template <typename LayerType>
static void CalculateDrawPropertiesInternal(
LayerType* layer,
const SubtreeGlobals<LayerType>& globals,
const DataForRecursion<LayerType>& data_from_ancestor,
typename LayerType::RenderSurfaceListType* render_surface_layer_list,
typename LayerType::LayerListType* layer_list,
std::vector<AccumulatedSurfaceState<LayerType> >* accumulated_surface_state,
int current_render_surface_layer_list_id) {
......
bool render_to_separate_surface;
if (globals.can_render_to_separate_surface) {
render_to_separate_surface = SubtreeShouldRenderToSeparateSurface(
layer, combined_transform.Preserves2dAxisAlignment());
} else {
render_to_separate_surface = IsRootLayer(layer);
}
if (render_to_separate_surface) {
......
typename LayerType::RenderSurfaceType* render_surface =
CreateOrReuseRenderSurface(layer);
......
render_surface_layer_list->push_back(layer);
} else {
......
layer->ClearRenderSurface();
......
}
......
typename LayerType::LayerListType& descendants =
(layer->render_surface() ? layer->render_surface()->layer_list()
: *layer_list);
// Any layers that are appended after this point are in the layer's subtree
// and should be included in the sorting process.
size_t sorting_start_index = descendants.size();
if (!LayerShouldBeSkipped(layer, layer_is_drawn)) {
......
descendants.push_back(layer);
}
......
for (size_t i = 0; i < layer->children().size(); ++i) {
// If one of layer's children has a scroll parent, then we may have to
// visit the children out of order. The new order is stored in
// sorted_children. Otherwise, we'll grab the child directly from the
// layer's list of children.
LayerType* child =
layer_draw_properties.has_child_with_a_scroll_parent
? sorted_children[i]
: LayerTreeHostCommon::get_layer_as_raw_ptr(layer->children(), i);
......
CalculateDrawPropertiesInternal<LayerType>(
child,
globals,
data_for_children,
render_surface_layer_list,
&descendants,
accumulated_surface_state,
current_render_surface_layer_list_id);
if (child->render_surface() &&
!child->render_surface()->layer_list().empty() &&
!child->render_surface()->content_rect().IsEmpty()) {
......
descendants.push_back(child);
}
......
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_common.cc中。
LayerTreeHostCommon类的静态模板成员函数CalculateDrawPropertiesInternal计算CC Layer Tree的过程非常复杂,这里我们将略过。这不会影响我们理解CC Layer Tree的绘制过程,有兴趣的读者可以自行分析。
有三个参数我们需要重点解释一下,分别是layer、globals、render_surface_layer_list和layer_list。
参数layer指向的是一个CC Layer Tree中的一个Layer, LayerTreeHostCommon类的静态模板成员函数CalculateDrawPropertiesInternal要做的事情就是计算以此Layer为根节点的子树的绘制属性,以及为该子树创建的Render Surface Tree。
参数globals指向一个SubtreeGlobals对象,这个SubtreeGlobals对象的成员变量can_render_to_separate_surface来自于前面分析的LayerTreeHost类的成员函数UpdateLayers的本地变量can_render_to_separate_surface,它的值被设置为true,表示允许为CC Layer Tree中的Layer创建单独的Render Surface。在不允许的情况下,CC Layer Tree中的所有Layer都将绘制在一个Render Surface上,也就是根节点拥有的Render Surface。
参数render_surface_layer_list是一个输出参数,用来保存最终得到的Render Surface Tree,这个参数来自于前面分析的LayerTreeHost类的成员函数UpdateLayers的本地变量update_list。
参数layer_list是一个Layer List,这个Layer List保存的是要绘制在当前正在处理的Render Surface的所有Layer。
LayerTreeHostCommon类的静态模板成员函数CalculateDrawPropertiesInternal为CC Layer Tree创建Render Surface Tree的过程如下所示:
1. 如果允许为CC Layer Tree中的Layer创建单独的Render Surface,那么就调用全局函数SubtreeShouldRenderToSeparateSurface判断是否真的要为参数layer描述的Layer创建Render Surface。如果要创建,那么就将本地变量render_to_separate_surface设置为true,否则就设置为false。
2. 如果不允许为CC Layer Tree中的Layer创建单独的Render Surface,那么就只有CC Layer Tree的根节点才允许创建Render Surface,也就是只有当参数layer描述的Layer是CC Layer Tree的根节点时,本地变量render_to_separate_surface才会设置为true。
3. 如果要为参数layer描述的Layer创建Render Surface,那么就会调用成员函数CreateOrReuseRenderSurface为其创建一个Render Surface。与此同时,参数layer描述的Layer会被保存在参数render_surface_layer_list描述的Render Surface List中。这也意味着保存在render_surface_layer_list描述的Render Surface List中的Layer都是有Render Surface的。
4. 如果不需要为参数layer描述的Layer创建Render Surface,那么就会调用该Layer的成员函数ClearRenderSurface检查之前是否为它创建过Render Surface。如果创建过,那么就将它删除。
5. 每一个Render Surface都有一个Layer List。这个Layer List保存的Layer都是要绘制宿主Render Surface上的。同时,如果我们为CC Layer Tree中的一个Layer创建了一个Render Surface,那么该Layer的后代Layer都会保存在该Layer的Render Surface上,直到碰到另外一个具有自己的Render Surface的后代Layer为止。此外,一个Layer要保存在一个Render Surface的Layer List上,还要满足一个条件,就是以这个Layer为根节点的子树是可见的。如果不可见,就意味不用绘制,因此就不用保存在Render Surface的Layer List去了。
6. 按照上述方式处理参数layer描述的Layer的所有子Layer,也就是递归调用LayerTreeHostCommon类的静态模板成员函数CalculateDrawPropertiesInternal处理参数layer描述的Layer的所有子Layer。
7. 如果一个子Layer具有自己的Render Surface,并且这个Render Surface的绘制区域不为空,以及它的Layer List不为空,那么当它被递归处理完成后,会添加到前一个Render Surface的Layer List中去。这个Render Surface对应的Layer是子Layer的某个祖先Layer。
我们通过图4说明Render Surface Tree创建完成后的结构,如下所示:
图4 Render Surface List的构造方式
图4的左边是一个CC Layer Tree。其中,第1、3和5个Layer具有Render Surface。因此,得到的Render Surface List就保存了Layer 1、Layer 3和Layer 5。这三个Render Surface分别称为1、3和5。Render Surface 1的Layer List保存了Layer 1、Layer 2和Layer 3。Render Surface 3的Layer List保存了Layer 3、Layer 4和Layer 5。Render Surface 5的Layer List保存了Layer 5。这表示,Layer 5先绘制在Render Surface 5上,接着Layer 3、Layer 4和Render Surface 5再绘制在Render Surface 3上,最后Layer 1、Layer 2和Render Surface 3绘制在Render Surface 1。最终得到的内容都在Render Surface 1中。因此,也将Render Surface 1称为Target Render Surface,Render Surface 3和Render Surface 5的Contributing Render Surface。
什么情况下需要为一个Layer创建的一个Render Surface呢?一般来说,如果一个Layer满足以下条件之一,就需要为其创建Render Surface:
1. 设置了蒙板。
2. 设置了镜像。
3. 设置了滤镜。
4. 设置了透明度以及转换矩阵。
5. 关联有Copy Output Request。
6. 具有WebGL Context。
7. 是CC Layer Tree的根节点。
具体的规则可以参考全局函数SubtreeShouldRenderToSeparateSurface的实现。另外,我们也可以调用Layer类的成员函数SetForceRenderSurface将一个Layer强制设置为创建Render Surface。
此外,Copy Output Request是用来拷贝它所关联的Layer的Render Surface的内容。也就是当一个Render Surface渲染完成的时候,如果与它对应的Layer关联有Copy Output Request,那么Chromium就将这个Render Surface的内容拷贝出来,并且发送给请求者。我们可以通过调用Layer类的成员函数RequestCopyOfOutput为一个Layer关联一个Copy Output Request。不过,这些Copy Output Request得到执行还需满足两个条件:
1. 它所关联的Layer的Render Surface是Target Render Surface。
2. 它所关联的Layer使用的渲染器是直接渲染器(Direct Renderer)。
关于第2点,我们进一步解释。从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,Render进程使用委托渲染器(Delegated Renderer)渲染网页的。委托渲染器实际上并没有执行实际的渲染操作,它只是要渲染的纹理传递给另外一个渲染器渲染,也就是Browser进程使用的渲染器,这是一个直接渲染器。这就意味着,我们无法对网页的CC Layer Tree的Layer关联Copy Output Request了,即使了关联了,也不会得到执行。不过我们却可以对浏览器窗口的CC Layer Tree的Layer关联Copy Output Request。浏览器窗口的CC Layer Tree有一个Layer,这个Layer描述的是网页的内容,因此虽然我们无法在Render进程中截取网页的内容,但是可以在Browser进程中截取网页的内容。
回到LayerTreeHost类的成员函数UpdateLayers中,得到了一个Render Surface List之后,接下来它就会调用另外一个成员函数PaintLayerContents根据这个Render Surface List绘制CC Layer Tree,如下所示:
void LayerTreeHost::PaintLayerContents(
const RenderSurfaceLayerList& render_surface_layer_list,
ResourceUpdateQueue* queue,
bool* did_paint_content,
bool* need_more_updates) {
......
// Iterates front-to-back to allow for testing occlusion and performing
// culling during the tree walk.
typedef LayerIterator<Layer> LayerIteratorType;
LayerIteratorType end = LayerIteratorType::End(&render_surface_layer_list);
for (LayerIteratorType it =
LayerIteratorType::Begin(&render_surface_layer_list);
it != end;
++it) {
......
if (it.represents_target_render_surface()) {
PaintMasksForRenderSurface(
*it, queue, did_paint_content, need_more_updates);
} else if (it.represents_itself()) {
......
*did_paint_content |= it->Update(queue, &occlusion_tracker);
*need_more_updates |= it->NeedMoreUpdates();
......
}
......
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。
LayerTreeHost类的成员函数PaintLayerContents按照从后到前的顺序绘制保存在参数render_surface_layer_list描述的Render Surface List中的Layer,以及Target Render Surface。绘制顺序可以参考前面图4的示例。其中,每一个Layer都是通过调用它的成员函数Update绘制的,Target Render Surface只需要绘制它的蒙板就可以了。每一个Layer绘制完成之后,LayerTreeHost类的成员函数PaintLayerContents都会调用它的另外一个成员函数NeedMoreUpdates判断它接下来是否需要再进行更新。从前面的分析可以知道,如果需要的话,LayerTreeHost类的成员函数UpdateLayers就会设置一个定时器,在100毫秒后进行更新。
接下来我们主要分析CC Layer Tree中的Layer的绘制过程。从前面Chromium网页Layer Tree创建过程分析一文可以知道,CC Layer Tree中的每一个Layer都是通过一个PictureLayer对象描述的。因此,接下来我们分析PictureLayer类的成员函数Update的实现,如下所示:
bool PictureLayer::Update(ResourceUpdateQueue* queue,
const OcclusionTracker<Layer>* occlusion) {
......
bool updated = Layer::Update(queue, occlusion);
......
gfx::Rect visible_layer_rect = gfx::ScaleToEnclosingRect(
visible_content_rect(), 1.f / contents_scale_x());
gfx::Rect layer_rect = gfx::Rect(paint_properties().bounds);
......
pile_->SetTilingRect(layer_rect);
......
pending_invalidation_.Swap(&pile_invalidation_);
pending_invalidation_.Clear();
......
updated |=
pile_->UpdateAndExpandInvalidation(client_,
&pile_invalidation_,
SafeOpaqueBackgroundColor(),
contents_opaque(),
client_->FillsBoundsCompletely(),
visible_layer_rect,
update_source_frame_number_,
RecordingMode(),
rendering_stats_instrumentation());
......
return updated;
}
这个函数定义在文件external/chromium_org/cc/layers/layer.cc中。
PictureLayer类的成员函数Update首先调用父类Layer的成员函数Update让其有机会对当前正在处理的Layer执行一些更新工作(实际上什么也没有做),接着再计算当前正在处理的Layer所占据的区域layer_rect和可见区域visible_layer_rect。
PictureLayer类有两个重要的成员变量pending_invalidation和pile。其中,成员变量pending_invalidation_指向的是一个Region对象。这个Region对象描述的是当前正在处理的Layer的待重绘区域。PictureLayer类的成员函数Update在重绘这个区域之前,会先将它的值设置到另外一个成员变量pile_invalidation_中去,以表示Layer的当前重绘区域,同时也会将待重绘区域清空。
另外一个成员变量pile_指向了一个PicturePile对象,这个PicturePile对象负责绘制当前正在处理的Layer的待重绘区域,这是通过调用它的成员函数UpdateAndExpandInvalidation实现的。
Layer的当前重绘区域会传递给PicturePile类的成员函数UpdateAndExpandInvalidation。PicturePile类的成员函数UpdateAndExpandInvalidation会使得这个当前重绘区域包含所有进行了重新绘制的分块。这里说的重新绘制,包含有三层含义。第一层含义是上一次绘制过,这一次变为不需要绘制。第二层含义是上一次绘制过,这一次需要重新再绘制。第三层含义是上次没有绘制过,这一次需要进行绘制。
后面将CC Layer Tree同步到新的CC Pending Layer Tree去时,CC Pending Layer Tree会获得每一个Layer的当前重绘区域。获得了每一个Layer的当前重绘区域之后,就可以对它所包含的分块进行光栅化操作了。
在调用PicturePile类的成员函数UpdateAndExpandInvalidation绘制Layer的内容之前,PictureLayer类的成员函数Update会先将Layer所占据的区域layer_rect设置到成员变量pile_指向的PicturePile对象的内部去,这是通过调用PicturePile类的成员函数SetTilingRect实现的。
PicturePile类的成员函数SetTilingRect是从父类PicturePileBase类继承下来的。在分析PicturePileBase类的成员函数SetTilingRect之前,我们需要分析PicturePileBase类的一个成员变量tiling_,它的定义如下所示:
class CC_EXPORT PicturePileBase : public base::RefCounted<PicturePileBase> {
......
protected:
......
TilingData tiling_;
......
};
这个类定义在文件external/chromium_org/cc/resources/picture_pile_base.h中。
从这里可以看到,PicturePileBase类的成员变量tiling_描述的是一个TilingData对象,这个TilingData对象是负责对Layer进行分块的,它的定义如下所示:
class CC_EXPORT TilingData {
......
private:
......
gfx::Size max_texture_size_;
gfx::Rect tiling_rect_;
int border_texels_;
// These are computed values.
int num_tiles_x_;
int num_tiles_y_;
};
这个类定义在文件external/chromium_org/cc/base/tiling_data.h中。
TilingData类有五个成员变量,它们的含义分别为:
2. tilingrect:表示Layer的大小。
3. bordertexels:表示分块的边界大小。这个边界即为图2所示的分块重叠区域的长度。
num_tilesx:表示Layer在x轴方向的分块个数。
num_tilesy:表示Layer在y轴方向的分块个数。
PicturePileBase类的成员变量tiling_描述的TilingData对象是通过调用TilingData类的默认构造函数创建的,如下所示:
TilingData::TilingData()
: border_texels_(0) {
RecomputeNumTiles();
}
这个函数定义在文件external/chromium_org/cc/base/tiling_data.cc中。
TilingData类的默认构造函数先将成员变量border_texels_的值设置为0,接着调用另外一个成员函数RecomputeNumTiles分别计算Layer在x轴和y轴方向的分块个数。由于现在TilingData类还不知道Layer的大小,因此现在的计算是没有意义的。
PictureLayer类的成员变量pile_指向的PicturePile对象在构造的时候,会调用父类PicturePileBase的构造函数设置分块的大小,如下所示:
const int kBasePictureSize = 512;
......
PicturePileBase::PicturePileBase()
: min_contents_scale_(0),
...... {
tiling_.SetMaxTextureSize(gfx::Size(kBasePictureSize, kBasePictureSize));
......
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_base.cc中。
从这里可以看到,PicturePileBase的构造函数将成员变量tiling_描述的TilingData对象使用的分块大小设置为512,这是通过调用TilingData类的成员函数SetMaxTextureSize实现的,如下所示:
void TilingData::SetMaxTextureSize(const gfx::Size& max_texture_size) {
max_texture_size_ = max_texture_size;
RecomputeNumTiles();
}
这个函数定义在文件external/chromium_org/cc/base/tiling_data.cc中。
TilingData类的成员函数SetMaxTextureSize先将参数max_texture_size描述的分块大小保存在成员变量max_texture_size_中,接着再调用成员函数RecomputeNumTiles重新计算Layer在x轴和y轴方向的分块个数。
从前面Chromium网页Layer Tree创建过程分析一文可以知道,CC Layer Tree是由Main线程中的一个LayerTreeHost对象管理的。CC模块会给CC Layer Tree的每一个节点,也就是每一个PictureLayer对象,都关联负责管理CC Layer Tree的LayerTreeHost对象。这是通过调用PictureLayer类的成员函数SetLayerTreeHost实现的,如下所示:
void PictureLayer::SetLayerTreeHost(LayerTreeHost* host) {
......
if (host) {
pile_->SetMinContentsScale(host->settings().minimum_contents_scale);
......
}
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer.cc中。
PictureLayer类的成员函数SetLayerTreeHost会通过参数host指向的LayerTreeHost对象获得CC Layer Tree的最小缩放因子minimum_contents_scale。这个最小缩放因子minimum_contents_scale设置为0.0625,也就是1/16。这个最小缩放因子会设置给成员变量pile_指向的PicturePile对象,这是通过调用PicturePile类的成员函数SetMinContentsScale实现的。
PicturePile类的成员函数SetMinContentsScale是从父类PicturePileBase继承下来的,它的实现如下所示:
void PicturePileBase::SetMinContentsScale(float min_contents_scale) {
......
// Picture contents are played back scaled. When the final contents scale is
// less than 1 (i.e. low res), then multiple recorded pixels will be used
// to raster one final pixel. To avoid splitting a final pixel across
// pictures (which would result in incorrect rasterization due to blending), a
// buffer margin is added so that any picture can be snapped to integral
// final pixels.
//
// For example, if a 1/4 contents scale is used, then that would be 3 buffer
// pixels, since that's the minimum number of pixels to add so that resulting
// content can be snapped to a four pixel aligned grid.
int buffer_pixels = static_cast<int>(ceil(1 / min_contents_scale) - 1);
buffer_pixels = std::max(0, buffer_pixels);
SetBufferPixels(buffer_pixels);
min_contents_scale_ = min_contents_scale;
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_base.cc中。
PicturePileBase类的成员函数SetMinContentsScale会将参数min_contents_scale的值保存在成员变量min_contents_scale_中。同时,PicturePileBase类的成员函数SetMinContentsScale也会计算出分块边界长度buffer_pixels,然后调用另外一个成员函数SetBufferPixels将它设置给成员变量tiling_描述的TilingData对象,如下所示:
void PicturePileBase::SetBufferPixels(int new_buffer_pixels) {
......
tiling_.SetBorderTexels(new_buffer_pixels);
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_base.cc中。
PicturePileBase类的成员函数SetBufferPixels调用TilingData类的成员函数SetBorderTexels将参数new_buffer_pixels描述的分块边界设置给成员变量tiling_描述的TilingData对象,如下所示:
void TilingData::SetBorderTexels(int border_texels) {
border_texels_ = border_texels;
RecomputeNumTiles();
}
这个函数定义在文件external/chromium_org/cc/base/tiling_data.cc中。
TilingData类的成员函数SetBorderTexels先将参数border_texels描述的分块边界长度保存在成员变量border_texels_中,接着再调用成员函数RecomputeNumTiles重新计算Layer在x轴和y轴方向的分块个数。
现在我们知道了一个Layer的分块大小和分块边界长度。如果再给出Layer的大小,那么就可以计算出Layer在x轴和y轴方向的分块个数了。从前面的分析可以知道,PictureLayer类的成员函数Update计算好一个Layer的大小之后,会将这个大小设置给其成员变量pile_指向的一个PicturePile对象。这是通过调用PicturePile类的成员函数SetTilingRect实现的。
PicturePile类的成员函数SetTilingRect是从父类PicturePileBase继承下来的,它的实现如下所示:
void PicturePileBase::SetTilingRect(const gfx::Rect& new_tiling_rect) {
......
tiling_.SetTilingRect(new_tiling_rect);
......
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_base.cc中。
PicturePileBase类的成员函数SetTilingRect调用TilingData类的成员函数SetTilingRect将参数new_tiling_rect描述的Layer大小设置给成员变量tiling_描述的TilingData对象,如下所示:
void TilingData::SetTilingRect(const gfx::Rect& tiling_rect) {
tiling_rect_ = tiling_rect;
RecomputeNumTiles();
}
这个函数定义在文件external/chromium_org/cc/base/tiling_data.cc中。
TilingData类的成员函数SetTilingRect先将参数tiling_rect描述的Layer大小保存在成员变量tiling_rect_中,接着再调用成员函数RecomputeNumTiles重新计算Layer在x轴和y轴方向的分块个数。
现在我们不仅知道了一个Layer的分块大小和分块边界长度,还知道了这个Layer的大小,于是就可以计算它在x轴和y轴方向上的分块数了,如下所示:
void TilingData::RecomputeNumTiles() {
num_tiles_x_ = ComputeNumTiles(
max_texture_size_.width(), tiling_rect_.width(), border_texels_);
num_tiles_y_ = ComputeNumTiles(
max_texture_size_.height(), tiling_rect_.height(), border_texels_);
}
这个函数定义在文件external/chromium_org/cc/base/tiling_data.cc中。
计算分块的个数,要同时考虑Layer大小、分块大小和分块边界长度。有了这些数据,就可以调用函数ComputeNumTiles进行计算了,如下所示:
static int ComputeNumTiles(int max_texture_size,
int total_size,
int border_texels) {
if (max_texture_size - 2 * border_texels <= 0)
return total_size > 0 && max_texture_size >= total_size ? 1 : 0;
int num_tiles = std::max(1,
1 + (total_size - 1 - 2 * border_texels) /
(max_texture_size - 2 * border_texels));
return total_size > 0 ? num_tiles : 0;
}
这个函数定义在文件external/chromium_org/cc/base/tiling_data.cc中。
读者可以参考图2的示例分析函数ComputeNumTiles计算分块个数的逻辑,这里就不详细分析了。
回到PictureLayer类的成员函数Update中,它计算好当前正在处理的Layer所占据的区域大小和可见区域大小之后,接下来就会调用成员变量pile_指向的PicturePile对象的成员函数UpdateAndExpandInvalidation做两件事情:
1. 重新绘制位于Layer的Interest Rect内的分块。
2. 使得Layer的当前重绘区域包含所有执行过重新绘制操作的分块。
PicturePile对象的成员函数UpdateAndExpandInvalidation的实现比较复杂,我们分段阅读。
PicturePile类的成员函数UpdateAndExpandInvalidation首先计算出一个Interest Rect,如下所示:
const int kPixelDistanceToRecord = 8000;
......
bool PicturePile::UpdateAndExpandInvalidation(
ContentLayerClient* painter,
Region* invalidation,
SkColor background_color,
bool contents_opaque,
bool contents_fill_bounds_completely,
const gfx::Rect& visible_layer_rect,
int frame_number,
Picture::RecordingMode recording_mode,
RenderingStatsInstrumentation* stats_instrumentation) {
......
gfx::Rect interest_rect = visible_layer_rect;
interest_rect.Inset(
-kPixelDistanceToRecord,
-kPixelDistanceToRecord,
-kPixelDistanceToRecord,
-kPixelDistanceToRecord);
这个代码段定义在文件external/chromium_org/cc/resources/picture_pile.cc中。
Interest Rect的面积大于可见区域(Viewport)的面积,但是小于整个Layer Rect的面积。Interest Rect、Visible Rect和Layer Rect的关系如图5所示:
图5 Interest Rect、Visible Rect和Layer Rect的关系
在Interest Rect内的分块,如果它们处于参数invalidation描述的重绘区域内,那么就是需要进行重绘的。
从前面的调用过程可以知道,参数invalidation指向的是PictureLayer类的成员变量pile_invalidation_描述的重绘区域,也就是它描述的是Layer的当前重绘区域,PicturePile类的成员函数UpdateAndExpandInvalidation接下来将这个区域对齐到分块边界,如下所示:
gfx::Rect interest_rect_over_tiles =
tiling_.ExpandRectToTileBounds(interest_rect);
Region invalidation_expanded_to_full_tiles;
bool invalidated = false;
for (Region::Iterator i(*invalidation); i.has_rect(); i.next()) {
gfx::Rect invalid_rect = i.rect();
// Split this inflated invalidation across tile boundaries and apply it
// to all tiles that it touches.
bool include_borders = true;
for (TilingData::Iterator iter(&tiling_, invalid_rect, include_borders);
iter;
++iter) {
const PictureMapKey& key = iter.index();
PictureMap::iterator picture_it = picture_map_.find(key);
if (picture_it == picture_map_.end())
continue;
// Inform the grid cell that it has been invalidated in this frame.
invalidated = picture_it->second.Invalidate(frame_number) || invalidated;
}
// Expand invalidation that is outside tiles that intersect the interest
// rect. These tiles are no longer valid and should be considerered fully
// invalid, so we can know to not keep around raster tiles that intersect
// with these recording tiles.
gfx::Rect invalid_rect_outside_interest_rect_tiles = invalid_rect;
// TODO(danakj): We should have a Rect-subtract-Rect-to-2-rects operator
// instead of using Rect::Subtract which gives you the bounding box of the
// subtraction.
invalid_rect_outside_interest_rect_tiles.Subtract(interest_rect_over_tiles);
invalidation_expanded_to_full_tiles.Union(tiling_.ExpandRectToTileBounds(
invalid_rect_outside_interest_rect_tiles));
}
invalidation->Union(invalidation_expanded_to_full_tiles);
这个代码段定义在文件external/chromium_org/cc/resources/picture_pile.cc中。
这个代码段首先将Interest Rect对齐到分块边界,得到新的Interest Rect保存在本地变量interest_rect_over_tiles中。
这个代码段接下来遍历Layer的当前重绘区域的每一个Rect。对于每一个Rect,又会遍历它包含的每一个分块,并且将这些分块标记为可能需要重新绘制。PicturePile类有一个成员变量picturemap,它是从父类PicturePileBase类继承下来的,描述的是一个Layer上一次绘制的所有分块。每一个分块用一个PictureInfo对象描述,并且保存在PicturePileBase类的成员变量picture_map_描述的一个map中,保存的键值为分块的索引号。
将一个分块标记为需要重新绘制是通过调用与它相对应的一个PictureInfo对象的成员函数Invalidate实现的,如下所示:
bool PicturePileBase::PictureInfo::Invalidate(int frame_number) {
......
bool did_invalidate = !!picture_;
picture_ = NULL;
return did_invalidate;
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_base.cc中。
当一个PictureInfo对象的成员变量picture_指向了一个Picture对象的时候,就说明与它对应的分块已经绘制过了。另一方面,当一个PictureInfo对象的成员变量picture_等于NULL的时候,就说明与它对应的分块还没有进行绘制。因引,将一个PictureInfo对象的成员变量picture_设置为NULL,就可以将它标记为可能需要重新绘制。
回到前面的代码片段中,它除了将位于Layer重绘区域的分块标记为可能需要重新绘制之外,还会做另外一件事情,也就是计算Layer当前重绘区域位于Interest Rect之外的那部分区域,并且会将这些区域对齐到分块边界。这些对齐后的区域保存在本地变量invalidation_expanded_to_full_tiles中。结束遍历后,保存在本地变量invalidation_expanded_to_full_tiles中的区域会被合并到Layer重绘区域中去。这一步做的事情实际上就是将Layer当前重绘区域位于Interest Rect之外的那部分区域扩大至分块边界。
总结一下,这个代码做了两件事情:
1. 将位于Layer当前重绘区域中的、上次绘制过的分块标记为可能需要重新绘制。
2. 将Layer当前重绘区域中位于Interest Rect之外的那部分区域扩大至分块边界。
PicturePile类的成员函数UpdateAndExpandInvalidation收集那些真正需要重新绘制的分块,如下所示:
// Make a list of all invalid tiles; we will attempt to
// cluster these into multiple invalidation regions.
std::vector<gfx::Rect> invalid_tiles;
bool include_borders = true;
for (TilingData::Iterator it(&tiling_, interest_rect, include_borders); it;
++it) {
const PictureMapKey& key = it.index();
PictureInfo& info = picture_map_[key];
......
int distance_to_visible =
rect.ManhattanInternalDistance(visible_layer_rect);
if (info.NeedsRecording(frame_number, distance_to_visible)) {
gfx::Rect tile = tiling_.TileBounds(key.first, key.second);
invalid_tiles.push_back(tile);
} else if (!info.GetPicture()) {
......
// If a tile in the interest rect is not recorded, the entire tile needs
// to be considered invalid, so that we know not to keep around raster
// tiles that intersect this recording tile.
invalidation->Union(tiling_.TileBounds(it.index_x(), it.index_y()));
}
}
这个代码段定义在文件external/chromium_org/cc/resources/picture_pile.cc中。
位于Interest Rect内的分块在满足以下条件时,就需要进行绘制:
1. 它被标记为可能需要重新绘制,也就是与它对应的一个PictureInfo对象的成员变量picture_的值等于NULL。这意味着这个分块以前从来没有被绘制过,或者以前被绘制过,但是现在由于处理Layer的当前重绘区域中,被要求进行重新绘制。
2. 它与Viewport的距离小于预设阀值,这个预设阀值为512。
这两个条件可以通过调用PictureInfo类的成员函数NeedsRecording判断是否满足。如果一个分块满足上述两个条件,那么它就会保存在本地变量invalid_tiles描述的一个std::vector中。
如果一个分块不能满足绘制条件,并且是因为与它对应的一个PictureInfo对象的成员变量picture_的值等于NULL引起的,那么就需要将它对齐分块边界后,记录在Layer的当前重绘区域中。之所以要这样做,是因为这些分块以前可能被绘制过。被绘制过的分块曾经被执行过光栅化操作,也就是CC模块为它们分配过纹理资源。现在不需要这些分块了,就要回收以前分配给它们的纹理资源。因此就需要将它们记录在Layer的当前重绘区域中,以便以后可以执行资源回收操作。
如果一个分块不能满足绘制条件,并且是与它对应的一个PictureInfo对象的成员变量picture_的值不等于NULL,那么就说明这个分块位于Interest Rect内,并且不在Layer的当前重绘区域,以前它被绘制过。这类分块就是属于没有发生变化的,并且以前也执行过绘制操作,于是就是复用上一次的绘制结果。
总结一下,这段代码做了两件事情:
1. 收集真正需要进行绘制的分块。这些分块一定是位于Interest Rect内,同时也可能位于Layer的当前重绘区域内。如果位于Layer的当前重绘区域内,有两种情况。第一种是之前被绘制过,那么现在就会被重新绘制。第二种是之前没有绘制过,那么现在就会进行绘制。如果不位于Layer的当前重绘区域内,那么就说明之前没有绘制过。
2. 将位于Interest Rect内,以前绘制过,但是现在不需要绘制的分块记录在Layer的当前重绘区域,以便后面可以回收为这些分块分配的光栅化资源。
我们通过图6说明到目前为止,PicturePile类的成员函数UpdateAndExpandInvalidation所完成的事情,如下所示:
图6 绘制分块收集和重绘区域边界对齐
第一件事情是将参数invalidation描述的Layer当前重绘区域会被对齐到分块边界,也就是将图6左边的Invalid rect扩大到分块边界,得到图6右边所示的Invalid rect。
第二件事情收集需要绘制的分块。图6右边显示的Draw rect是一个大于Viewport小于Interest rect的区域,它里面的区域就是需要绘制。但是并不是Draw rect内的所有分块都是需要绘制的,因为有些之前已经绘制过了。只有那些之前没有绘制过,或者之前绘制过但是现在位于Invalid rect的分块才需要绘制。如果假设图6区域1的分块之前都是绘制过的,那么就仅有与Invalid rect重叠的区域2的分块才是需要重新的绘制的。
第三件事情是那些位于Layer当前重绘区域内的的分块记录起来,以便后面可以回收它们的资源,如图6右边区域2和区域3包含的分块。但是区域2和区域3的分块有一点区别,就是区域2的分块是需要重新绘制的,也是因为它们要重新绘制,所以才回收旧的资源,后面会给它们分配新的资源,区域3的分块不是需要重新绘制的,它们的资源回收后也不需要重新分配。
接下来我们分析PicturePile类的成员函数UpdateAndExpandInvalidation的最后一段代码:
std::vector<gfx::Rect> record_rects;
ClusterTiles(invalid_tiles, &record_rects);
......
for (std::vector<gfx::Rect>::iterator it = record_rects.begin();
it != record_rects.end();
it++) {
gfx::Rect record_rect = *it;
record_rect = PadRect(record_rect);
int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_);
scoped_refptr<Picture> picture;
......
{
......
for (int i = 0; i < repeat_count; i++) {
......
picture = Picture::Create(record_rect,
painter,
tile_grid_info_,
gather_pixel_refs,
num_raster_threads,
recording_mode);
......
}
......
}
......
bool include_borders = true;
for (TilingData::Iterator it(&tiling_, record_rect, include_borders); it;
++it) {
const PictureMapKey& key = it.index();
gfx::Rect tile = PaddedRect(key);
if (record_rect.Contains(tile)) {
PictureInfo& info = picture_map_[key];
info.SetPicture(picture);
......
}
}
......
}
has_any_recordings_ = true;
......
return true;
}
这个代码段定义在文件external/chromium_org/cc/resources/picture_pile.cc中。
从前面的分析可以知道,保存在本地变量invalid_tiles描述的一个std::vector中的区域都是需要绘制的。这些区域有可能是相邻的,因此这段代码调用函数ClusterTiles将相邻的区域合并起来形成一个大的区域。经过合并后,得到需要绘制的区域保存在另外一个本地变量record_rects描述的std::vector中。
这段代码接下来就遍历保存在本地变量record_rects描述的std::vector中的每一个区域,并且调用Picture类的成员函数Create为它创建一个Picture对象。在创建Picture对象的过程中,也会执行相应的绘制工作。
分析到这里我们就可以知道,PicturePile类的成员函数UpdateAndExpandInvalidation将网页分块的内容绘制在一系列的Picture对象中。其中,一个Picture对象涵盖多个分块的内容。每一个分块关联的Picture对象都会记录在与它对应的一个PicutreInfo对象中。这是通过调用PicutreInfo类的成员函数SetPicture实现的,如下所示:
void PicturePileBase::PictureInfo::SetPicture(scoped_refptr<Picture> picture) {
picture_ = picture;
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_base.cc中。
接下来我们继续分析Layer的一个区域的绘制过程,也就是Picture类的成员函数Create的实现,如下所示:
scoped_refptr<Picture> Picture::Create(
const gfx::Rect& layer_rect,
ContentLayerClient* client,
const SkTileGridFactory::TileGridInfo& tile_grid_info,
bool gather_pixel_refs,
int num_raster_threads,
RecordingMode recording_mode) {
scoped_refptr<Picture> picture = make_scoped_refptr(new Picture(layer_rect));
picture->Record(client, tile_grid_info, recording_mode);
......
return picture;
}
这个函数定义在文件external/chromium_org/cc/resources/picture.cc中。
参数layer_rect描述的是绘制区域的位置和大小,Picture类的成员函数Create再以它为参数,创建一个Picture对象,然后调用这个Picture对象的成员函数Record对参数layer_rect描述的是区域进行绘制,如下所示:
void Picture::Record(ContentLayerClient* painter,
const SkTileGridFactory::TileGridInfo& tile_grid_info,
RecordingMode recording_mode) {
......
scoped_ptr<EXPERIMENTAL::SkRecording> recording;
......
skia::RefPtr<SkCanvas> canvas;
canvas = skia::SharePtr(recorder.beginRecording(
layer_rect_.width(), layer_rect_.height(), &factory));
......
canvas->save();
canvas->translate(SkFloatToScalar(-layer_rect_.x()),
SkFloatToScalar(-layer_rect_.y()));
SkRect layer_skrect = SkRect::MakeXYWH(layer_rect_.x(),
layer_rect_.y(),
layer_rect_.width(),
layer_rect_.height());
canvas->clipRect(layer_skrect);
gfx::RectF opaque_layer_rect;
painter->PaintContents(
canvas.get(), layer_rect_, &opaque_layer_rect, graphics_context_status);
canvas->restore();
picture_ = skia::AdoptRef(recorder.endRecording());
......
}
这个函数定义在文件external/chromium_org/cc/resources/picture.cc中。
Picture类的成员函数Record首先创建一个类型为SkCanvas的画布,接着将这个画布的裁剪区间设置为当前正在处理的Picture对象所描述的区域。
参数painter是从前面分析的PictureLayer类的成员函数Update传递进来的,来自于PictureLayer类的成员变量client_。从前面Chromium网页Layer Tree创建过程分析一文可以知道,PictureLayer类的成员变量client_指向的是一个WebContentLayerImpl对象。Picture类的成员函数Record接下来就调用这个WebContentLayerImpl对象的成员函数PaintContents绘制它所描述的Layer在本地变量canvas描述的一个画布上。
这里有两点需要注意:
本地变量canvas描述的画布是设置为裁剪区间的,并且这个裁剪区间刚好就是当前正在处理的Picture对象所描述的区域,这意味着虽然我们在调用WebContentLayerImpl类的成员函数PaintContents绘制一个Layer的时候,最终得到只是这个Layer的指定区域的内容。
WebContentLayerImpl类的成员函数PaintContents是通过调用传递给它的画布的API绘制Layer的内容的,也就是本地变量canvas描述的画布。这是一个特殊的画布,在调用它的API的时候,它只是记录了所调用的API及其调用参数,并没有真正执行绘制操作,也就是没有将绘制命令转化为像素。这一点对WebContentLayerImpl类的成员函数PaintContents是透明的,它只管调用画布的API绘制自己想要的内容。
绘制完成之后,通过画布关联的SkRecording对象可以获得一个SkPicture对象。这个SkPicture对象在内容记录了所有的绘制命令。它的作用就类似于Android应用程序UI硬件加速渲染中的Display List。关于Android应用程序UI硬件加速渲染中的Display List,可以参考前面Android应用程序UI硬件加速渲染的Display List渲染过程分析一文。
以后对CC Pending Layer Tree执行光栅化操作时,就需要真正执行记录在上述SkPicture对象中的绘制命令,这个过程称为Replay。后面我们分析CC Pending Layer Tree的光栅化过程时就会看到这一点。
接下来,我们继续分析WebContentLayerImpl类的成员函数PaintContents的实现,以便了解CC Layer Tree的绘制过程,如下所示:
void WebContentLayerImpl::PaintContents(
SkCanvas* canvas,
const gfx::Rect& clip,
gfx::RectF* opaque,
ContentLayerClient::GraphicsContextStatus graphics_context_status) {
......
blink::WebFloatRect web_opaque;
client_->paintContents(
canvas,
clip,
can_use_lcd_text_,
web_opaque,
graphics_context_status == ContentLayerClient::GRAPHICS_CONTEXT_ENABLED
? blink::WebContentLayerClient::GraphicsContextEnabled
: blink::WebContentLayerClient::GraphicsContextDisabled);
*opaque = web_opaque;
}
这个函数定义在文件external/chromium_org/content/renderer/compositor_bindings/web_content_layer_impl.cc中。
从前面Chromium网页Layer Tree创建过程分析一文可以知道,WebContentLayerImpl类的成员变量client_指向WebKit中的一个OpaqueRectTrackingContentLayerDelegate对象,WebContentLayerImpl类的成员函数PaintContents调用这个OpaqueRectTrackingContentLayerDelegate对象的成员函数paintContents绘制当前正在处理的WebContentLayerImpl对象描述的Layer的内容。
OpaqueRectTrackingContentLayerDelegate类的成员函数paintContents的实现如下所示:
void OpaqueRectTrackingContentLayerDelegate::paintContents(
SkCanvas* canvas, const WebRect& clip, bool canPaintLCDText, WebFloatRect& opaque,
blink::WebContentLayerClient::GraphicsContextStatus contextStatus)
{
......
GraphicsContext context(canvas,
contextStatus == blink::WebContentLayerClient::GraphicsContextEnabled ? GraphicsContext::NothingDisabled : GraphicsContext::FullyDisabled);
......
m_painter->paint(context, clip);
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/OpaqueRectTrackingContentLayerDelegate.cpp中。
OpaqueRectTrackingContentLayerDelegate类的成员函数paintContents首先将参数canvas描述的画布封装在一个GraphicsContext对象中。这个GraphicsContext对象提供了一套绘图API,这些API最终会将绘制命令转发给参数canvas描述的画布提供的绘图API处理。
OpaqueRectTrackingContentLayerDelegate类的成员变量m_painter指向的是一个GraphicsLayer对象。从前面Chromium网页Graphics Layer Tree创建过程分析一文可以知道,这个GraphicsLayer对象描述的是网页的Graphics Layer Tree中的一个Layer。OpaqueRectTrackingContentLayerDelegate类的成员函数paintContents调用这个GraphicsLayer对象的成员函数paint就可以绘制它所描述的Layer的内容。
接下来我们继续分析GraphicsLayer类的成员函数paint的实现,以便了解一个Graphics Layer的绘制过程,如下所示:
void GraphicsLayer::paint(GraphicsContext& context, const IntRect& clip)
{
paintGraphicsLayerContents(context, clip);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/platform/graphics/GraphicsLayer.cpp中。
GraphicsLayer类的成员函数paint调用另外一个成员函数paintGraphicsLayerContents绘制当前正在处理的Graphics Layer的内容,如下所示:
void GraphicsLayer::paintGraphicsLayerContents(GraphicsContext& context, const IntRect& clip)
{
......
m_client->paintContents(this, context, m_paintingPhase, clip);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/CompositedLayerMapping.cpp中。
GraphicsLayer类的成员变量m_client指向的是一个CompositedLayerMapping对象,GraphicsLayer类的成员函数paintGraphicsLayerContents调用这个CompositedLayerMapping对象的成员函数paintContents绘制当前正在处理的Graphics Layer的内容,如下所示:
void CompositedLayerMapping::paintContents(const GraphicsLayer* graphicsLayer, GraphicsContext& context, GraphicsLayerPaintingPhase paintingPhase, const IntRect& clip)
{
......
if (graphicsLayer == m_graphicsLayer.get()
|| graphicsLayer == m_foregroundLayer.get()
|| graphicsLayer == m_backgroundLayer.get()
|| graphicsLayer == m_maskLayer.get()
|| graphicsLayer == m_childClippingMaskLayer.get()
|| graphicsLayer == m_scrollingContentsLayer.get()
|| graphicsLayer == m_scrollingBlockSelectionLayer.get()) {
GraphicsLayerPaintInfo paintInfo;
paintInfo.renderLayer = &m_owningLayer;
paintInfo.compositedBounds = compositedBounds();
paintInfo.offsetFromRenderer = graphicsLayer->offsetFromRenderer();
paintInfo.paintingPhase = paintingPhase;
paintInfo.isBackgroundLayer = (graphicsLayer == m_backgroundLayer);
// We have to use the same root as for hit testing, because both methods can compute and cache clipRects.
doPaintTask(paintInfo, &context, clip);
} else if (graphicsLayer == m_squashingLayer.get()) {
ASSERT(compositor()->layerSquashingEnabled());
for (size_t i = 0; i < m_squashedLayers.size(); ++i)
doPaintTask(m_squashedLayers[i], &context, clip);
} else if (graphicsLayer == layerForHorizontalScrollbar()) {
paintScrollbar(m_owningLayer.scrollableArea()->horizontalScrollbar(), context, clip);
} else if (graphicsLayer == layerForVerticalScrollbar()) {
paintScrollbar(m_owningLayer.scrollableArea()->verticalScrollbar(), context, clip);
} else if (graphicsLayer == layerForScrollCorner()) {
const IntRect& scrollCornerAndResizer = m_owningLayer.scrollableArea()->scrollCornerAndResizerRect();
context.save();
context.translate(-scrollCornerAndResizer.x(), -scrollCornerAndResizer.y());
IntRect transformedClip = clip;
transformedClip.moveBy(scrollCornerAndResizer.location());
m_owningLayer.scrollableArea()->paintScrollCorner(&context, IntPoint(), transformedClip);
m_owningLayer.scrollableArea()->paintResizer(&context, IntPoint(), transformedClip);
context.restore();
}
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/CompositedLayerMapping.cpp中。
从前面Chromium网页Graphics Layer Tree创建过程分析一文可以知道,网页的Render Layer Tree中的一个Render Layer实际上对应的是一个Graphics Layer子树。这个Graphics Layer子树由一个CompositedLayerMapping对象管理。
参数graphicsLayer指向的就是上述Graphics Layer子树中的某一个Graphics Layer。不同的Graphics Layer有不同的绘制方式。例如,对于描述网页主要内容的Main Graphics Layer,CompositedLayerMapping类的成员函数paintContents是通过调用另外一个成员函数doPaintTask绘制它的内容的。
接下来我们就继续分析CompositedLayerMapping类的成员函数doPaintTask的实现,如下所示:
void CompositedLayerMapping::doPaintTask(GraphicsLayerPaintInfo& paintInfo, GraphicsContext* context,
const IntRect& clip) // In the coords of rootLayer.
{
......
if (paintInfo.renderLayer->compositingState() != PaintsIntoGroupedBacking) {
......
LayerPaintingInfo paintingInfo(paintInfo.renderLayer, dirtyRect, PaintBehaviorNormal, paintInfo.renderLayer->subpixelAccumulation());
paintInfo.renderLayer->paintLayerContents(context, paintingInfo, paintFlags);
......
} else {
......
LayerPaintingInfo paintingInfo(paintInfo.renderLayer, dirtyRect, PaintBehaviorNormal, paintInfo.renderLayer->subpixelAccumulation());
......
paintInfo.renderLayer->paintLayer(context, paintingInfo, paintFlags);
......
}
.....
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/compositing/CompositedLayerMapping.cpp中。
参数paintInfo描述的GraphicsLayerPaintInfo对象的成员变量renderLayer描述的是当前要绘制的Render Layer。当一个Render Layer拥有自己的Graphics Layer时,它会绘制自己的Backing Store中,否则的话,它与其它的Render Layer一起绘制在别的Backing Store中。
我们假设当前要绘制的Render Layer拥有自己的Graphics Layer,这时候调用它的成员函数compositingState得到的返回值不等于PaintsIntoGroupedBacking,因此接下来CompositedLayerMapping类的成员函数doPaintTask就会调用RenderLayer类的成员函数paintLayerContents对它进行绘制。
RenderLayer类的成员函数paintLayerContents的实现如下所示:
void RenderLayer::paintLayerContents(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
......
LayerFragments layerFragments;
if (shouldPaintContent || shouldPaintOutline || isPaintingOverlayScrollbars) {
// Collect the fragments. This will compute the clip rectangles and paint offsets for each layer fragment, as well as whether or not the content of each
// fragment should paint.
collectFragments(layerFragments, localPaintingInfo.rootLayer, localPaintingInfo.paintDirtyRect,
(paintFlags & PaintLayerTemporaryClipRects) ? TemporaryClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize,
shouldRespectOverflowClip(paintFlags, renderer()), &offsetFromRoot, localPaintingInfo.subPixelAccumulation);
updatePaintingInfoForFragments(layerFragments, localPaintingInfo, paintFlags, shouldPaintContent, &offsetFromRoot);
}
if (shouldPaintBackground) {
paintBackgroundForFragments(layerFragments, context, transparencyLayerContext, paintingInfo.paintDirtyRect, haveTransparency,
localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags);
}
if (shouldPaintNegZOrderList)
paintChildren(NegativeZOrderChildren, context, localPaintingInfo, paintFlags);
if (shouldPaintOwnContents) {
paintForegroundForFragments(layerFragments, context, transparencyLayerContext, paintingInfo.paintDirtyRect, haveTransparency,
localPaintingInfo, paintBehavior, paintingRootForRenderer, selectionOnly, forceBlackText, paintFlags);
}
if (shouldPaintOutline)
paintOutlineForFragments(layerFragments, context, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags);
if (shouldPaintNormalFlowAndPosZOrderLists)
paintChildren(NormalFlowChildren | PositiveZOrderChildren, context, localPaintingInfo, paintFlags);
if (shouldPaintOverlayScrollbars)
paintOverflowControlsForFragments(layerFragments, context, localPaintingInfo, paintFlags);
......
if (shouldPaintMask)
paintMaskForFragments(layerFragments, context, localPaintingInfo, paintingRootForRenderer, paintFlags);
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
RenderLayer类的成员函数paintLayerContents首先调用成员函数collectFragments当前正在处理的Render Layer的Fragment。Fragment是CSS3定义的一个规范:CSS3 Fragmentation,WebKit的实现可以参考这篇文章:CSS Fragmentation In WebKit。当一个Render Layer对应的Render Object设置了Fragment相关的属性之后,这个Render Layer就会以Fragment的方式显示,也就是它由一系列的Fragment组成。
收集到了要绘制的Fragment之后,RenderLayer类的成员函数paintLayerContents大概就按照以下顺序绘制自己的内容:
1. Background
2. Z-index为负的子Render Layer
3. Foreground
4. Outline
5. Z-index为0和正数的子Render Layer
6. Scollbar
7. Mask
除了子Render Layer的内容是通过调用成员函数paintChildren进行绘制的,其余的内容是通过调用成员函数paintXXXForFragments进行绘制的,也就是说,Render Layer自身的内容是通过调用成员函数paintXXXForFragments进行绘制的。
我们以Render Layer的Background的绘制为例,分析Render Layer自有内容的绘制过程。Render Layer的Background是通过调用RenderLayer类的成员函数paintBackgroundForFragments绘制的,它的实现如下所示:
void RenderLayer::paintBackgroundForFragments(const LayerFragments& layerFragments, GraphicsContext* context, GraphicsContext* transparencyLayerContext,
const LayoutRect& transparencyPaintDirtyRect, bool haveTransparency, const LayerPaintingInfo& localPaintingInfo, PaintBehavior paintBehavior,
RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags)
{
for (size_t i = 0; i < layerFragments.size(); ++i) {
const LayerFragment& fragment = layerFragments.at(i);
......
// Paint the background.
// FIXME: Eventually we will collect the region from the fragment itself instead of just from the paint info.
PaintInfo paintInfo(context, pixelSnappedIntRect(fragment.backgroundRect.rect()), PaintPhaseBlockBackground, paintBehavior, paintingRootForRenderer, 0, 0, localPaintingInfo.rootLayer->renderer());
renderer()->paint(paintInfo, toPoint(fragment.layerBounds.location() - renderBoxLocation() + subPixelAccumulationIfNeeded(localPaintingInfo.subPixelAccumulation, compositingState())));
......
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
调用RenderLayer类的成员函数renderer可以获得一个Render Object。这个Render Object是绘制在当前正在处理的Render Layer上的。这个Render Object对应于Render Object Tree的一个节点,它代表的是网页中的一个元素,并且知道如何绘制这个元素。
RenderLayer类的成员函数paintBackgroundForFragments遍历当前正在处理的Render Layer的每一个Fragment。对于每一个Fragment,都调用与当前正在处理的Render Layer对应的一个Render Object的成员函数paint进行绘制。
我们假设当前正在处理的Render Layer对应的一个Render Object是一个Render Block,那么接下来RenderLayer类的成员函数paintBackgroundForFragments就会调用RenderBlock类的成员函数paint绘制当前正在处理的Render Layer的Fragment,如下所示:
void RenderBlock::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
......
{
......
paintObject(paintInfo, adjustedPaintOffset);
}
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。
RenderBlock类的成员函数paint主要是调用另外一个成员函数paintObject绘制当前正在处理的Render Block的内容,如下所示:
void RenderBlock::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
PaintPhase paintPhase = paintInfo.phase;
......
// 1. paint background, borders etc
......
// 2. paint contents
if (paintPhase != PaintPhaseSelfOutline) {
if (hasColumns())
paintColumnContents(paintInfo, scrolledOffset);
else
paintContents(paintInfo, scrolledOffset);
}
// 3. paint selection
......
// 4. paint floats.
......
// 5. paint outline.
......
// 6. paint continuation outlines.
......
// 7. paint caret.
......
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。
Render Block的内容是分阶段绘制的。我们假设现在是绘制内容阶段,并且假设当前正在绘制的Render Block没有设置column属性。在这种情况下,RenderBlock类的成员函数paint调用另外一个成员函数paintContents绘制它的内容,如下所示:
void RenderBlock::paintContents(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
......
if (childrenInline())
m_lineBoxes.paint(this, paintInfo, paintOffset);
else {
PaintPhase newPhase = (paintInfo.phase == PaintPhaseChildOutlines) ? PaintPhaseOutline : paintInfo.phase;
newPhase = (newPhase == PaintPhaseChildBlockBackgrounds) ? PaintPhaseChildBlockBackground : newPhase;
// We don't paint our own background, but we do let the kids paint their backgrounds.
PaintInfo paintInfoForChild(paintInfo);
paintInfoForChild.phase = newPhase;
paintInfoForChild.updatePaintingRootForChildren(this);
paintChildren(paintInfoForChild, paintOffset);
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。
Render Block的内容来自于它的子Render Object。我们假设当前正在绘制的Render Block的子Render Object不是以inline方式显示。在这种情况下,RenderBlock类的成员函数paintContents调用另外一个成员函数paintChildren绘制它的子Render Object的内容,如下所示:
void RenderBlock::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox())
paintChild(child, paintInfo, paintOffset);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。
RenderBlock类的成员函数paintChildren遍历它的所有子Render Object,并且分别调用成员函数paintChild绘制这些子Render Object,如下所示:
void RenderBlock::paintChild(RenderBox* child, PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
LayoutPoint childPoint = flipForWritingModeForChild(child, paintOffset);
if (!child->hasSelfPaintingLayer() && !child->isFloating())
child->paint(paintInfo, childPoint);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderBlock.cpp中。
当参数child描述的子Render Object不拥有自己的Render Layer,并且它也没有设置float属性的时候,RenderBlock类的成员函数paintChild就会调用它的成员函数paint进行绘制。假设这个子Render Object也是一个Render Block,那么RenderBlock类的成员函数paintChild就会调用前面分析过的成员函数paint对这个子Render Object进行绘制。这是一个递归调用过程,直到中间某个Render Object的所有子Render Object都具有自己的Render Layer为止,或者都设置了float属性。
这样,一个Render Layer的内容就绘制完成了。回到前面分析的RenderLayer类的成员函数paintLayerContents中,它除了绘制自已的内容,也会绘制它的子Render Layer的内容。这是通过调用RenderLayer类的成员函数paintChildren完成的,如下所示:
void RenderLayer::paintChildren(unsigned childrenToVisit, GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
......
RenderLayerStackingNodeIterator iterator(*m_stackingNode, childrenToVisit);
while (RenderLayerStackingNode* child = iterator.next()) {
RenderLayer* childLayer = child->layer();
......
if (!childLayer->isPaginated())
childLayer->paintLayer(context, paintingInfo, paintFlags);
else
paintPaginatedChildLayer(childLayer, context, paintingInfo, paintFlags);
}
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
RenderLayer类的成员函数paintChildren按照我们在前面Chromium网页Graphics Layer Tree创建过程分析一文提到的Stacking Context顺序绘制当前正在处理的Render Layer的所有子Render Layer。对于设置了分页的子Render Layer,RenderLayer类的成员函数paintChildren调用另外一个成员函数paintPaginatedChildLayer绘制它们的内容;而对于没有设置分页的子Render Layer,RenderLayer类的成员函数paintChildren直接它们的成员函数paintLayer对它们进行绘制。
接下来我们只关注没有设置分页的子Render Layer的绘制过程,也就是RenderLayer类的成员函数paintLayer的实现,如下所示:
void RenderLayer::paintLayer(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
......
// Non self-painting leaf layers don't need to be painted as their renderer() should properly paint itself.
if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant())
return;
......
paintLayerContentsAndReflection(context, paintingInfo, paintFlags);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
从前面的调用过程可以知道,参数context描述的是当前正在处理的Render Layer的父Render Layer的绘图上下文,因此只有在当前正在处理的Render Layer不具有自己的Graphics Layer的时候,它的内容才会绘制在父Render Layer拥有的Graphics Layer上,也就是参数context描述的绘图上下文上。
我们假设当前正在处理的Render Layer没有自己的Graphics Layer,也就是它要绘制在父Render Layer的绘图上下文上。在这种情况下,RenderLayer类的成员函数paintLayer就会调用另外一个成员函数paintLayerContentsAndReflection绘制它的内容,如下所示:
void RenderLayer::paintLayerContentsAndReflection(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
......
PaintLayerFlags localPaintFlags = paintFlags & ~(PaintLayerAppliedTransform);
// Paint the reflection first if we have one.
if (m_reflectionInfo)
m_reflectionInfo->paint(context, paintingInfo, localPaintFlags | PaintLayerPaintingReflection);
localPaintFlags |= PaintLayerPaintingCompositingAllPhases;
paintLayerContents(context, paintingInfo, localPaintFlags);
}
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/rendering/RenderLayer.cpp中。
如果当前正在处理的Render Layer设置了box-reflect属性,那么RenderLayer类的成员函数paintLayerContentsAndReflection就会先绘制它的倒影,接着再调用前面分析过的成员函数paintLayerContents绘制它自己的内容。这是一个递归的绘制过程,直到当前正在处理的Render Layer的所有子Render Layer都有自己的绘图上下文为止。
这一步执行完成之后,回到前面分析的LayerTreeHost类的成员函数UpdateLayers中,这时候网页的CC Layer Tree的内容就绘制完成了,不过仅仅是将绘制命令记录在一系列的Picture对象中。
LayerTreeHost类的成员函数UpdateLayers执行完成后,返回到ThreadProxy类的成员函数BeginMainFrame中,这时候它就会向Compositor线程的消息队列发送一个Task。这个Task绑定了ThreadProxy类的成员函数StartCommitOnImplThread。这意味着接下来ThreadProxy类的成员函数StartCommitOnImplThread会在Compositor线程中执行。注意,在Compositor线程执行ThreadProxy类的成员函数StartCommitOnImplThread期间,Main线程通过一个Completion Event进入等待状态。
ThreadProxy类的成员函数StartCommitOnImplThread的实现如下所示:
void ThreadProxy::StartCommitOnImplThread(CompletionEvent* completion,
ResourceUpdateQueue* raw_queue) {
......
impl().scheduler->NotifyBeginMainFrameStarted();
scoped_ptr<ResourceUpdateQueue> queue(raw_queue);
......
impl().commit_completion_event = completion;
impl().current_resource_update_controller = ResourceUpdateController::Create(
this,
Proxy::ImplThreadTaskRunner(),
queue.Pass(),
impl().layer_tree_host_impl->resource_provider());
impl().current_resource_update_controller->PerformMoreUpdates(
impl().scheduler->AnticipatedDrawTime());
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
在前面Chromium网页Layer Tree创建过程分析一文中,我们假设Render进程启用了Impl Side Painting特性。在这种情况下,参数raw_queue描述的一个Resoure Update Queue为空。
如果Render进程没有启用Impl Side Painting特性,并且网页包含了img标签,那么CC模块就会分别为这些img标签创建一个Image Layer,并且将要显示的图片设置给这些Image Layer。这些Image Layer在绘制的时候,同样会对要显示的图片进行分块。得到的每一个图片分块就是一个Resource Update。这些Resource Update就保存在参数raw_queue描述的Resoure Update Queue中。
Resoure Update描述的图片分块数据是通过调用一个ResourceUpdateController对象的成员函数PerformMoreUpdates上传到GPU去当纹理渲染的。这个ResourceUpdateController对象可以通过调用ResourceUpdateController类的静态成员函数Create创建。
即使参数raw_queue描述的Resoure Update Queue为空,ThreadProxy类的成员函数StartCommitOnImplThread也会创建一个ResourceUpdateController对象,并且调用它的成员函数PerformMoreUpdates。但是这个ResourceUpdateController对象的成员函数PerformMoreUpdates在执行的时候,会判断它的Resoure Update Queue是否为空。如果为空,它就会跳过上述的GPU纹理数据上传操作。
ThreadProxy类的成员函数StartCommitOnImplThread在调用ResourceUpdateController类的成员函数PerformMoreUpdates之前,还会做两件事情。第一件事情就是调用Scheduler类的成员函数NotifyBeginMainFrameStarted通知调度器修改状态。第二件事情是将参数completion描述的一个Completion Event保存在内部的一个CompositorThreadOnly对象的成员变量commit_completion_event。后面Compositor线程要通过这个Completion Event唤醒目前正在睡眠的Main线程。
接下来我们先分析Scheduler类的成员函数NotifyBeginMainFrameStarted的实现,以便了解调度器的状态迁移过程,如下所示:
void Scheduler::NotifyBeginMainFrameStarted() {
......
state_machine_.NotifyBeginMainFrameStarted();
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数NotifyBeginMainFrameStarted调用SchedulerStateMachine类的成员函数NotifyBeginMainFrameStarted修改状态机的CommitState状态,如下所示:
void SchedulerStateMachine::NotifyBeginMainFrameStarted() {
DCHECK_EQ(commit_state_, COMMIT_STATE_BEGIN_MAIN_FRAME_SENT);
commit_state_ = COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED;
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
从前面的分析可以知道,Main线程在绘制CC Layer Tree的时候,状态机的CommitState状态等于COMMIT_STATE_BEGIN_MAIN_FRAME_SENT。现在CC Layer Tree已经绘制完成了,CommitState状态就会从COMMIT_STATE_BEGIN_MAIN_FRAME_SENT变迁为COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED。表示Compositor线程正在上传网页中的图片数据到GPU去。
回到ThreadProxy类的成员函数StartCommitOnImplThread中,它接下来会调用ResourceUpdateController类的成员函数PerformMoreUpdates上传网页中的图片数据到GPU去,如下所示:
void ResourceUpdateController::PerformMoreUpdates(
base::TimeTicks time_limit) {
time_limit_ = time_limit;
......
// Post a 0-delay task when no updates were left. When it runs,
// ReadyToFinalizeTextureUpdates() will be called.
if (!UpdateMoreTexturesIfEnoughTimeRemaining()) {
task_posted_ = true;
task_runner_->PostTask(
FROM_HERE,
base::Bind(&ResourceUpdateController::OnTimerFired,
weak_factory_.GetWeakPtr()));
}
......
}
这个函数定义在文件external/chromium_org/cc/resources/resource_update_controller.cc中。
参数time_limit规定了上传图片数据到GPU最多可以使用的时间。一旦超出这个时间,ResourceUpdateController类会通过成员变量task_runner_描述的一个SingleThreadTaskRunner向Compositor线程的消息队列发送一个Task。这个Task绑定了ResourceUpdateController类的成员函数OnTimerFired。
此外,如果没有图片数据需要上传到GPU去,也就是前面提到的Resource Update Queue为空。这时候调用ResourceUpdateController类的成员函数UpdateMoreTexturesIfEnoughTimeRemaining得到的返回值就为false。在这种情况下,ResourceUpdateController类的成员函数PerformMoreUpdates会直接向Compositor线程的消息队列发送上述的Task。
在我们这个情景中,Resource Update Queue为空。因此,ResourceUpdateController类的成员函数PerformMoreUpdates会马上向Compositor线程的消息队列发送上述的Task。这意味着接下来ResourceUpdateController类的成员函数OnTimerFired会在Compositor线程中执行。
ResourceUpdateController类的成员函数OnTimerFired会在Compositor的实现如下所示:
void ResourceUpdateController::OnTimerFired() {
......
if (!UpdateMoreTexturesIfEnoughTimeRemaining()) {
.....
client_->ReadyToFinalizeTextureUpdates();
}
}
这个函数定义在文件external/chromium_org/cc/resources/resource_update_controller.cc中。
前面提到,Resource Update Queue为空时,调用ResourceUpdateController类的成员函数UpdateMoreTexturesIfEnoughTimeRemaining得到的返回值就为false。因此,ResourceUpdateController类的成员函数OnTimerFired会直接调用成员变量client_指向的一个ThreadProxy对象的成员函数ReadyToFinalizeTextureUpdates,用来通知它现在可以将刚刚绘制好的CC Layer Tree同步到一个新的CC Pending Layer Tree去。
ThreadProxy类的成员函数ReadyToFinalizeTextureUpdates的实现如下所示:
void ThreadProxy::ReadyToFinalizeTextureUpdates() {
DCHECK(IsImplThread());
impl().scheduler->NotifyReadyToCommit();
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类的成员函数ReadyToFinalizeTextureUpdates调用Scheduler类的成员函数NotifyReadyToCommit修改状态机的状态,如下所示:
void Scheduler::NotifyReadyToCommit() {
......
state_machine_.NotifyReadyToCommit();
ProcessScheduledActions();
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数NotifyReadyToCommit首先调用SchedulerStateMachine类的成员函数NotifyReadyToCommit修改状态机的CommitState状态,如下所示:
void SchedulerStateMachine::NotifyReadyToCommit() {
DCHECK(commit_state_ == COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED) << *AsValue();
commit_state_ = COMMIT_STATE_READY_TO_COMMIT;
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数NotifyReadyToCommit将状态机的CommitState状态从COMMIT_STATE_BEGIN_MAIN_FRAME_STARTED迁移为COMMIT_STATE_READY_TO_COMMIT。
回到Scheduler类的成员函数NotifyReadyToCommit中,它将状态机的CommitState状态修改为COMMIT_STATE_READY_TO_COMMIT之后,再调用成员函数ProcessScheduledActions时,就会触发调度器执行图1所示的第3个操作ACTION_COMMIT,也就是将刚刚绘制好的CC Layer Tree同步到一个新的CC Pending Layer Tree中去。
至此,我们就分析完成网页的CC Layer Tree的绘制过程了。这里我们做一个小结:
1. 在绘制CC Layer Tree之前,CC模块会先计算CC Layer Tree的动画和布局。
2. 在绘制CC Layer Tree之后,如果网页包含有图片资源,并且Render进程没有启用Impl Side Painting特性,那么CC模块就会对这些图片进行分块,分块得到的数据将会上传到GPU去以纹理方式渲染。
3. CC Layer Tree是以Layer为单位绘制CC Layer Tree的。每一个Layer又被划分为若干个分块进行绘制。每一个分块在绘制的时候,仅仅是绘制命令记录在了一个Picture对象。
上述三个操作执行完成之后,调度器就会通知Compositor线程将刚刚绘制好的CC Layer Tree同步到一个新的CC Pending Layer Tree中去。这个同步过程我们在接下来一篇文章中就详细进行分析。敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8