CC Layer Tree绘制完成后,会同步到一个新的CC Pending Layer Tree去。同步过程由Compositor线程执行,并且Main线程处于等待状态。所谓同步,就是将CC Layer Tree的内容拷贝到CC Pending Layer Tree去。同步完毕,Main线程就会被唤醒。本文接下来分析CC Layer Tree同步为CC Pending Layer Tree的过程。
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!
CC Layer Tree同步为CC Pending Layer Tree的操作是由调度器发起的,它对应于网页渲染过程中的第3个步骤ACTION_COMMIT,如下所示:
图1 CC Layer Tree同步为CC Pending Layer Tree的时机
从前面Chromium网页渲染调度器(Scheduler)实现分析一文可以知道,当调度器调用SchedulerStateMachine类的成员函数NextAction询问状态机下一步要执行的操作时,SchedulerStateMachine类的成员函数NextAction会调用另外一个成员函数ShouldCommit。当SchedulerStateMachine类的成员函数ShouldCommit返回值等于true的时候,状态机就会提示调度器接下来需要执行ACTION_COMMIT操作,也就是将CC Layer Tree同步为CC Pending Layer Tree,如下所示:
SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
......
if (ShouldCommit())
return ACTION_COMMIT;
......
return ACTION_NONE;
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
接下来我们就继续分析SchedulerStateMachine类的成员函数ShouldCommit什么情况下会返回true,它的实现如下所示:
bool SchedulerStateMachine::ShouldCommit() const {
if (commit_state_ != COMMIT_STATE_READY_TO_COMMIT)
return false;
// We must not finish the commit until the pending tree is free.
if (has_pending_tree_) {
DCHECK(settings_.main_frame_before_activation_enabled);
return false;
}
// Prioritize drawing the previous commit before finishing the next commit.
if (active_tree_needs_first_draw_)
return false;
return true;
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数ShouldCommit返回true需要满足三个条件:
1. 状态机的CommitState状态等于COMMIT_STATE_READY_TO_COMMIT。从前面Chromium网页Layer Tree绘制过程分析一文可以知道,当CC Layer Tree绘制完成时,会将状态机的CommitState状态设置为COMMIT_STATE_READY_TO_COMMIT。
2. 成员变量has_pending_tree_的值等于false。当这个成员变量的值等于true的时候,表示现在有一个CC Pending Layer Tree正在执行光栅化操作。要等到这个CC Pending Layer Tree的光栅化操作完成,并且激活为CC Active Layer Tree之后,才可以执行下一个ACTION_COMMIT操作。这是因为执行ACTION_COMMIT操作的时候又会产生一个新的CC Pending Layer Tree。同一时刻只能存在一个CC Pending Layer Tree。
3. 成员变量active_tree_needs_first_draw_的值等于false。当这个成员变量的值等于true的时候,表示现在有一个CC Active Layer Tree刚刚被激活,但是这个刚刚被激活的CC Active Layer Tree还没有被执行过渲染操作。渲染操作的优先级大于ACTION_COMMIT操作。因此,在这种情况下,就不允许执行ACTION_COMMIT操作。
回到SchedulerStateMachine类的成员函数NextAction中,当它调用成员函数ShouldCommit得到的返回值等于true,它就会返回一个ACTION_COMMIT给Scheduler类的成员函数ProcessScheduledActions。这时候Scheduler类的成员函数ProcessScheduledActions就会请求Compositor线程将刚刚绘制完成的CC Layer Tree同步为CC Pending Layer Tree,如下所示:
void Scheduler::ProcessScheduledActions() {
......
SchedulerStateMachine::Action action;
do {
action = state_machine_.NextAction();
......
state_machine_.UpdateState(action);
......
switch (action) {
......
case SchedulerStateMachine::ACTION_COMMIT:
client_->ScheduledActionCommit();
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对象的成员函数ScheduledActionCommit请求Compositor线程将刚刚绘制好的CC Layer Tree同步为CC Pending Layer Tree。
SchedulerStateMachine类的成员函数UpdateState的实现如下所示:
void SchedulerStateMachine::UpdateState(Action action) {
switch (action) {
......
case ACTION_COMMIT: {
bool commit_was_aborted = false;
UpdateStateOnCommit(commit_was_aborted);
return;
}
......
}
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数UpdateState调用另外一个成员函数UpdateStateOnCommit修改状态机的CommitState状态。SchedulerStateMachine类的成员函数UpdateStateOnCommit的详细分析可以参考前面Chromium网页Layer Tree绘制过程分析一文,我们假设它将状态机的CommitState状态从COMMIT_STATE_READY_TO_COMMIT修改为COMMIT_STATE_WAITING_FOR_ACTIVATION,表示调度器正在等待一个新的CC Pending Layer Tree被激活为CC Active Layer Tree。
回到Scheduler类的成员函数ProcessScheduledActions中,它修改了状态机的CommitState状态之后,接下来调用ThreadProxy类的成员函数ScheduledActionCommit请求Compositor线程将刚刚绘制好的CC Layer Tree同步为CC Pending Layer Tree,如下所示:
void ThreadProxy::ScheduledActionCommit() {
......
impl().layer_tree_host_impl->BeginCommit();
......
layer_tree_host()->FinishCommitOnImplThread(
impl().layer_tree_host_impl.get());
......
bool hold_commit = layer_tree_host()->settings().impl_side_painting &&
blocked_main().commit_waits_for_activation;
......
if (hold_commit) {
......
impl().completion_event_for_commit_held_on_tree_activation =
impl().commit_completion_event;
impl().commit_completion_event = NULL;
} else {
impl().commit_completion_event->Signal();
impl().commit_completion_event = NULL;
}
......
impl().layer_tree_host_impl->CommitComplete();
......
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
调用ThreadProxy类的成员函数impl可以获得一个CompositorThreadOnly对象。这个CompositorThreadOnly对象的成员变量layer_tree_host_impl指向一个LayerTreeHostImpl对象。这个LayerTreeHostImpl对象负责管理CC Pending Layer Tree和CC Active Layer Tree。ThreadProxy类的成员函数ScheduledActionCommit首先调用这个LayerTreeHostImpl对象的成员函数BeginCommit创建一个空的CC Pending Layer Tree。
ThreadProxy类的成员函数ScheduledActionCommit接下来又调用成员函数layer_tree_host获得一个LayerTreeHost对象。这个LayerTreeHost对象负责管理CC Layer Tree。有了这个LayerTreeHost对象之后,就可以调用它的成员函数FinishCommitOnImplThread将刚刚绘制好的CC Layer Tree同步到前面创建的CC Pending Layer Tree中去。
调用ThreadProxy类的成员函数blocked_main可以获得一个MainThreadOrBlockedMainThread对象。在Render进程启用了Impl Side Painting特性的情况下,当这个MainThreadOrBlockedMainThread对象的成员变量commit_waits_for_activation等于true时,表示CC Layer Tree中的某些Layer请求Compositor线程一直阻塞Main线程,直到前面创建的CC Pending Layer Tree完成光栅化并且激活为CC Active Layer Tree之后,再唤醒Main线程。这些Layer有一个特点,就是不需要Compositor线程为它们执行光栅化操作,例如Texture Layer,本身的内容就是已经光栅化好了的。
如果需要阻塞Main线程到CC Pending Layer Tree激活为CC Active Layer Tree之后,那么ThreadProxy类的成员函数ScheduledActionCommit就会将保存在前面获得的CompositorThreadOnly对象的成员变量commit_completion_event中的Completion Event转移到另外一个成员变量completion_event_for_commit_held_on_tree_activation中,以便在CC Pending Layer Tree激活为CC Acitve Layer Tree的时候,通过保存在后面这个成员变量中的Completion Event来唤醒Main线程。
另一方面,如果不需要阻塞Main线程到CC Pending Layer Tree激活为CC Active Layer Tree之后,那么现在就可以唤醒Main线程了,也就是调用保存在前面获得的CompositorThreadOnly对象的成员变量commit_completion_event中的Completion Event的成员函数Signal。
最后,ThreadProxy类的成员函数ScheduledActionCommit又调用前面获得的CompositorThreadOnly对象的成员变量layer_tree_host_impl指向的LayerTreeHostImpl对象的成员函数CommitComplete为前面已经同步好的CC Pending Layer Tree中的Layer创建分块,以便后面可以对这些分块执行光栅化操作。
接下来,我们就先分析LayerTreeHostImpl类的成员函数BeginCommit的实现,然后再分析LayerTreeHost类的成员函数FinishCommitOnImplThread以及LayerTreeHostImpl类的成员函数CommitComplete的实现,以便了解CC Layer Tree同步为CC Pending Layer Tree的过程,以及为CC Pending Layer Tree创建分块的过程。
LayerTreeHostImpl类的成员函数BeginCommit的实现如下所示:
void LayerTreeHostImpl::BeginCommit() {
......
if (settings_.impl_side_painting)
CreatePendingTree();
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
只有在Render进程启用Impl Side Painting特性情况下,才会存在CC Pending Layer Tree。因此,只有在Render进程启用Impl Side Painting特性情况下,LayerTreeHostImpl类的成员函数BeginCommit才会调用另外一个成员函数CreatePendingTree创建一个新的CC Pending Layer Tree,如下所示:
void LayerTreeHostImpl::CreatePendingTree() {
CHECK(!pending_tree_);
if (recycle_tree_)
recycle_tree_.swap(pending_tree_);
else
pending_tree_ = LayerTreeImpl::create(this);
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
LayerTreeHostImpl类的成员变量pending_tree_描述的是上一次创建的CC Pending Layer Tree。这个CC Pending Layer Tree正在执行光栅化操作。一旦这个光栅化操作执行完成,LayerTreeHostImpl类的成员变量pending_tree_的值就会被设置为NULL,同时它原先指向的一个LayerTreeImpl对象会被缓存在另外一个成员变量recycletree。等到下次需要再创建新的CC Pending Layer Tree时,LayerTreeHostImpl类的成员函数CreatePendingTree就会先检查成员变量recycle_tree_的值是否为NULL。如果不为NULL,就会复用它所指向的一个LayerTreeImpl对象,用作新的CC Pending Layer Tree。否则的话,就会调用LayerTreeImpl类的静态成员函数create创建一个新的CC Pending Layer Tree。无论如何,最终得到的CC Pending Layer Tree都保存在成员变量pending_tree_中。
LayerTreeImpl类的静态成员函数create的实现如下所示:
class CC_EXPORT LayerTreeImpl {
public:
static scoped_ptr<LayerTreeImpl> create(
LayerTreeHostImpl* layer_tree_host_impl) {
return make_scoped_ptr(new LayerTreeImpl(layer_tree_host_impl));
}
......
};
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_impl.h中。
从这里可以看到,LayerTreeImpl类的静态成员函数create创建的是一个LayerTreeImpl对象。这个LayerTreeImpl对象的创建过程,也就是LayerTreeImpl类的构造函数的实现如下所示:
LayerTreeImpl::LayerTreeImpl(LayerTreeHostImpl* layer_tree_host_impl)
: layer_tree_host_impl_(layer_tree_host_impl),
...... {
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_impl.cc中。
LayerTreeImpl类的构造函数主要是将参数layer_tree_host_impl指向的一个LayerTreeHostImpl对象保存在成员变量layer_tree_host_impl_中。
回到前面分析的ThreadProxy类的成员函数ScheduledActionCommit中,它调用LayerTreeHostImpl类的成员函数BeginCommit创建了一个新的CC Pending Layer Tree之后,接下来就调用LayerTreeHost类的成员函数FinishCommitOnImplThread将之前绘制好的CC Layer Tree同步到刚才创建出来的CC Pending Layer Tree去,如下所示:
void LayerTreeHost::FinishCommitOnImplThread(LayerTreeHostImpl* host_impl) {
......
LayerTreeImpl* sync_tree = host_impl->sync_tree();
......
if (needs_full_tree_sync_)
sync_tree->SetRootLayer(TreeSynchronizer::SynchronizeTrees(
root_layer(), sync_tree->DetachLayerTree(), sync_tree));
{
......
TreeSynchronizer::PushProperties(root_layer(), sync_tree->root_layer());
}
sync_tree->set_needs_full_tree_sync(needs_full_tree_sync_);
needs_full_tree_sync_ = false;
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.cc中。
LayerTreeHost类的成员函数FinishCommitOnImplThread首先调用参数host_impl指向的一个LayerTreeHostImpl对象的成员函数sync_tree获得刚才创建的CC Pending Layer Tree,如下所示:
class CC_EXPORT LayerTreeHostImpl
: public InputHandler,
public RendererClient,
public TileManagerClient,
public OutputSurfaceClient,
public TopControlsManagerClient,
public ScrollbarAnimationControllerClient,
public base::SupportsWeakPtr<LayerTreeHostImpl> {
public:
......
// Returns the tree LTH synchronizes with.
LayerTreeImpl* sync_tree() {
// In impl-side painting, synchronize to the pending tree so that it has
// time to raster before being displayed.
return settings_.impl_side_painting ? pending_tree_.get()
: active_tree_.get();
}
......
};
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.h中。
在Render进程启用Impl Side Painting特性的情况下,LayerTreeHostImpl对象的成员函数sync_tree返回的是成员变量pending_tree_指向的一个LayerTreeImpl对象,也就是前面创建的CC Pending Layer Tree。如果Render进程没有启用Impl Side Painting特性,那么LayerTreeHostImpl对象的成员函数sync_tree返回的是成员变量active_tree_指向的一个LayerTreeImpl对象,也就是CC模块上一次获得一个CC Active Layer Tree。这里我们只考虑Render进程启用Impl Side Painting特性的情况。
回到前面分析的LayerTreeHost类的成员函数FinishCommitOnImplThread中,它获得了前面创建的CC Pending Layer Tree之后,接下来再判断成员变量needs_full_tree_sync_的值是否等于true。如果等于true,那么就说明CC Layer Tree的结构发生了变化,也就是增加了Layer,或者减少了Layer。在这种情况下,就需要将CC Layer Tree的结构同步到CC Pending Layer Tree中去。这是通过调用TreeSynchronizer类的静态成员函数SynchronizeTrees实现的。
在调用TreeSynchronizer类的静态成员函数SynchronizeTrees之前,LayerTreeHost类的成员函数FinishCommitOnImplThread还会做两件事情。
第一件事情是调用LayerTreeHost类的成员函数root_layer获得CC Layer Tree的根节点,如下所示:
class CC_EXPORT LayerTreeHost {
public:
......
Layer* root_layer() { return root_layer_.get(); }
......
private:
......
scoped_refptr<Layer> root_layer_;
......
};
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host.h中。
LayerTreeHost类的成员root_layer_返回的是成员变量root_layer_指向的一个Layer对象。这个Layer对象描述的就是CC Layer Tree的根节点。
回到LayerTreeHost类的成员函数FinishCommitOnImplThread中,它接下来做的第二件事情是清空前一个CC Pending Layer Tree,并且返回这个CC Pending Layer Tree的根节点给调用者,这是通过调用LayerTreeImpl类的成员函数DetachLayerTree实现的,如下所示:
scoped_ptr<LayerImpl> LayerTreeImpl::DetachLayerTree() {
// Clear all data structures that have direct references to the layer tree.
......
render_surface_layer_list_.clear();
......
return root_layer_.Pass();
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_impl.cc中。
CC Pending Layer Tree像CC Layer Tree一样,内部也维护有一个Render Surface Tree,保存在成员变量render_surface_layer_list_中。这个Render Surface Tree是根据CC Pending Layer Tree创建的。关于Render Surface Tree更详细的描述,可以参考前面Chromium网页Layer Tree绘制过程分析一文。
LayerTreeImpl类的成员变量root_layer_指向的是一个LayerImpl对象。这个LayerImpl对象描述的就是前一个CC Pending Layer Tree的根节点,LayerTreeImpl类的成员函数DetachLayerTree将它返回给调用者。
回到LayerTreeHost类的成员函数FinishCommitOnImplThread中,它获得了之前刚刚绘制好的CC Layer Tree和前一个CC Pending Layer Tree的根节点之后,接下来就调用TreeSynchronizer类的静态成员函数SynchronizeTrees将前者同步到新的CC Pending Layer Tree去,如下所示:
scoped_ptr<LayerImpl> TreeSynchronizer::SynchronizeTrees(
Layer* layer_root,
scoped_ptr<LayerImpl> old_layer_impl_root,
LayerTreeImpl* tree_impl) {
return SynchronizeTreesInternal(
layer_root, old_layer_impl_root.Pass(), tree_impl);
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
我们首先明确一下各个参数的含义。参数layer_root描述的是之前刚刚绘制好的CC Layer Tree。参数old_layer_impl_root描述的是前一个CC Pending Layer Tree。这个CC Pending Layer Tree已经光栅化完毕,但是被缓存了起来,现在将被复用。参数tree_impl描述的是新的CC Pending Layer Tree。
TreeSynchronizer类的静态成员函数SynchronizeTrees所做的事情就是将之前刚刚绘制好的CC Layer Tree的结构同步到新的CC Pending Layer Tree。在同步的过程中,需要使用前一个CC Pending Layer Tree。这是通过调用另外一个静态成员函数SynchronizeTreesInternal实现的,如下所示:
template <typename LayerType>
scoped_ptr<LayerImpl> SynchronizeTreesInternal(
LayerType* layer_root,
scoped_ptr<LayerImpl> old_layer_impl_root,
LayerTreeImpl* tree_impl) {
......
ScopedPtrLayerImplMap old_layers;
RawPtrLayerImplMap new_layers;
CollectExistingLayerImplRecursive(&old_layers, old_layer_impl_root.Pass());
scoped_ptr<LayerImpl> new_tree = SynchronizeTreesRecursive(
&new_layers, &old_layers, layer_root, tree_impl);
......
return new_tree.Pass();
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数SynchronizeTreesInternal是一个模板函数。在我们这个情况中,它的模板参数LayerType等于Layer。这意味着参数layer_root指向的是一个Layer对象。这个Layer对象描述的就是CC Layer Tree。
TreeSynchronizer类的静态成员函数SynchronizeTreesInternal首先调用函数CollectExistingLayerImplRecursive收集前一个CC Pending Layer Tree中的Layer,如下所示:
void CollectExistingLayerImplRecursive(ScopedPtrLayerImplMap* old_layers,
scoped_ptr<LayerImpl> layer_impl) {
......
OwnedLayerImplList& children = layer_impl->children();
for (OwnedLayerImplList::iterator it = children.begin();
it != children.end();
++it)
CollectExistingLayerImplRecursive(old_layers, children.take(it));
......
int id = layer_impl->id();
old_layers->set(id, layer_impl.Pass());
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
函数CollectExistingLayerImplRecursive会递归调用自己,以便收集参数layer_impl描述的CC Pending Layer Tree的所有Layer。这些Layer分别以它们的ID为键值,保存在参数old_layers描述的一个Map中。
回到TreeSynchronizer类的静态成员函数SynchronizeTreesInternal,它将前一个CC Pending Layer Tree中的所有Layer都收集到本地变量old_layers描述的一个Map之后,接下来另外一个静态成员函数SynchronizeTreesRecursive将参数root_layer描述的CC Layer Tree的结构同步到tree_impl描述的新的CC Pending Layer Tree中去,如下所示:
scoped_ptr<LayerImpl> SynchronizeTreesRecursive(
RawPtrLayerImplMap* new_layers,
ScopedPtrLayerImplMap* old_layers,
Layer* layer,
LayerTreeImpl* tree_impl) {
return SynchronizeTreesRecursiveInternal(
new_layers, old_layers, layer, tree_impl);
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数SynchronizeTreesRecursive调用另外一个静态成员函数SynchronizeTreesRecursiveInternal将参数layer描述的CC Layer Tree同步到参数tree_impl描述的CC Pending Layer Tree去,如下所示:
template <typename LayerType>
scoped_ptr<LayerImpl> SynchronizeTreesRecursiveInternal(
RawPtrLayerImplMap* new_layers,
ScopedPtrLayerImplMap* old_layers,
LayerType* layer,
LayerTreeImpl* tree_impl) {
......
scoped_ptr<LayerImpl> layer_impl =
ReuseOrCreateLayerImpl(new_layers, old_layers, layer, tree_impl);
layer_impl->ClearChildList();
for (size_t i = 0; i < layer->children().size(); ++i) {
layer_impl->AddChild(SynchronizeTreesRecursiveInternal(
new_layers, old_layers, layer->child_at(i), tree_impl));
}
......
return layer_impl.Pass();
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数SynchronizeTreesRecursiveInternal也是一个模板函数。在我们这个情景中,它的模板参数LayerType等于Layer。这意味着参数layer指向的是一个Layer对象。这个Layer对象描述的就是CC Layer Tree的根节点。
TreeSynchronizer类的静态成员函数SynchronizeTreesRecursiveInternal将参数layer描述的一个Layer同步到参数tree_impl描述的CC Pending Layer Tree去,这是通过调用另外一个静态成员函数ReuseOrCreateLayerImpl实现的。
TreeSynchronizer类的静态成员函数SynchronizeTreesRecursiveInternal接下来又递归调用自己将参数layer描述的Layer的所有子Layer同步到参数tree_impl描述的CC Pending Layer Tree去,从而将CC Layer Tree的所有Layer都同步到CC Pending Layer Tree去。
TreeSynchronizer类的静态成员函数ReuseOrCreateLayerImpl的实现如下所示:
template <typename LayerType>
scoped_ptr<LayerImpl> ReuseOrCreateLayerImpl(RawPtrLayerImplMap* new_layers,
ScopedPtrLayerImplMap* old_layers,
LayerType* layer,
LayerTreeImpl* tree_impl) {
scoped_ptr<LayerImpl> layer_impl = old_layers->take(layer->id());
if (!layer_impl)
layer_impl = layer->CreateLayerImpl(tree_impl);
(*new_layers)[layer->id()] = layer_impl.get();
return layer_impl.Pass();
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数ReuseOrCreateLayerImpl首先在参数old_layers描述的一个Map中检查是否存在一个LayerImpl对象,这个LayerImpl对象的ID与参数layer指向的Layer对象的ID值相同。如果存在,就意味在参数layer描述的Layer在CC Layer Tree的绘制前后,没有发生变化。这意味着我们可以复用在前一个CC Pending Layer Tree中对应的Layer。
如果不存在,那么就说明参数layer描述的Layer是新增加到CC Layer Tree中的。在这种情况下,就需要调用参数layer指向的一个Layer对象的成员函数CreateLayerImpl创建一个LayerImpl对象。这个LayerImpl对象用来描述在新的CC Pending Layer Tree中,与参数layer描述的Layer对应的一个Layer。
参数layer描述的Layer是CC Layer Tree中的一个Layer。从前面Chromium网页Layer Tree创建过程分析一文可以知道,CC Layer Tree中的Layer的实际类型是PictureLayer。因此,TreeSynchronizer类的静态成员函数ReuseOrCreateLayerImpl实际上是调用PictureLayer类的成员函数CreateLayerImpl创建一个LayerImpl对象,如下所示:
scoped_ptr<LayerImpl> PictureLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) {
return PictureLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>();
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer.cc中。
PictureLayer类的成员函数CreateLayerImpl调用PictureLayerImpl类的静态成员函数Create创建了一个PictureLayerImpl对象。这个PictureLayerImpl对象的ID与当前正在处理的一个PictureLayer对象的ID值相同。这意味着,在CC Layer Tree和CC Pending Layer Tree中,对应的Layer具有相同的ID。
PictureLayerImpl类的静态成员函数Create的实现如下所示:
class CC_EXPORT PictureLayerImpl
: public LayerImpl,
NON_EXPORTED_BASE(public PictureLayerTilingClient) {
public:
......
static scoped_ptr<PictureLayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
return make_scoped_ptr(new PictureLayerImpl(tree_impl, id));
}
......
};
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.h中。
从这里可以看到,PictureLayerImpl类的静态成员函数Create创建的是一个PictureLayerImpl对象,并且将这个PictureLayerImpl对象返回给调用者。这个PictureLayerImpl对象的创建过程,也就是PictureLayerImpl类的构造函数的实现,如下所示:
PictureLayerImpl::PictureLayerImpl(LayerTreeImpl* tree_impl, int id)
: LayerImpl(tree_impl, id),
...... {
......
}
这个函数定义在文件external/chromium_org/cc$ vi layers/picture_layer_impl.cc中。
PictureLayerImpl类的构造函数主要是调用父类Layer的构造函数对当前正在创建的PictrueLayerImpl对象进行初始化,如下所示:
LayerImpl::LayerImpl(LayerTreeImpl* tree_impl, int id)
: ......,
layer_id_(id),
layer_tree_impl_(tree_impl),
...... {
......
}
这个函数定义在文件external/chromium_org/cc/layers/layer_impl.cc中。
从前面的调用过程可以知道,参数tree_impl描述的是一个CC Pending Layer Tree。这个CC Pending Layer Tree记录在Layer类的成员变量layer_tree_impl_中,表示当前正在创建的PictureLayerImpl对象所属的Tree。另外一个参数id描述的当前正在创建的PictureLayerImpl对象的ID,保存在另外一个成员变量layer_id_中。
回到TreeSynchronizer类的静态成员函数ReuseOrCreateLayerImpl中,它获得了与参数layer指向的Layer对象对应的一个PictureLayerImpl对象之后,就以这个PictureLayerImpl的ID为键值将这个PictureLayerImpl对象保存在参数new_layers描述的一个Map中。这意味将之前刚绘制好的CC Layer Tree同步到新的CC Pending Layer Tree去之后,后者的所有的Layer都保存在参数new_layers描述的Map中。
这一步执行完成后,之前刚绘制好的CC Layer Tree的结构就同步到新创建的CC Pending Layer Tree中去了。回到前面分析的LayerTreeHost类的成员函数FinishCommitOnImplThread中,它接下来继续执行同步操作。不过这时候是将CC Layer Tree的属性变化同步到CC Pending Layer Tree中去。这是通过调用TreeSynchronizer类的静态成员函数PushProperties实现的,如下所示:
void TreeSynchronizer::PushProperties(Layer* layer,
LayerImpl* layer_impl) {
size_t num_dependents_need_push_properties = 0;
PushPropertiesInternal(
layer, layer_impl, &num_dependents_need_push_properties);
......
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数PushProperties调用另外一个静态成员函数PushPropertiesInternal将参数layer描述的CC Layer Tree的属性变化同步到另外一个参数layer_impl描述的CC Pending Layer Tree中去,如下所示:
template <typename LayerType>
void TreeSynchronizer::PushPropertiesInternal(
LayerType* layer,
LayerImpl* layer_impl,
size_t* num_dependents_need_push_properties_for_parent) {
......
bool push_layer = layer->needs_push_properties();
bool recurse_on_children_and_dependents =
layer->descendant_needs_push_properties();
if (push_layer)
layer->PushPropertiesTo(layer_impl);
......
if (recurse_on_children_and_dependents) {
.......
const OwnedLayerImplList& impl_children = layer_impl->children();
......
for (size_t i = 0; i < layer->children().size(); ++i) {
PushPropertiesInternal(layer->child_at(i),
impl_children[i],
&num_dependents_need_push_properties);
}
......
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/tree_synchronizer.cc中。
TreeSynchronizer类的静态成员函数PushPropertiesInternal是一个模板函数。在我们这个情景中,它的模板参数LayerType等于Layer。这意味着参数layer指向的是一个Layer对象。*这个Layer对象描述的就是CC Layer Tree的根节点。
TreeSynchronizer类的静态成员函数PushPropertiesInternal首先是检查参数layer描述的Layer的属性自从上一次绘制之后是否发生了变化。如果发生变了变化,那么调用它的成员函数needs_push_properties获得的返回就会等于true。这种情况就表示要将参数layer描述的Layer的属性变化同步到描述layer_impl的一个Layer中去,这是通过调用前者的成员函数PushPropertiesTo实现的。注意,后者是属于新创建的CC Pending Layer Tree中的一个Layer。这相当于是将CC Layer Tree的某一个Layer的属性变化同步到新创建的CC Pending Layer Tree中去。
TreeSynchronizer类的静态成员函数PushPropertiesInternal接下来再检查数layer描述的Layer的子Layer的属性自从上一次绘制之后是否发生了变化。如果发生了变化,那么就会递归调用自已,以便将layer描述的Layer的子Layer的属性变化也同步到新创建的CC Pending Layer Tree中去。
前面提到,参数layer指向的实际上是一个PictureLayer对象。因此,TreeSynchronizer类的静态成员函数PushPropertiesInternal实际上是通过调用PictureLayer类的成员函数PushPropertiesTo将CC Layer Tree中的Layer属性变化同步到新创建的CC Pending Layer Tree中去。
PictureLayer类的成员函数PushPropertiesTo的实现如下所示:
void PictureLayer::PushPropertiesTo(LayerImpl* base_layer) {
Layer::PushPropertiesTo(base_layer);
PictureLayerImpl* layer_impl = static_cast<PictureLayerImpl*>(base_layer);
......
// Unlike other properties, invalidation must always be set on layer_impl.
// See PictureLayerImpl::PushPropertiesTo for more details.
layer_impl->invalidation_.Clear();
layer_impl->invalidation_.Swap(&pile_invalidation_);
layer_impl->pile_ = PicturePileImpl::CreateFromOther(pile_.get());
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer.cc中。
PictureLayer类的成员函数PushPropertiesTo首先调用父类Layer的成员函数PushPropertiesTo将当前正在处理的Layer的绘制属性同步到参数base_layer描述的另外一个Layer中去。从前面的分析可以知道,当前正在处理的Layer是属于CC Layer Tree的,而参数base_layer描述的Layer是属于新创建的CC Pending Layer Tree的。
从Layer类的成员函数PushPropertiesTo返回后,当前正在处理的Layer的绘制属性就同步到CC Pending Layer Tree中去了。接下来PictureLayer类的成员函数PushPropertiesTo还做了两件事情。
第一件事情是将当前正在处理的Layer的重绘区域记录在参数base_layer描述的Layer的成员变量invalidation_中。在对后者执行光栅化操作时,需要使用到这个重绘区域。当前正在处理的Layer的重绘区域记录在PictureLayer类的成员变量pileinvalidation。关于Layer的重绘区域的计算,可以参考前面Chromium网页Layer Tree绘制过程分析一文。
第二件事情是调用PicturePileImpl类的静态成员函数CreateFromOther创建一个PicturePileImpl对象,并且保存在参数base_layer指向的一个PictureLayerImpl对象的成员变量pile_中。这个PicturePileImpl对象负责对参数base_layer描述的Layer执行光栅化操作,它是根据PictureLayer类的成员变量pile_描述的一个PicturePile对象创建的。从前面Chromium网页Layer Tree绘制过程分析一文可以知道,这个PicturePile对象是负责绘制当前正在处理的Layer的。
接下来我们继续分析PicturePileImpl类的静态成员函数CreateFromOther的实现,如下所示:
scoped_refptr<PicturePileImpl> PicturePileImpl::CreateFromOther(
const PicturePileBase* other) {
return make_scoped_refptr(new PicturePileImpl(other));
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_impl.cc中。
从这里可以看到,PicturePileImpl类的静态成员函数CreateFromOther创建的是一个PicturePileImpl对象,并且将这个PicturePileImpl对象返回给调用者。这个PicturePileImpl对象的创建过程,也就是PicturePileImpl类的构造函数的实现,如下所示:
PicturePileImpl::PicturePileImpl(const PicturePileBase* other)
: PicturePileBase(other),
...... {
}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_impl.cc中。
参数other指向的实际上是一个PicturePile对象,PicturePileImpl类的构造函数以这个PicturePile对象为参数,调用父类PicturePileBase的构造函数,后者的实现如下所示:
PicturePileBase::PicturePileBase(const PicturePileBase* other)
: picture_map_(other->picture_map_),
tiling_(other->tiling_),
recorded_viewport_(other->recorded_viewport_),
min_contents_scale_(other->min_contents_scale_),
...... {}
这个函数定义在文件external/chromium_org/cc/resources/picture_pile_base.cc中。
PicturePileBase的构造函数将参数other指向的PicturePile对象的成员变量picturemap、tiling_、recorded_viewport_和min_contents_scale_分别保存在当前正在创建的一个PicturePileImpl对象的成员变量picturemap、tiling_、recorded_viewport_和min_contentsscale。这些成员变量描述了CC Layer Tree的一个Layer的绘制信息,它们的含义分别为:
2. tiling_:描述了一个TilingData对象。这个TilingData对象记录了CC Layer Tree的一个Layer的分块信息,例如分块大小和分块重叠边界大小等。
4. min_contentsscale:描述的是CC Layer Tree的最小缩放因子。
以后对CC Pending Layer Tree中的Layer进行光栅化操作时,就需要用到这些绘制信息。关于这些绘制信息的更详细描述,可以参考前面Chromium网页Layer Tree绘制过程分析一文。
这一步执行完成后,回到前面分析的LayerTreeHost类的成员函数FinishCommitOnImplThread中,这时候之前刚刚绘制好的CC Layer Tree的结构和属性变化就同步到新创建的CC Pending Layer Tree中去了。LayerTreeHost类的成员函数FinishCommitOnImplThread接下来会调用LayerTreeImpl类的成员函数set_needs_full_tree_sync记录是否为新创建的CC Pending Layer Tree执行过结构同步,如下所示:
class CC_EXPORT LayerTreeImpl {
public:
......
void set_needs_full_tree_sync(bool needs) { needs_full_tree_sync_ = needs; }
bool needs_full_tree_sync() const { return needs_full_tree_sync_; }
......
private
......
// In impl-side painting mode, this is true when the tree may contain
// structural differences relative to the active tree.
bool needs_full_tree_sync_;
......
};
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_impl.h中。
LayerTreeImpl类的成员函数set_needs_full_tree_sync将参数needs的值记录在成员变量needs_full_tree_sync_中,以后通过调用另外一个成员函数needs_full_tree_sync就可以知道这个成员变量的值。当这个成员变量的值等于true的时候,就表示新创建的CC Pending Layer Tree的结构发生过变化,在将它激活为CC Active Layer Tree的时候,需要在CC Pending Layer Tree与CC Active Layer Tree之间执行一次结构同步操作,就如在CC Layer Tree与CC Pending Layer Tree之间执行一次结构同步操作一样。
这一步执行完成后,回到前面分析的ThreadProxy类的成员函数ScheduledActionCommit中,这时候之前刚刚绘制好的CC Layer Tree就同步到一个新的CC Pending Layer Tree中去了,ThreadProxy类的成员函数ScheduledActionCommit接下来就会唤醒Main线程,并且继续在当前线程,也就是Compositor线程,调用LayerTreeHostImpl类的成员函数CommitComplete为新的CC Pending Layer Tree中的Layer更新或者创建分块,为以后执行光栅化操作做准备。
LayerTreeHostImpl类的成员函数CommitComplete的实现如下所示:
void LayerTreeHostImpl::CommitComplete() {
......
if (settings_.impl_side_painting) {
......
pending_tree_->UpdateDrawProperties();
......
}
......
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
从前面的分析可以知道,LayerTreeHostImpl类的成员变量pending_tree_指向的是一个LayerTreeImpl对象。这个LayerTreeImpl对象描述的是新创建的CC Pending Layer Tree。在Render进程启用Impl Side Painting特性的情况下, LayerTreeHostImpl类的成员函数CommitComplete调用LayerTreeImpl类的成员函数UpdateDrawProperties为CC Pending Layer Tree更新或者创建分块。
LayerTreeImpl类的成员函数UpdateDrawProperties的实现如下所示:
bool LayerTreeImpl::UpdateDrawProperties() {
......
render_surface_layer_list_.clear();
{
......
++render_surface_layer_list_id_;
LayerTreeHostCommon::CalcDrawPropsImplInputs inputs(
root_layer(),
DrawViewportSize(),
layer_tree_host_impl_->DrawTransform(),
device_scale_factor(),
total_page_scale_factor(),
page_scale_layer,
MaxTextureSize(),
settings().can_use_lcd_text,
can_render_to_separate_surface,
settings().layer_transforms_should_scale_layer_contents,
&render_surface_layer_list_,
render_surface_layer_list_id_);
LayerTreeHostCommon::CalculateDrawProperties(&inputs);
}
{
......
typedef LayerIterator<LayerImpl> LayerIteratorType;
LayerIteratorType end = LayerIteratorType::End(&render_surface_layer_list_);
for (LayerIteratorType it =
LayerIteratorType::Begin(&render_surface_layer_list_);
it != end;
++it) {
LayerImpl* layer = *it;
if (it.represents_itself())
layer->UpdateTiles();
......
}
}
......
return true;
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_impl.cc中。
LayerTreeImpl类的成员函数UpdateDrawProperties首先调用LayerTreeHostCommon类的静态成员函数CalculateDrawProperties计算CC Pending Layer Tree中的每一个Layer的绘制属性,并且为CC Pending Layer Tree创建一个Render Surface Tree。创建出来的Render Surface Tree就以列表的方式保存在LayerTreeImpl类的成员变量render_surface_layer_list_中。在前面Chromium网页Layer Tree绘制过程分析一文中,也是通过调用LayerTreeHostCommon类的静态成员函数CalculateDrawProperties为CC Layer Tree创建一个Render Surface Tree的。因此,关于LayerTreeHostCommon类的静态成员函数CalculateDrawProperties的实现,以及Render Surface Tree的概念,可以参考前面Chromium网页Layer Tree绘制过程分析一文。
LayerTreeImpl类的成员函数UpdateDrawProperties接下来遍历前面创建的Render Surface Tree中的每一个Layer节点。每一个Layer节点都是通过一个PictureLayerImpl对象描述的。LayerTreeImpl类的成员函数UpdateDrawProperties通过调用这些PictureLayerImpl对象的成员函数UpdateTiles更新它们所描述的Layer的分块。
接下来我们就继续分析PictureLayerImpl类的成员函数UpdateTiles的实现,以便了解CC Pending Layer Tree中的Layer是如何分块的,如下所示:
void PictureLayerImpl::UpdateTiles() {
......
UpdateIdealScales();
.....
if (!raster_contents_scale_ || ShouldAdjustRasterScale()) {
RecalculateRasterScales();
AddTilingsForRasterScale();
}
......
UpdateTilePriorities();
if (layer_tree_impl()->IsPendingTree())
MarkVisibleResourcesAsRequired();
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
PictureLayerImpl类的成员函数UpdateTiles首先调用成员函数UpdateIdealScales更新分块的理想缩放因子,即Ideal Scale,如下所示:
void PictureLayerImpl::UpdateIdealScales() {
DCHECK(CanHaveTilings());
float min_contents_scale = MinimumContentsScale();
DCHECK_GT(min_contents_scale, 0.f);
float min_page_scale = layer_tree_impl()->min_page_scale_factor();
DCHECK_GT(min_page_scale, 0.f);
float min_device_scale = 1.f;
float min_source_scale =
min_contents_scale / min_page_scale / min_device_scale;
float ideal_page_scale = draw_properties().page_scale_factor;
float ideal_device_scale = draw_properties().device_scale_factor;
float ideal_source_scale = draw_properties().ideal_contents_scale /
ideal_page_scale / ideal_device_scale;
ideal_contents_scale_ =
std::max(draw_properties().ideal_contents_scale, min_contents_scale);
ideal_page_scale_ = draw_properties().page_scale_factor;
ideal_device_scale_ = draw_properties().device_scale_factor;
ideal_source_scale_ = std::max(ideal_source_scale, min_source_scale);
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
Ideal Scale指的是分块的大小与它实际要显示的大小是1:1的关系。一个分块有4种Ideal Scale:
1. Device Scale:设置在显示设备上的缩放因子。
2. Page Scale:用户Pinch网页时产生的缩放因子。
3. Source Scale:通过Javascript和CSS设置的缩放因子。
4. Contents Scale:上述三种缩放因子相乘之后得到的缩放因子。
这些Ideal Scale是由前面分析的LayerTreeImpl类的成员函数UpdateDrawProperties调用LayerTreeHostCommon类的静态成员函数CalculateDrawProperties计算得到的,并且会保存在PictureLayerImpl类的成员变量ideal_devicescale、ideal_pagescale、ideal_source_scale_和ideal_contents_scale_中。
此外,上述4种Ideal Scale还规定了最小值,也就是前面计算出来4种Ideal Scale不能小于各自对应的Min Scale。
回到PictureLayerImpl类的成员函数UpdateTiles中,更新了分块的Ideal Scale之后,它接下来判断是否需要重新计算分块的光栅化缩放因子,即Raster Scale。在两种情况下,需要重新计算分块的Raster Scale。
第一种情况是分块的Raster Scale还没有计算过。这时候PictureLayerImpl类的成员变量raster_contentsscale 的值等于0。
第二种情况是分块的Raster Scale自上次计算以来又发生了新的变化。这是通过调用PictureLayerImpl类的成员函数ShouldAdjustRasterScale进行判断的,如下所示:
bool PictureLayerImpl::ShouldAdjustRasterScale() const {
if (was_screen_space_transform_animating_ !=
draw_properties().screen_space_transform_is_animating)
return true;
bool is_pinching = layer_tree_impl()->PinchGestureActive();
if (is_pinching && raster_page_scale_) {
// We change our raster scale when it is:
// - Higher than ideal (need a lower-res tiling available)
// - Too far from ideal (need a higher-res tiling available)
float ratio = ideal_page_scale_ / raster_page_scale_;
if (raster_page_scale_ > ideal_page_scale_ ||
ratio > kMaxScaleRatioDuringPinch)
return true;
}
if (!is_pinching) {
// When not pinching, match the ideal page scale factor.
if (raster_page_scale_ != ideal_page_scale_)
return true;
}
// Always match the ideal device scale factor.
if (raster_device_scale_ != ideal_device_scale_)
return true;
// When the source scale changes we want to match it, but not when animating
// or when we've fixed the scale in place.
if (!draw_properties().screen_space_transform_is_animating &&
!raster_source_scale_is_fixed_ &&
raster_source_scale_ != ideal_source_scale_)
return true;
return false;
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
在以下5种情况下,PictureLayerImpl类的成员函数ShouldAdjustRasterScale的返回值等于true,表示需要重新计算分块的Raster Scale:
1. 网页分块之前处于动画显示状态,现在动画显示结束,或者之前不是处于动画显示状态,现在开始显示动画。
2. 用户正在Pinch网页,并且由此造成分块的Raster Page Scale大于Ideal Page Scale,或者Raster Page Scale小于Ideal Page Scale的kMaxScaleRatioDuringPinch分之1。kMaxScaleRatioDuringPinch定义为2。前一种情形表示网页被放大,而后一种情形表示网页被缩小,并且缩小的程度大于预置的阀值。网页在放大的过程中,需要创建低分辨率分块临时使用,而网页在缩小到一定程度时,需要创建与实际显示大小1:1关系的分块,这种分块也称为高分辨率分块,或者Ideal分块。
3. 用户当前对网页没有Pinch操作,但是这时候分块的Raster Page Scale不等于Ideal Page Scale。分块最终是以Ideal Page Scale显示的,因此如果当前网页分块的Raster Page Scale不等于Ideal Page Scale时,就需要重新计算网页分块的Raster Page Scale。
4. Device Scale发生了变化。
5. 不是以动画的形式修改了Source Scale,并且Source Scale还没有被调整,即PictureLayerImpl类的成员变量raster_source_scale_is_fixed_的值就会等于false。以非动画形式修改Source Scale,不会触发新的光栅化操作的。但是不触发新的光栅化操作,会出现不可预测的情况。例如,以非动画形式将Source Scale从0.0625逐渐修改到1.0。由于这个过程没有触发新的光栅化操作,网页自始到终都是以缩放因子为0.0625的分块进行显示,这就会造成内容模糊。为了解决这个问题,以非动画形式修改Source Scale时,Source Scale会被强制调整为1.0,即会忽略掉对Source Scale的修改。这个问题的由来及解决方案可以参考BUG#368201。
PictureLayerImpl类有另外4个成员变量raster_devicescale、raster_pagescale、raster_source_scale_和raster_contentsscale,它们分别与前面介绍的4个成员变量ideal_devicescale、ideal_pagescale、ideal_source_scale_和ideal_contents_scale_对应,表示网页分块上次执行光栅化操作时所使用的4种Raster Scale。
再回到PictureLayerImpl类的成员函数UpdateTiles中,一旦它决定要重新计算网页分块的Raster Scale,那么它就会调用成员函数RecalculateRasterScales进行计算,如下所示:
AddTilingsForRasterScalevoid PictureLayerImpl::RecalculateRasterScales() {
float old_raster_contents_scale = raster_contents_scale_;
float old_raster_page_scale = raster_page_scale_;
float old_raster_source_scale = raster_source_scale_;
raster_device_scale_ = ideal_device_scale_;
raster_page_scale_ = ideal_page_scale_;
raster_source_scale_ = ideal_source_scale_;
raster_contents_scale_ = ideal_contents_scale_;
// If we're not animating, or leaving an animation, and the
// ideal_source_scale_ changes, then things are unpredictable, and we fix
// the raster_source_scale_ in place.
if (old_raster_source_scale &&
!draw_properties().screen_space_transform_is_animating &&
!was_screen_space_transform_animating_ &&
old_raster_source_scale != ideal_source_scale_)
raster_source_scale_is_fixed_ = true;
// TODO(danakj): Adjust raster source scale closer to ideal source scale at
// a throttled rate. Possibly make use of invalidation_.IsEmpty() on pending
// tree. This will allow CSS scale changes to get re-rastered at an
// appropriate rate.
if (raster_source_scale_is_fixed_) {
raster_contents_scale_ /= raster_source_scale_;
raster_source_scale_ = 1.f;
}
// During pinch we completely ignore the current ideal scale, and just use
// a multiple of the previous scale.
// TODO(danakj): This seems crazy, we should use the current ideal, no?
bool is_pinching = layer_tree_impl()->PinchGestureActive();
if (is_pinching && old_raster_contents_scale) {
// See ShouldAdjustRasterScale:
// - When zooming out, preemptively create new tiling at lower resolution.
// - When zooming in, approximate ideal using multiple of kMaxScaleRatio.
bool zooming_out = old_raster_page_scale > ideal_page_scale_;
float desired_contents_scale =
zooming_out ? old_raster_contents_scale / kMaxScaleRatioDuringPinch
: old_raster_contents_scale * kMaxScaleRatioDuringPinch;
raster_contents_scale_ = SnappedContentsScale(desired_contents_scale);
raster_page_scale_ =
raster_contents_scale_ / raster_device_scale_ / raster_source_scale_;
}
raster_contents_scale_ =
std::max(raster_contents_scale_, MinimumContentsScale());
// Since we're not re-rasterizing during animation, rasterize at the maximum
// scale that will occur during the animation, if the maximum scale is
// known. However, to avoid excessive memory use, don't rasterize at a scale
// at which this layer would become larger than the viewport.
if (draw_properties().screen_space_transform_is_animating) {
bool can_raster_at_maximum_scale = false;
if (draw_properties().maximum_animation_contents_scale > 0.f) {
gfx::Size bounds_at_maximum_scale = gfx::ToCeiledSize(gfx::ScaleSize(
bounds(), draw_properties().maximum_animation_contents_scale));
if (bounds_at_maximum_scale.GetArea() <=
layer_tree_impl()->device_viewport_size().GetArea())
can_raster_at_maximum_scale = true;
}
if (can_raster_at_maximum_scale) {
raster_contents_scale_ =
std::max(raster_contents_scale_,
draw_properties().maximum_animation_contents_scale);
} else {
raster_contents_scale_ =
std::max(raster_contents_scale_,
1.f * ideal_page_scale_ * ideal_device_scale_);
}
}
// If this layer would only create one tile at this content scale,
// don't create a low res tiling.
gfx::Size content_bounds =
gfx::ToCeiledSize(gfx::ScaleSize(bounds(), raster_contents_scale_));
gfx::Size tile_size = CalculateTileSize(content_bounds);
if (tile_size.width() >= content_bounds.width() &&
tile_size.height() >= content_bounds.height()) {
low_res_raster_contents_scale_ = raster_contents_scale_;
return;
}
float low_res_factor =
layer_tree_impl()->settings().low_res_contents_scale_factor;
low_res_raster_contents_scale_ = std::max(
raster_contents_scale_ * low_res_factor,
MinimumContentsScale());
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
PictureLayerImpl类的成员函数RecalculateRasterScales首先是将上次执行光栅化操作时所使用的Raster Scale记录在本地变量中,接着又将之前计算得到的Ideal Scale分别记录在成员变量raster_devicescale、raster_pagescale、raster_source_scale_和raster_contentsscale,作为接下来执行的光栅化操作使用的Raster Scale。其中,最重要的Raster Scale就是Raster Contents Scale,因为它决定了后面要创建的分块的大小。
PictureLayerImpl类的成员函数RecalculateRasterScales接下来所做的事情就是判断是否需要调整Raster Contents Scale的值,过程如下所示:
2. 如果用户正在Pinch网页,并且当前正在处理的Layer之前执行过光栅化操作,那么就进一步检查网页是被缩小还是放大。如果是缩小,就将当前的Raster Contents Scale设置为上一次的Raster Contents Scale的1/kMaxScaleRatioDuringPinch。如果是放大,就将当前的Raster Contents Scale设置为上一次的Raster Contents Scale的kMaxScaleRatioDuringPinch倍。前面计算出来的Raster Contents Scale还不是最终使用的Raster Contents Scale。PictureLayerImpl类的成员函数RecalculateRasterScales调用另外一个成员函数SnappedContentsScale检查前面的光栅化操作使用的Raster Contents Scale是否与前面计算出来的Raster Contents Scale接近。如果接近,就复用之前已经光栅化好的分块。否则的话,就会按照前面计算出来的Raster Contents Scale进行新的分块划分。
3. 如果前面得到的Raster Contents Scale小于预设置的最小值,那么它将被设置为最小值。这个最小值可以通过调用成员函数MinimumContentsScale获得。
4. 一个Layer在动画执行过程中,是不会多次执行光栅化操作的,也就是不会创建新的分块。在动画执行期间,使用的都是动画开始时所创建的分块,也就是通过对动画开始时所创建的分块进行变换,从而实现动画效果。如果这个动画涉及到缩放变换,并且设置了这个缩放变换的最大因子,那么在动画开始时就会按照这个最大因子对Layer进行分块,也就是将当前的Raster Contents Scale设置为缩放变换的最大因子。这样就可以保证在动画执行期间,用户看到的都是最清晰的内容。但是有一点需要注意,如果按照缩放变换的最大因子进行分块,会导致分块后的Layer的面积比网页的视图还要大,那么就会重新再计算一个Raster Contents Scale。这个Raster Contents Scale只考虑Device Scale和Page Scale,不考虑Source Scale。最后在前面已经计算出来的Raster Contents Scale与重新计算出来的Raster Contents Scale之间选择较大的一个作为当前的Raster Contents Scale。
按照以上步骤调整出来的Raster Contents Scale属于Ideal Raster Content Scale,PictureLayerImpl类的成员函数RecalculateRasterScales接下来还要计算一个Low Res Raster Contents Scale,并且保存在成员变量low_res_raster_contents_scale_中。这个Low Res Raster Contents Scale有什么用呢?原来,一个Layer除了会按照Ideal Raster Content Scale进行分块之外,还会按照Low Res Raster Contents Scale进行分块。这样就可以先快速地显示出缩放因子为Low Res Raster Contents Scale的分块来,尽管这些分块的内容是模糊的,但是也比用户什么也看不到强。在显示缩放因子为Low Res Raster Contents Scale的分块的同时,CC模块也会在背后默默地对缩放因子为Ideal Raster Content Scale的分块进行光栅化操作。等到光栅化操作完成的时候,缩放因子为Ideal Raster Content Scale的分块就可以替换掉缩放因子为Low Res Raster Contents Scale的分块,使得用户最终可以看到清晰的内容。
在默认情况下,Low Res Raster Contents Scale被设置为Ideal Raster Content Scale的0.25倍。但是如果一个Layer只有一个分块,那么就会取消创建缩放因子为Low Res Raster Contents Scale的分块,也就是将Ideal Raster Content Scale和Low Res Raster Contents Scale设置为相同的值。
再回到PictureLayerImpl类的成员函数UpdateTiles中,它重新计算好网页分块的Raster Scale之后,接下来就会按照计算出来的Raster Scale对当前正在处理的Layer创建新的分块划分,这是通过PictureLayerImpl类的成员函数AddTilingsForRasterScale实现的,如下所示:
void PictureLayerImpl::AddTilingsForRasterScale() {
PictureLayerTiling* high_res = NULL;
PictureLayerTiling* low_res = NULL;
PictureLayerTiling* previous_low_res = NULL;
for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
PictureLayerTiling* tiling = tilings_->tiling_at(i);
if (tiling->contents_scale() == raster_contents_scale_)
high_res = tiling;
if (tiling->contents_scale() == low_res_raster_contents_scale_)
low_res = tiling;
if (tiling->resolution() == LOW_RESOLUTION)
previous_low_res = tiling;
// Reset all tilings to non-ideal until the end of this function.
tiling->set_resolution(NON_IDEAL_RESOLUTION);
}
if (!high_res) {
high_res = AddTiling(raster_contents_scale_);
if (raster_contents_scale_ == low_res_raster_contents_scale_)
low_res = high_res;
}
// Only create new low res tilings when the transform is static. This
// prevents wastefully creating a paired low res tiling for every new high res
// tiling during a pinch or a CSS animation.
bool is_pinching = layer_tree_impl()->PinchGestureActive();
if (layer_tree_impl()->create_low_res_tiling() && !is_pinching &&
!draw_properties().screen_space_transform_is_animating && !low_res &&
low_res != high_res)
low_res = AddTiling(low_res_raster_contents_scale_);
// Set low-res if we have one.
if (!low_res)
low_res = previous_low_res;
if (low_res && low_res != high_res)
low_res->set_resolution(LOW_RESOLUTION);
// Make sure we always have one high-res (even if high == low).
high_res->set_resolution(HIGH_RESOLUTION);
SanityCheckTilingState();
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
一个Layer按照某一个Raster Scale进行的分块划分称为一个Tiling。一个Layer可能存在多个Tiling,如图2所示:
图2 Tiling Set
每一个Tiling都通过一个PictureLayerTiling对象描述,并且这些PictureLayerTiling对象保存在PictureLayerImpl类的成员变量tilings_描述的一个PictureLayerTiling Set中。
从前面的分析可以知道,当前正在处理的Layer重新计算出了两个Raster Scale。其中一个称为Ideal Raster Contents Scale,另一个称为Low Res Raster Contents Scale,它们分别保存在PictureLayerImpl类的成员变量raster_contents_scale_和low_res_raster_contents_scale_中。注意,Ideal Raster Contents Scale也称为High Res Raster Contents Scale。
PictureLayerImpl类的成员函数AddTilingsForRasterScale首先在成员变量tilings_描述的PictureLayerTiling Set中检查是否已经存在两个Tiling。这两个Tiling,一个使用的Raster Scale为High Res Raster Contents Scale,另一个使用的Raster Scale为Low Res Raster Contents Scale。如果存在,那么就不用创建新的Tiling了。
如果Raster Scale为High Res Raster Contents Scale的Tiling不存在,那么PictureLayerImpl类的成员函数AddTilingsForRasterScale就会调用另外一个成员函数AddTiling进行创建。但是如果Raster Scale为Low Res Raster Contents Scale的Tiling不存在,并且满足以下4个条件,那么PictureLayerImpl类的成员函数AddTilingsForRasterScale才会创建Raster Scale为Low Res Raster Contents Scale的Tiling:
1. Pending Layer Tree允许为每一个Raster Scale为High Res Raster Contents Scale的Tiling创建一个Raster Scale为Low Res Raster Contents Scale的Tiling。在使用CPU执行光栅化操作的情况下,这个条件才会成立。
2. 用户不是正在Pinch网页。
3. 网页不是正在显示动画。
设置这4个条件的目的,是尽可能地不要为每一个Raster Scale为High Res Raster Contents Scale的Tiling创建一个Raster Scale为Low Res Raster Contents Scale的Tiling,尤其是在用户Pinch网页,或者网页显示动画期间。这是因为创建一个新的Tiling不仅耗费时间,还耗费资源,并且Raster Scale为Low Res Raster Contents Scale的Tiling只是临时使用,它最终会被Raster Scale为High Res Raster Contents Scale的Tiling替换,因此与其创建新的、临时使用的Tiling,还不如复用之前所创建的Raster Scale为Low Res Raster Contents Scale的Tiling。也正因为如此,PictureLayerImpl类的成员函数AddTilingsForRasterScale开始的时候,除了检查在成员变量tilings_描述的PictureLayerTiling Set中检查Raster Scale为High Res Raster Contents Scale和Low Res Raster Contents Scale的两个Tiling是否存在之外,还会检查之前有没有创建过类型为LOW_RESOLUTION的Tiling。
PictureLayerImpl类的成员函数AddTilingsForRasterScale还会做一件事情,就是对当前正在处理的Layer的所有Tiling进行标记。其中,Raster Scale为High Res Raster Contents Scale的Tiling的类型被标记为HIGH_RESOLUTION,Raster Scale为Low Res Raster Contents Scale的Tiling如果存在,并且与Raster Scale为High Res Raster Contents Scale的Tiling不相同,那么它的类型被标记为LOW_RESOLUTION,其余的Tiling被标记为NON_IDEAL_RESOLUTION。之所以要对Tiling进行这样的标记,是因为后面对分块执行光栅化操作之前,会先对Tiling中的分块设置优先级。优先级越高的分块,它就越优先执行光栅化操作,而分块的优先级,与它们所在的Tiling的类型有关。这一点我们在接下来一篇文章中再详细分析。
接下来我们继续分析PictureLayerImpl类的成员函数AddTiling的实现,以便了解一个新的Tiling的创建过程,如下所示:
PictureLayerTiling* PictureLayerImpl::AddTiling(float contents_scale) {
......
PictureLayerTiling* tiling = tilings_->AddTiling(contents_scale);
......
if (twin_layer_)
twin_layer_->SyncTiling(tiling);
return tiling;
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
PictureLayerImpl类的成员函数AddTiling首先调用成员变量tilings_指向的一个PictureLayerTilingSet对象的成员函数AddTiling创建一个Raster Scale等于contents_scale的Tiling,如下所示:
PictureLayerTiling* PictureLayerTilingSet::AddTiling(float contents_scale) {
......
tilings_.push_back(PictureLayerTiling::Create(contents_scale,
layer_bounds_,
client_));
PictureLayerTiling* appended = tilings_.back();
tilings_.sort(LargestToSmallestScaleFunctor());
return appended;
}
这个函数定义在文件external/chromium_org/cc/resources/picture_layer_tiling_set.cc中。
PictureLayerTilingSet类的成员函数AddTiling首先调用PictureLayerTiling类的静态成员函数Create创建了一个PictureLayerTiling对象,接着又将这个PictureLayerTiling对象保存在成员变量tilings_描述的一个Vector中。从这里我们就可以推断出,这个Vector保存的就是同一个Layer的不同Tilings,并且每一个Tiling都有着不同的Raster Scale。
PictureLayerTilingSet类的成员函数AddTiling在将新创建的Tiling返回给调用者之前,还会对保存在成员变量tilings_描述的Vector中的Tiling进行排序,使得按照Raster Scale从大到小的顺序排列。
接下来我们继续分析PictureLayerTiling类的静态成员函数Create的实现,以便了解一个Tiling的创建过程,如下所示:
scoped_ptr<PictureLayerTiling> PictureLayerTiling::Create(
float contents_scale,
const gfx::Size& layer_bounds,
PictureLayerTilingClient* client) {
return make_scoped_ptr(new PictureLayerTiling(contents_scale,
layer_bounds,
client));
}
这个函数定义在文件external/chromium_org/cc/resources/picture_layer_tiling.cc中。
从这里可以看到,PictureLayerTiling类的静态成员函数Create创建了一个PictureLayerTiling对象,并且返回给调用者。这个PictureLayerTiling对象描述的就是一个Tiling,它的创建过程,也就是PictureLayerTiling类的构造函数的实现,如下所示:
PictureLayerTiling::PictureLayerTiling(float contents_scale,
const gfx::Size& layer_bounds,
PictureLayerTilingClient* client)
: contents_scale_(contents_scale),
layer_bounds_(layer_bounds),
...... {
gfx::Size content_bounds =
gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale));
gfx::Size tile_size = client_->CalculateTileSize(content_bounds);
......
tiling_data_.SetTilingRect(gfx::Rect(content_bounds));
tiling_data_.SetMaxTextureSize(tile_size);
}
这个函数定义在文件external/chromium_org/cc/resources/picture_layer_tiling.cc中。
参数contents_scale描述的是当前正在创建的Tiling的Raster Scale,另外一个参数layer_bounds描述的是当前正在创建Tiling的Layer的原始大小,PictureLayerTiling类的构造函数分别将这两个参数的值保存在成员变量contents_scale_和layer_bounds_中。
PictureLayerTiling类还有一个重要的成员变量tilingdata,它描述的是一个TilingData对象。这个TilingData对象负责对当前正在创建的Tiling进行分块。从前面Chromium网页Layer Tree绘制过程分析一文可以知道,CC Layer Tree中的Layer也是通过TilingData类进行分块的。这意味着不管是CC Layer Tree中的Layer和CC Pening Layer Tree中的Layer的分块方式都是一样的,区别只是在于分块的大小一样。
参数client指向的是一个CC Pening Layer Tree中的Layer的分块大小是通过调用参数client指向的是一个PictureLayerImpl对象。通过调用这个PictureLayerImpl对象的成员函数CalculateTileSize可以计算出当前正在创建的Tiling的分块的大小,如下所示:
gfx::Size PictureLayerImpl::CalculateTileSize(
const gfx::Size& content_bounds) const {
......
int max_texture_size =
layer_tree_impl()->resource_provider()->max_texture_size();
gfx::Size default_tile_size = layer_tree_impl()->settings().default_tile_size;
if (layer_tree_impl()->use_gpu_rasterization()) {
// TODO(ernstm) crbug.com/365877: We need a unified way to override the
// default-tile-size.
default_tile_size =
gfx::Size(layer_tree_impl()->device_viewport_size().width(),
layer_tree_impl()->device_viewport_size().height() / 4);
}
default_tile_size.SetToMin(gfx::Size(max_texture_size, max_texture_size));
gfx::Size max_untiled_content_size =
layer_tree_impl()->settings().max_untiled_layer_size;
max_untiled_content_size.SetToMin(
gfx::Size(max_texture_size, max_texture_size));
bool any_dimension_too_large =
content_bounds.width() > max_untiled_content_size.width() ||
content_bounds.height() > max_untiled_content_size.height();
bool any_dimension_one_tile =
content_bounds.width() <= default_tile_size.width() ||
content_bounds.height() <= default_tile_size.height();
// If long and skinny, tile at the max untiled content size, and clamp
// the smaller dimension to the content size, e.g. 1000x12 layer with
// 500x500 max untiled size would get 500x12 tiles. Also do this
// if the layer is small.
if (any_dimension_one_tile || !any_dimension_too_large) {
int width = std::min(
std::max(max_untiled_content_size.width(), default_tile_size.width()),
content_bounds.width());
int height = std::min(
std::max(max_untiled_content_size.height(), default_tile_size.height()),
content_bounds.height());
// Round width and height up to the closest multiple of 64, or 56 if
// we should avoid power-of-two textures. This helps reduce the number
// of different textures sizes to help recycling, and also keeps all
// textures multiple-of-eight, which is preferred on some drivers (IMG).
bool avoid_pow2 =
layer_tree_impl()->GetRendererCapabilities().avoid_pow2_textures;
int round_up_to = avoid_pow2 ? 56 : 64;
width = RoundUp(width, round_up_to);
height = RoundUp(height, round_up_to);
return gfx::Size(width, height);
}
return default_tile_size;
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
PictureLayerImpl类的成员函数CalculateTileSize首先获得GPU允许的最大纹理大小max_texture_size。这个值是通过调用一个负责为CC模块创建纹理的ResourceProvider对象的成同函数max_texture_size获得的,后者又是通过查询GPU获得其允许的最大纹理大小的。
PictureLayerImpl类的成员函数CalculateTileSize接下来又获得CC模块为CC Pending Layer Tree设置的默认分块大小default_tile_size。这个值被设置为256x256。不过,如果CC Pending Layer Tree使用GPU来执行光栅化操作,那么默认分块大小会被屏幕大小的1/4。其中,分块的宽度与屏幕的宽度一致,分块的高度等于屏幕高度的1/4。
获得GPU允许的最大纹理大小和CC Pending Layer Tree默认使用的分块大小之后,PictureLayerImpl类的成员函数CalculateTileSize就在这两者之中选择小的那个作为分块的大小,并且保存在本地变量default_tile_size中。
PictureLayerImpl类的成员函数CalculateTileSize接下来又获得CC模块为CC Pending Layer Tree设置的允许不进行分块的最大大小max_untiled_content_size。这个值被设置为512x512。意思就是说,如果一个Layer的某一个Tiling的大小content_bounds小于512x512,那么这个Tiling实际上就是不需要进行分块的。同时这也意味着分块的大小的最小值为512x512。
如果一个Tiling的大小content_bounds的宽度和高度之一比max_untiled_content_size值大,那么这个Tiling就认为是too large,这时候本地变量any_dimension_too_large的值会被设置为true。
如果一个Tiling的大小content_bounds的宽度和高度之一比default_tile_size值小,那么我们就知道这个Tiling在宽度或者高度上只有一个分块,这时候本地变量any_dimension_one_tile的值会被设置为true。
本地变量any_dimension_too_large和any_dimension_one_tile主要是用来判断当前正在处理的Tiling是否是长窄型的,也就是它的高度远大于宽度,或者宽度远大于高度。对于这个Layer,在分块的时候,少划分一些分块。我们知道,分块越大,划分出来的分块数量就越少。分块的大小一方面可以设置为default_tile_size,又一方面又可以设置为max_untiled_content_size。对于长窄型的Layer,就在两者之间选择大的那个,这样就可以减少分块的数量。
假设当前正在处理的Tiling是长窄型的,它的大小为1000x12,并且default_tile_size为256x256,max_untiled_content_size为512x512,这时候分块的大小就被设置为512x512,与分块大小设置为256x256相比,分块的数量可以少一半。
对于长窄型Tiling的分块大小,最终会被对齐到56或者64的倍数,取决于是否避免使用2的指数倍大小的纹理。如果避免,就对齐到56;否则的话,就对齐到64。不管是对齐到56还是64,分块的大小都是对齐到8个字节的。之所以要对齐到8个字节,是因为有些GPU驱动,倾向于使用大小为8的倍数的纹理。
对于不是长窄型Tiling的分块大小,它的值就等于default_tile_size。
这一步执行完成后,回到PictureLayerTiling类的构造函数中,这时候当前正在创建的Tiling使用的分块大小就确定下来了,这个值会设置到其成员变量tiling_data_描述的一个TilingData对象中,同时设置给这个TilingData对象的还有当前正在创建的Tiling的大小。
沿着调用堆栈再回到PictureLayerImpl类的成员函数AddTiling中,它创建了一个Tiling之后,接下来会检查成员变量twin_layer_的值是否等于NULL。如果不等于NULL,那么它就会指向一个PictureLayerImpl对象。这个PictureLayerImpl对象描述的是CC Active Layer Tree中的一个Layer,并且这个Layer与当前正在处理的Layer具有相同的ID,也就是它们描述的是相同的网页内容。注意,当前正在处理的Layer是属于CC Pending Layer Tree中的Layer。CC模块将在CC Active Layer Tree和CC Pending Layer Tree中具有相同ID的两个Layer称为Twin Layer。两个Twin Layer之间有些分块是可以共享的。这样可以避免创建重复的Layer。
两个Twin Layer具有相同的Tiling。这意味如果我们为一个Layer创建了一个Tiling,那么也需要为其对应的Twin Layer创建一个相同的Tiling。这是通过调用PictureLayerImpl类的成员函数SyncTiling实现的,如下所示:
void PictureLayerImpl::SyncTiling(
const PictureLayerTiling* tiling) {
......
tilings_->AddTiling(tiling->contents_scale());
// If this tree needs update draw properties, then the tiling will
// get updated prior to drawing or activation. If this tree does not
// need update draw properties, then its transforms are up to date and
// we can create tiles for this tiling immediately.
if (!layer_tree_impl()->needs_update_draw_properties() &&
should_update_tile_priorities_) {
UpdateTilePriorities();
}
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
注意,参数tiling描述的是一个Layer新创建的Tiling,而当前正在处理的PictureLayerImpl对象描述的是其对应的Twin Layer。PictureLayerImpl类的成员函数SyncTiling所做的工作就是为后者创建一个与参数tiling一样的Tiling,也就是创建一个具有相同Raster Scale的Tiling。这同样也是通过调用前面分析的PictureLayerTilingSet类的成员函数AddTiling创建的。
PictureLayerImpl类的成员函数SyncTiling为一个Layer的Twin Layer创建了一个相同的Tiling之后,接下来会判断后者是否需要重绘。如果需要,那么这个Twin Layer在重绘之前,会调用PictureLayerImpl类的成员函数UpdateTilePriorities为新创建的Tiling创建分块。否则的话,现在就可以马上调用PictureLayerImpl类的成员函数UpdateTilePriorities为新创建的Tiling创建分块。
这一步执行完成之后,回到PictureLayerImpl类的成员函数UpdateTiles中,这时候它就为当前正在处理的Layer创建了两个Tiling。其中一个Tiling的Raster Scale为High Res Raster Contents Scale,另一个Tiling的Raster Scale为Low Res Raster Contents Scale。PictureLayerImpl类的成员函数UpdateTiles接下来需要为这两个Tiling创建分块,这是通过调用另外一个成员函数UpdateTilePriorities实现的,如下所示:
void PictureLayerImpl::UpdateTilePriorities() {
......
gfx::Rect visible_rect_in_content_space(
GetViewportForTilePriorityInContentSpace());
visible_rect_in_content_space.Intersect(visible_rect_for_tile_priority_);
gfx::Rect visible_layer_rect = gfx::ScaleToEnclosingRect(
visible_rect_in_content_space, 1.f / contents_scale_x());
WhichTree tree =
layer_tree_impl()->IsActiveTree() ? ACTIVE_TREE : PENDING_TREE;
for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
// TODO(sohanjg): Passing MaximumContentsScale as layer contents scale
// in UpdateTilePriorities is wrong and should be ideal contents scale.
tilings_->tiling_at(i)->UpdateTilePriorities(tree,
visible_layer_rect,
MaximumTilingContentsScale(),
current_frame_time_in_seconds);
}
// Tile priorities were modified.
layer_tree_impl()->DidModifyTilePriorities();
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
PictureLayerImpl类的成员函数UpdateTilePriorities首先计算出来当前正在处理的Layer的可见部分大小visible_layer_rect,接着遍历保存成员变量tilings_描述的一个PictureLayerTilingSet对象中的每一个Tiling,并且让这些Tiling根据前面计算出来的visible_layer_rect重新其内部的分块优先级。
从前面的分析可以知道,Layer的每一个Tiling都是通过一个PictureLayerTiling对象描述的,更新Tiling中的分块优先级就可以通过调用这些PictureLayerTiling对象的成员函数UpdateTilePriorities实现。在更新分块优先级的过程中,如果发现分块还没有创建,那么就会先创建。
PictureLayerImpl类的成员函数UpdateTilePriorities最后调用成员函数layer_tree_impl获得一个LayerTreeImpl对象。这个LayerTreeImpl对象描述的就是CC Pending Layer Tree。有了这个LayerTreeImpl对象之后,就可以调用它的成员函数DidModifyTilePriorities通知调度器CC Pening Layer Tree的分块已经创建完毕,并且这些分块的优先级也已经确定。调度器获得这个通知之后,接下来就会请求Compositor线程对CC Pending Layer Tree进行光栅化操作。
接下来我们先分析PictureLayerTiling类的成员函数UpdateTilePriorities的实现,接着再分析LayerTreeImpl类的成员函数DidModifyTilePriorities的实现。
PictureLayerTiling类的成员函数UpdateTilePriorities的实现如下所示:
void PictureLayerTiling::UpdateTilePriorities(
WhichTree tree,
const gfx::Rect& visible_layer_rect,
float layer_contents_scale,
double current_frame_time_in_seconds) {
......
gfx::Rect visible_rect_in_content_space =
gfx::ScaleToEnclosingRect(visible_layer_rect, contents_scale_);
......
size_t max_tiles_for_interest_area = client_->GetMaxTilesForInterestArea();
gfx::Size tile_size = tiling_data_.max_texture_size();
int64 eventually_rect_area =
max_tiles_for_interest_area * tile_size.width() * tile_size.height();
gfx::Rect skewport = ComputeSkewport(current_frame_time_in_seconds,
visible_rect_in_content_space);
......
gfx::Rect eventually_rect =
ExpandRectEquallyToAreaBoundedBy(visible_rect_in_content_space,
eventually_rect_area,
TilingRect(),
&expansion_cache_);
.....
SetLiveTilesRect(eventually_rect);
......
current_visible_rect_in_content_space_ = visible_rect_in_content_space;
current_skewport_ = skewport;
current_eventually_rect_ = eventually_rect;
......
TilePriority now_priority(resolution_, TilePriority::NOW, 0);
float content_to_screen_scale = layer_contents_scale / contents_scale_;
// Assign now priority to all visible tiles.
bool include_borders = true;
for (TilingData::Iterator iter(
&tiling_data_, visible_rect_in_content_space, include_borders);
iter;
++iter) {
TileMap::iterator find = tiles_.find(iter.index());
if (find == tiles_.end())
continue;
Tile* tile = find->second.get();
tile->SetPriority(tree, now_priority);
}
// Assign soon priority to skewport tiles.
for (TilingData::DifferenceIterator iter(
&tiling_data_, skewport, visible_rect_in_content_space);
iter;
++iter) {
TileMap::iterator find = tiles_.find(iter.index());
if (find == tiles_.end())
continue;
Tile* tile = find->second.get();
gfx::Rect tile_bounds =
tiling_data_.TileBounds(iter.index_x(), iter.index_y());
float distance_to_visible =
visible_rect_in_content_space.ManhattanInternalDistance(tile_bounds) *
content_to_screen_scale;
TilePriority priority(resolution_, TilePriority::SOON, distance_to_visible);
tile->SetPriority(tree, priority);
}
// Assign eventually priority to interest rect tiles.
for (TilingData::DifferenceIterator iter(
&tiling_data_, eventually_rect, skewport);
iter;
++iter) {
TileMap::iterator find = tiles_.find(iter.index());
if (find == tiles_.end())
continue;
Tile* tile = find->second.get();
gfx::Rect tile_bounds =
tiling_data_.TileBounds(iter.index_x(), iter.index_y());
float distance_to_visible =
visible_rect_in_content_space.ManhattanInternalDistance(tile_bounds) *
content_to_screen_scale;
TilePriority priority(
resolution_, TilePriority::EVENTUALLY, distance_to_visible);
tile->SetPriority(tree, priority);
}
// Upgrade the priority on border tiles to be SOON.
current_soon_border_rect_ = visible_rect_in_content_space;
float border = kSoonBorderDistanceInScreenPixels / content_to_screen_scale;
current_soon_border_rect_.Inset(-border, -border, -border, -border);
for (TilingData::DifferenceIterator iter(
&tiling_data_, current_soon_border_rect_, skewport);
iter;
++iter) {
TileMap::iterator find = tiles_.find(iter.index());
if (find == tiles_.end())
continue;
Tile* tile = find->second.get();
TilePriority priority(resolution_,
TilePriority::SOON,
tile->priority(tree).distance_to_visible);
tile->SetPriority(tree, priority);
}
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
PictureLayerTiling类的成员函数UpdateTilePriorities会根据当前正在处理的Tiling的缩放因子计算出4个区域,如图3所示:
图3 Tiling的区域划分
这4个区域分别是:
1. Visible rect:描述Layer的当前可见区域。
2. Skewport:根据Layer的移动方向和速度计算出来的区域。假设Layer按Y轴方向移动,移动前的坐标为old_y,移动后的坐标为new_y,从old_y到new_y耗费的时间为delta秒,那么移动速度就为(new_y - old_y) / delta。按照这个速度移动1秒的时间,就得到Skewport的大小。注意,Skewport是在Visible rect的基础上进行计算的,也就是两者的原点是一样的。还有一点需要注意的是,如果光栅化操作是通过GPU实现的,那么Skewport的大小与Visible rect是一致的。
3. Border rect:由Visible rect在四周分别扩展312 pixels后得到。
4. Eventually rect:一个由128个分块组成的区域,这个区域以Visible rect为中心。
位于上述区域中的分块都是需要创建的,并且在创建之后,需要给它们赋予优先级。以后CC模块就会根据它们的优先级对它们进行光栅化,其中:
1. 位于Visible rect中的分块优先级最高,设置为TilePriority::NOW,表示它们现在就需要显示。
2. 位于Skewport和Border rect中但是不位于Visible中的分块的优先级次之,设置为TilePriority::SOON,表示它们可能很快就需要显示。
3. 位于Eventually rect中但是不位于Skewport和Border rect中的分块优先级最低,设置为TilePriority::EVENTUALLY,表示它们最终可能会被显示。
位于Skewport、Border rect和Eventually rect中的分块,它们最终的优先级还与它们与Visible rect的距离有关。距离越远,优先级越低。
在图3中,我们还看到一个Tiling rect。这个区域表示Layer的内容区域,它可能是一个远远大于Visible rect的区域,所以CC模块才会对它进一步划分,以区分出重要和不重要的区域。重要区域是现在或者最近需要显示的,因此要对它们执行光栅化操作。不重要区域是可能永远都不需要显示,或者很久之后才会显示。这部分区域不着急对它们执行光栅化操作。
分块的优先级的计算很复杂。除了需要考虑它们的TilePriority::NOW、TilePriority::SOON和TilePriority::EVENTUALLY属性和它们到Visible rect的距离,还需要考虑它们是属于CC Pending Layer Tree还是CC Active Layer Tree,以及它们所属的Tiling的分辨率。
给分块设置TilePriority::NOW、TilePriority::SOON和TilePriority::EVENTUALLY属性,是为了给它们分类。一个分类也称为一个Priority Bin。一共有三个Priority Bin,分别称为Now Bin、Soon Bin和Eventually Bin。
给一个分块设置优先级可以通过调用Tile类的成员函数SetPriority实现,如下所示:
void Tile::SetPriority(WhichTree tree, const TilePriority& priority) {
if (priority == priority_[tree])
return;
priority_[tree] = priority;
......
}
这个函数定义在文件external/chromium_org/cc/resources/tile.cc中。
Tile类的成员变量priotity_是一个类型为TilePriority的数组,数组的大小为2,它的定义如下所示:
class CC_EXPORT Tile : public RefCountedManaged<Tile> {
......
private:
......
TilePriority priority_[NUM_TREES];
......
};
这个成员变量定义在文件external/chromium_org/cc/resources/tile.cc中。
NUM_TREES是一个类型为WhichTree的枚举值,后者的定义如下所示:
enum WhichTree {
// Note: these must be 0 and 1 because we index with them in various places,
// e.g. in Tile::priority_.
ACTIVE_TREE = 0,
PENDING_TREE = 1,
NUM_TREES = 2
// Be sure to update WhichTreeAsValue when adding new fields.
};
这个枚举定义在文件external/chromium_org/cc/resources/tile_priority.h。
从这里可以看到NUM_TREES的值等于2。WhichTree还定义了另外两个枚举值ACTIVE_TREE和PENDING_TREE,分别表示CC Active Layer Tree和CC Pending Layer Tree。
回到Tile类的成员函数SetPriority中,从它的参数和实现可以看出,一个分块有两个优先级,其中一个是为CC Active Layer Tree设置的,另一个是为CC Pending Layer Tree设置的。每一个优先级都是通过一个TilePriority对象描述。
为什么一个分块的优先级会有两个呢?前面我们提到,在CC Pending Layer Tree和CC Active Layer Tree中,两个具有相同ID的Layer称为Twin Layer。两个Twin Layer中的Tiling是可以共享分块的,如图4所示:
图4 在CC Pending Layer Tree与CC Active Layer Tree之间共享分块
在图4中,编号为3、4、6、7、8和9的分块在两个Twin Layer中是共享的,其余分块对每一个Layer来说,都是唯一的,不共享的。对于非共享的分块,它们的优先级可以根据它们所属的Tree确定。我们考虑图4中编号为16的分块,它是非共享的,并且属于CC Active Layer Tree。这个分块的优先级可以通过调用与它对应的一个Tile对象的成员函数priority获得,如下所示:
class CC_EXPORT Tile : public RefCountedManaged<Tile> {
public:
......
const TilePriority& priority(WhichTree tree) const {
return priority_[tree];
}
......
};
这个函数定义在文件external/chromium_org/cc/resources/tile.h中。
这里将参数tree设置为ACTIVE_TREE即可。
相应地,如果我们需要获得图4中编号为24的分块的优先级,那么在调用与它对应的一个Tile对象的成员函数priority时,将参数设置为PENDING_TREE即可。
对于共享的分块,它最终的优先级由它在CC Active Layer Tree和CC Pending Layer Tree的优先级组合而成,如下所示:
class CC_EXPORT Tile : public RefCountedManaged<Tile> {
public:
......
TilePriority combined_priority() const {
return TilePriority(priority_[ACTIVE_TREE],
priority_[PENDING_TREE]);
}
......
};
这个函数定义在文件external/chromium_org/cc/resources/tile.h中。
这个组合出来的优先级也是由一个TilePriority对象描述的。在分析组合规则之前,我们先分析TilePriority类的实现。
如前所述,TilePriority类用来描述分块的优先级,它有4个成员变量,如下所示:
enum TileResolution {
LOW_RESOLUTION = 0 ,
HIGH_RESOLUTION = 1,
NON_IDEAL_RESOLUTION = 2,
};
......
struct CC_EXPORT TilePriority {
enum PriorityBin { NOW, SOON, EVENTUALLY };
......
TileResolution resolution;
bool required_for_activation;
PriorityBin priority_bin;
float distance_to_visible;
};
这个类定义在文件external/chromium_org/cc/resources/tile_priority.h中。
这4个成员变量的含义如下所示:
1. resolution:表示分块所属的Tiling的分辨率类型。从前面分析的PictureLayerImpl类的成员函数AddTilingsForRasterScale可以知道。一个Tiling的分辨率类型有三种,分别是LOW_RESOLUTION、HIGH_RESOLUTION和NON_IDEAL_RESOLUTION。其中,LOW_RESOLUTION表示Tiling是临时使用,也就是在用户Pinch或者Scroll网页的过程中临时使用,HIGH_RESOLUTION表示Tiling在网页最终按照1:1比例显示时使用,NON_IDEAL_RESOLUTION表示Tiling不是当前需要用到的。
2. required_for_activation:表示分块光栅化操作完成后,需要触发图1所示的ACTION_ACTIVE_PENDING_TREE操作,也就是该分块是属于CC Pending Layer Tree的。CC Pending Layer Tree的分块完成光栅化操作后,需要激活为CC Active Layer Tree。
3. priority_bin:表示分块的类型,也就是分块被划分为Now Bin、Soon Bin还是Eventually Bin。
如果一个分块是不共享的,也就是它只属于其中一个Tree,那么描述的它的优先级的TreePriority对象可以通过调用TreePriority类的以下构造函数创建,如下所示:
struct CC_EXPORT TilePriority {
......
TilePriority(TileResolution resolution,
PriorityBin bin,
float distance_to_visible)
: resolution(resolution),
required_for_activation(false),
priority_bin(bin),
distance_to_visible(distance_to_visible) {}
......
};
这个函数定义在文件external/chromium_org/cc/resources/tile_priority.h中。
在创建时直接指定resolution、priority_bin和distance_to_visible属性,另外一个required_for_activation默认为false。
如果一个分块是共享的,也就是它同时属于两个Tree,那么描述的它的优先级的TreePriority对象可以通过调用TreePriority类的以下构造函数创建,如下所示:
struct CC_EXPORT TilePriority {
......
TilePriority(const TilePriority& active, const TilePriority& pending) {
if (active.resolution == HIGH_RESOLUTION ||
pending.resolution == HIGH_RESOLUTION)
resolution = HIGH_RESOLUTION;
else if (active.resolution == LOW_RESOLUTION ||
pending.resolution == LOW_RESOLUTION)
resolution = LOW_RESOLUTION;
else
resolution = NON_IDEAL_RESOLUTION;
required_for_activation =
active.required_for_activation || pending.required_for_activation;
if (active.priority_bin < pending.priority_bin) {
priority_bin = active.priority_bin;
distance_to_visible = active.distance_to_visible;
} else if (active.priority_bin > pending.priority_bin) {
priority_bin = pending.priority_bin;
distance_to_visible = pending.distance_to_visible;
} else {
priority_bin = active.priority_bin;
distance_to_visible =
std::min(active.distance_to_visible, pending.distance_to_visible);
}
}
......
};
这个函数定义在文件external/chromium_org/cc/resources/tile_priority.h中。
参数active描述的是分块在CC Acitive Layer Tree中的优先级,另外一个参数pending描述的是分块在CC Pending Layer Tree中的优先级。接下来我们分别看TreePriority类的4个成员变量的取值:
1. resolution:取active.resolution与pending.resolution之间的较高优先级值,其中,HIGH_RESOLUTION > LOW_RESOLUTION > NON_IDEAL_RESOLUTION。
2. required_for_activation:只要active.required_for_activation与pending.required_for_activation之一为true,那么就设置为true。
3. priority_bin:取active.priority_bin与pending.priority_bin之间的较高优先级值,其中,NOW > SOON > EVENTUALLY。
4. distance_to_visible:取值规则与priority_bin保持一致,即如果priority_bin取值于active.priority_bin,则distance_to_visible取值于active.distance_to_visible,否则取值于pending.distance_to_visible,不过如果active.priority_bin等于pending.priority_bin,则取active.distance_to_visible与pending.distance_to_visible之间的较小值者。
上述取值规则也是将两个TilePriority组合成一个TilePriority的规则。
TilePrioirty类提供一个成员函数IsHigherPriorityThan,用来比较两个TilePriority的优先级高低,它的实现如下所示:
struct CC_EXPORT TilePriority {
......
bool IsHigherPriorityThan(const TilePriority& other) const {
return priority_bin < other.priority_bin ||
(priority_bin == other.priority_bin &&
distance_to_visible < other.distance_to_visible);
}
......
};
这个函数定义在文件external/chromium_org/cc/resources/tile_priority.h中。
TilePrioirty类的成员函数IsHigherPriorityThan在比较两个TilePriority的优先级时,只要是看它们的Priority Bin和Distance to Visible属性:
1. 如果两者的Priority Bin不相等,那么Priority Bin优先级高的TilePriority优先级高。
2. 如果两者的Priority Bin相等,则Distance to Visible值小的TilePriority优先级高。
理解了分块的优先级之后,回到PictureLayerTiling类的成员函数UpdateTilePriorities中,它在设置分块的优先级之前,会调用另外一个成员函数SetLiveTilesRect,用来检查哪些分块是需要创建的,哪些分块是需要移除的,它的实现如下所示:
void PictureLayerTiling::SetLiveTilesRect(
const gfx::Rect& new_live_tiles_rect) {
......
// Iterate to delete all tiles outside of our new live_tiles rect.
PictureLayerTiling* recycled_twin = client_->GetRecycledTwinTiling(this);
for (TilingData::DifferenceIterator iter(&tiling_data_,
live_tiles_rect_,
new_live_tiles_rect);
iter;
++iter) {
TileMapKey key(iter.index());
TileMap::iterator found = tiles_.find(key);
// If the tile was outside of the recorded region, it won't exist even
// though it was in the live rect.
if (found != tiles_.end()) {
tiles_.erase(found);
if (recycled_twin)
recycled_twin->RemoveTileAt(iter.index_x(), iter.index_y());
}
}
const PictureLayerTiling* twin_tiling = client_->GetTwinTiling(this);
// Iterate to allocate new tiles for all regions with newly exposed area.
for (TilingData::DifferenceIterator iter(&tiling_data_,
new_live_tiles_rect,
live_tiles_rect_);
iter;
++iter) {
TileMapKey key(iter.index());
CreateTile(key.first, key.second, twin_tiling);
}
live_tiles_rect_ = new_live_tiles_rect;
}
这个函数定义在文件external/chromium_org/cc/resources/picture_layer_tiling.cc中。
从前面的调用过程可以知道,参数new_live_tiles_rect描述的是图2所示的Eventually rect。位于这个Eventually rect中的分块都是需要创建的。
PictureLayerTiling类的成员变量live_tiles_rect_保存的是上一次使用的Eventually rect。注意,这里可以保证位于上一次使用的Eventually rect中的分块都是已经创建好了的。因此,当前需要创建的分块是位于(new_live_tiles_rect - live_tilesrect)区域中的。同时也意味着位于(live_tilesrect - new_live_tiles_rect )区域中的分块不再需要了的,它们需要删除,以节省空间。
PictureLayerTiling类的成员函数SetLiveTilesRect做的第一件事情就是将位于(live_tilesrect - new_live_tiles_rect )区域中的分块删除,这是通过第一个for循环完成的。
前面分析LayerTreeHostImpl类的成员函数CreatePendingTree时提到,LayerTreeHostImpl类内部维护有一个Recycle Layer Tree。这个Recycle Layer Tree实际就是上一次使用CC Pending Layer Tree。维护这个Recycle Layer Tree的目的是使得它可以复用为下一个CC Pending Layer Tree。这意味着在这个Recycle Layer Tree中,也有可能存在一个与当前正在处理的Tiling对应的Tiling。这个Tiling称为Recycled Twin Tiling。现在由于当前正在处理的Tiling的某些分块被删除了,因此也需要其对应的Recycled Twin Tiling的对应分块也删除掉,以保持两者的一致性,这样才可以使得Recycle Layer Tree以后能被正确复用。
通过调用成员变量client_指向的一个PictureLayerImpl对象的成员函数GetRecycledTwinTiling可以在Recycle Layer Tree获得与当前正在处理的Tiling对应的Recycled Twin Tiling。PictureLayerImpl对象的成员函数GetRecycledTwinTiling首先获得当前正在处理的Tiling所属的Layer的ID,接着根据在这个ID在Recycle Layer Tree中找到一个具有相同ID的Layer。这个Layer称为Recycled Twin Layer。有了这个Recycled Twin Layer之后,再在里面找一个与当前正在处理的Tiling有相同Raster Scale的Tiling。这个Tiling就是我们要找到的Recycled Twin Tiling了。有了这个Recycled Twin Tiling之后,就可以将那些位于(live_tilesrect - new_live_tiles_rect )区域中的分块也删除掉。
PictureLayerTiling类的成员函数SetLiveTilesRect做的第二件事情就是为(new_live_tiles_rect - live_tilesrect)区域创建分块,这是通过第二个for循环完成的。在这一个区域中的每一个分块都是通过调用PictureLayerTiling类的成员函数CreateTile创建的。
在调用PictureLayerTiling类的成员函数CreateTile创建一个分块之前,需要获在CC Active Layer Tree中获得一个与当前正在处理的Tiling对应的Tiling。这两个Tiling也称为Twin Tiling。这个Twin Tiling可以通过调用成员变量client_指向的一个PictureLayerImpl对象的成员函数GetTwinTiling获得。PictureLayerImpl对象的成员函数GetTwinTiling首先获得当前正在处理的Tiling所属的Layer的ID,接着根据在这个ID在CC Active Layer Tree中找到一个具有相同ID的Layer。这个Layer就是前面所说的Twin Layer。有了这个Twin Layer之后,再在里面找一个与当前正在处理的Tiling有相同Raster Scale的Tiling。这个Tiling就是我们要找到的Twin Tiling了。
PictureLayerTiling类的成员函数SetLiveTilesRect做的第三件事情就是将参数new_live_tiles_rect描述的Eventually rect保存在成员变量live_tiles_rect_中,以便下一次更新当前正在处理的Tiling时,可以增量地创建不存在的分块。
接下来我们继续分析PictureLayerTiling类的成员函数CreateTile的实现,以便了解每一个分块的创建过程,如下所示:
Tile* PictureLayerTiling::CreateTile(int i,
int j,
const PictureLayerTiling* twin_tiling) {
TileMapKey key(i, j);
......
gfx::Rect paint_rect = tiling_data_.TileBoundsWithBorder(i, j);
gfx::Rect tile_rect = paint_rect;
tile_rect.set_size(tiling_data_.max_texture_size());
// Check our twin for a valid tile.
if (twin_tiling &&
tiling_data_.max_texture_size() ==
twin_tiling->tiling_data_.max_texture_size()) {
if (Tile* candidate_tile = twin_tiling->TileAt(i, j)) {
gfx::Rect rect =
gfx::ScaleToEnclosingRect(paint_rect, 1.0f / contents_scale_);
if (!client_->GetInvalidation()->Intersects(rect)) {
tiles_[key] = candidate_tile;
return candidate_tile;
}
}
}
// Create a new tile because our twin didn't have a valid one.
scoped_refptr<Tile> tile = client_->CreateTile(this, tile_rect);
if (tile.get())
tiles_[key] = tile;
return tile.get();
}
这个函数定义在文件external/chromium_org/cc/resources/picture_layer_tiling.cc中。
要创建的分块通过参数i和j描述,表示第i行第j列的分块。PictureLayerTiling类的成员函数CreateTile首先根据参数i和j,以及成员变量tiling_data描述的分块信息,计算出即将要创建的分块所占据的区域tile_rect。接下来再在参数twin_tiling描述的Twin Tiling中检查它第i行第j列的分块是否已经创建。如果这个分块已经创建,并且不在当前帧的重绘区域中,那么它就被会当前正在处理的Tiling所使用。这也意味着该分块是一个共享分块,同时被CC Pending Layer Tree和CC Active Layer Tree使用。为什么要求这个分块不在当前帧的重绘区域中呢?因为如果这个分块在当前帧的重绘区域中,那么它就是一个无效的分块。无效的分块是不能直接使用的。
如果在参数twin_tiling描述的Twin Tiling中,第i行第j列的分块还没有创建,或者已经创建,但是已经失效,那么PictureLayerTiling类的成员函数CreateTile就会调用成员变量client_指向的一个PictureLayerImpl对象的成员函数CreateTile为当前正在处理的Tiling的第i行第j列创建一个新的分块。
不管是通过哪一种方式得到当前正在处理的Tiling的第i行第j列的分块,它最终都会被保存在PictureLayerTiling类的成员变量tiles_描述的一个Map中。从这里我们就可以知道,PictureLayerTiling类的成员变量tiles_描述的Map保存了一个Tiling当前创建的所有的分块。
接下来我们继续分析PictureLayerImpl类的成员函数CreateTile的实现,以便了解一个分块的创建过程,如下所示:
scoped_refptr<Tile> PictureLayerImpl::CreateTile(PictureLayerTiling* tiling,
const gfx::Rect& content_rect) {
......
int flags = Tile::USE_PICTURE_ANALYSIS;
return layer_tree_impl()->tile_manager()->CreateTile(
pile_.get(),
content_rect.size(),
content_rect,
contents_opaque() ? content_rect : gfx::Rect(),
tiling->contents_scale(),
id(),
layer_tree_impl()->source_frame_number(),
flags);
}
这个函数定义在文件external/chromium_org/cc/resources/tile_manager.cc。
PictureLayerImpl类的成员函数CreateTile首先调用另外一个成员函数layer_tree_impl获得一个LayerTreeImpl对象。这个LayerTreeImpl对象描述的就是一个CC Pending Layer Tree。有了这个LayerTreeImpl对象之后,再调用它的成员函数tile_manager获得一个TileManager对象。这个TileManager对象负责光栅化CC Pending Layer Tree的所有分块,它的创建过程可以参考前面Chromium网页绘图表面(Output Surface)创建过程分析一文。
获得负责执行光栅化操作的TileManager对象之后,PictureLayerImpl类的成员函数CreateTile就调用它的成员函数CreateTile创建一个分块。在创建这个分块的时候,指定了一个Tile::USE_PICTURE_ANALYSIS标志,表示以后对该分块执行光栅化操作之前,先分析一下它的光栅化结果是否是一个Solid Color。如果是的话,那么只要计算出这个Solid Color的RGBA值即可,而不需要去执行相应的绘制命令来获得每一个像素的值。
TileManager类的成员函数CreateTile的实现如下所示:
scoped_refptr<Tile> TileManager::CreateTile(PicturePileImpl* picture_pile,
const gfx::Size& tile_size,
const gfx::Rect& content_rect,
const gfx::Rect& opaque_rect,
float contents_scale,
int layer_id,
int source_frame_number,
int flags) {
scoped_refptr<Tile> tile = make_scoped_refptr(new Tile(this,
picture_pile,
tile_size,
content_rect,
opaque_rect,
contents_scale,
layer_id,
source_frame_number,
flags));
......
tiles_[tile->id()] = tile;
......
prioritized_tiles_dirty_ = true;
return tile;
}
这个函数定义在文件external/chromium_org/cc/resources/tile_manager.cc中。
TileManager类的成员函数CreateTile首先创建了一个Tile对象。这个Tile对象描述的就是要创建的分块,它将以它所在的Layer的ID为键值,保存在TileManager类的成员变量tiles_描述的一个Map中。从这里我们就可以看到,不管一个分块是属于哪一个Layer的,也不管这个Layer是属于CC Pending Layer Tree,还是属于CC Active Layer Tree,它都是直接被TileManager类管理的。换句话说,就是TileManager类知道CC Pending Layer Tree和CC Active Layer Tree的所有分块,并且这些分块保存它的成员变量tiles_描述的一个Map中。
TileManager类的成员函数CreateTile在将创建的分块返回给调用者之前,还会将成员变量prioritized_tiles_dirty_的值设置为true,表示它所管理的分块发生了变化,需要重新按照优先级进行排序,然后执行光栅化操作。这一点我们在接下来的一篇文章中再分析。
这一步执行完成之后,经过ACTION_COMMIT操作后得到的CC Pending Layer Tree的分块就创建完毕,并且这些分块已经被赋予了优先级。回到PictureLayerImpl类的成员函数UpdateTilePriorities中,它接下来就会调用LayerTreeImpl类的成员函数DidModifyTilePriorities通知调度器CC Pending Layer Tree的分块优先级发生了变化,需要调度执行一个ACTION_MANAGE_TILES操作,如下所示:
void LayerTreeImpl::DidModifyTilePriorities() {
layer_tree_host_impl_->DidModifyTilePriorities();
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_impl.cc中。
LayerTreeImpl类的成员变量layer_tree_host_impl_指向的是一个LayerTreeHostImpl对象。这个LayerTreeHostImpl对象负责管理CC Pending Layer Tree和CC Active Layer Tree。有了这个LayerTreeHostImpl对象之后,LayerTreeImpl类的成员函数DidModifyTilePriorities就调用它的成员函数DidModifyTilePriorities通知调度器CC Pending Layer Tree的分块优先级发生了变化,如下所示:
void LayerTreeHostImpl::DidModifyTilePriorities() {
......
tile_priorities_dirty_ = true;
client_->SetNeedsManageTilesOnImplThread();
}
这个函数定义在文件external/chromium_org/cc/trees/layer_tree_host_impl.cc中。
LayerTreeHostImpl类的成员函数DidModifyTilePriorities首先将成员变量tile_priorities_dirty_的值设置为true,表示CC Pending Layer Tree的分块优先级发生了变化,接下来再调用成员变量client_指向的一个ThreadProxy对象的成员函数SetNeedsManageTilesOnImplThread通知调度器调度执行一个ACTION_MANAGE_TILES操作,如下所示:
void ThreadProxy::SetNeedsManageTilesOnImplThread() {
DCHECK(IsImplThread());
impl().scheduler->SetNeedsManageTiles();
}
这个函数定义在文件external/chromium_org/cc/trees/thread_proxy.cc中。
ThreadProxy类的成员函数SetNeedsManageTilesOnImplThread调用Scheduler类的成员函数SetNeedsManageTiles通知调度器调度执行一个ACTION_MANAGE_TILES操作,如下所示:
void Scheduler::SetNeedsManageTiles() {
......
state_machine_.SetNeedsManageTiles();
ProcessScheduledActions();
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler.cc中。
Scheduler类的成员函数SetNeedsManageTiles首先调用SchedulerStateMachine类的成员函数SetNeedsManageTiles修改调度器内部使用的状态机的状态,如下所示:
void SchedulerStateMachine::SetNeedsManageTiles() {
if (!needs_manage_tiles_) {
......
needs_manage_tiles_ = true;
}
}
这个函数定义在文件external/chromium_org/cc/scheduler/scheduler_state_machine.cc中。
SchedulerStateMachine类的成员函数SetNeedsManageTiles检查其成员变量needs_manage_tiles_的值是否等于true。如果不等于,那么就将它的值设置为true,表示接下来要执行一个ACTION_MANAGE_TILES操作。
回到Scheduler类的成员函数SetNeedsManageTiles中,当它将调度器内部使用的状态机的成员变量needs_manage_tiles_的值设置为true之后,再调用另外一个成员函数ProcessScheduledActions时,就会触发调度器请求Compositor线程执行一个ACTION_MANAGE_TILES操作,也就是请求Compositor线程对CC Pending Layer Tree执行光栅化操作。这个光栅化操作的执行过程我们在接下来的一篇文章中再详细分析。
这一步执行完成之后,CC Pending Layer Tree的光栅化操作就准备就绪了。回到前面分析的PictureLayerImpl类的成员函数UpdateTiles中,它还需要执行一个操作,就是将CC Pending Layer Tree中的分块标记为Required For Activation,也就是CC模块正在等待这些分块的光栅化操作执行完成,以便将CC Pending Layer Tree激活为CC Active Layer Tree,这是通过调用PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired实现的,如下所示:
void PictureLayerImpl::MarkVisibleResourcesAsRequired() const {
......
gfx::Rect rect(visible_rect_for_tile_priority_);
......
float min_acceptable_scale =
std::min(raster_contents_scale_, ideal_contents_scale_);
if (PictureLayerImpl* twin = twin_layer_) {
float twin_min_acceptable_scale =
std::min(twin->ideal_contents_scale_, twin->raster_contents_scale_);
// Ignore 0 scale in case CalculateContentsScale() has never been
// called for active twin.
if (twin_min_acceptable_scale != 0.0f) {
min_acceptable_scale =
std::min(min_acceptable_scale, twin_min_acceptable_scale);
}
}
PictureLayerTiling* high_res = NULL;
PictureLayerTiling* low_res = NULL;
// First pass: ready to draw tiles in acceptable but non-ideal tilings are
// marked as required for activation so that their textures are not thrown
// away; any non-ready tiles are not marked as required.
Region missing_region = rect;
for (size_t i = 0; i < tilings_->num_tilings(); ++i) {
PictureLayerTiling* tiling = tilings_->tiling_at(i);
......
if (tiling->resolution() == LOW_RESOLUTION) {
......
low_res = tiling;
}
if (tiling->contents_scale() < min_acceptable_scale)
continue;
if (tiling->resolution() == HIGH_RESOLUTION) {
......
high_res = tiling;
continue;
}
for (PictureLayerTiling::CoverageIterator iter(tiling,
contents_scale_x(),
rect);
iter;
++iter) {
if (!*iter || !iter->IsReadyToDraw())
continue;
missing_region.Subtract(iter.geometry_rect());
iter->MarkRequiredForActivation();
}
}
......
// If these pointers are null (because no twin, no matching tiling, or the
// simpification just below), then high res tiles will be required to fill any
// holes left by the first pass above. If the pointers are valid, then this
// layer is allowed to skip any tiles that are not ready on its twin.
const PictureLayerTiling* twin_high_res = NULL;
const PictureLayerTiling* twin_low_res = NULL;
if (twin_layer_) {
// As a simplification, only allow activating to skip twin tiles that the
// active layer is also missing when both this layer and its twin have
// "simple" sets of tilings: only 2 tilings (high and low) or only 1 high
// res tiling. This avoids having to iterate/track coverage of non-ideal
// tilings during the last draw call on the active layer.
if (tilings_->num_tilings() <= 2 &&
twin_layer_->tilings_->num_tilings() <= tilings_->num_tilings()) {
twin_low_res = low_res ? GetTwinTiling(low_res) : NULL;
twin_high_res = high_res ? GetTwinTiling(high_res) : NULL;
}
......
}
// As a second pass, mark as required any visible high res tiles not filled in
// by acceptable non-ideal tiles from the first pass.
if (MarkVisibleTilesAsRequired(
high_res, twin_high_res, contents_scale_x(), rect, missing_region)) {
// As an optional third pass, if a high res tile was skipped because its
// twin was also missing, then fall back to mark low res tiles as required
// in case the active twin is substituting those for missing high res
// content. Only suitable, when low res is enabled.
if (low_res) {
MarkVisibleTilesAsRequired(
low_res, twin_low_res, contents_scale_x(), rect, missing_region);
}
}
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired所做的事情就是将覆盖在当前正在处理的Layer在可见区域中的分块标记为Required For Activation。这些分块主要是来自于CC Pending Layer Tree,但是也有可能来自于CC Acitve Layer Tree,因为这两个Tree可能会共享一些分块。
PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired做的第一件事情就是计算可见区域rect,以及计算当前正在处理的Layer的最小Raster Scale。从前面的分析可以知道,PictureLayerImpl类的成员变量ideal_contents_scale_表示当前正在处理的Layer的Ideal Raster Scale,另外一个成员变量raster_contents_scale_表示当前正在处理的Layer的High Res Raster Scale。High Res Raster Scale一般情况下就等于Ideal Raster Scale。不过,如果用户正在Pinch网页,那么High Res Raster Scale是根据上一次使用的Raster Scale计算出来的,具体可以参考前面分析的PictureLayerImpl类的成员函数RecalculateRasterScales。
High Res Raster Scale对应的Tiling也是当前需要进行光栅化的一个Tiling,我们称之为High Res Tiling。此外,CC Pending Layer Tree中的Layer一般还有一个Low Res Tiling,它的Raster Scale等于High Res Raster Scale的0.25倍,称为Low Res Raster Scale。除了High Res Tiling和Low Res Tiling,一个Layer可能还存在其它有着不同的Raster Scale的Tiling。不过,只有High Res Tiling和Low Res Tiling是必要的,其它的Tiling可有可无。
由于一个Layer可能会有多个Tiling,因此它的可见区域可以通过不同的Tiling进行覆盖。PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired首先考虑用Raster Scale值较大的Tiling中的分块来覆盖Layer的可见区域。
什么样的Raster Scale值算是较大呢?当它的值不小于当前正在处理的Layer的High Res Raster Scale与Ideal Raster Scale的较小者时,它就是较大的。由于当前正在处理的Layer的分块有可能来自于它的Twin Layer,因此在确定什么样的Raster Scale值算是较大时,也需要考虑Twin Layer的High Res Raster Scale和Ideal Raster Scale。总的来说,就是一个Raster Scale不小于当前正在处理的Layer及其Twin Layer的High Res Raster Scale与Ideal Raster Scale的最小者时,它就是较大的。之所以要做这个限制,就是避免使用分辨率很低的分块来覆盖Layer的可见区域。
PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired其次考虑使用Low Res Tiling中的分块来覆盖Layer的可见区域。创建Low Res Tiling的目的就是为了让用户可以尽快看到网页内容的,因此它的分块可以优先考虑用来覆盖Layer的可见区域。
PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired最后也会考虑使用High Res Tiling的分块来覆盖Layer的可见区域。将High Res Tiling的分块放在最后考虑使用,是因为它们的光栅化操作是最耗费时间的。
上述三类Tiling的分块按照以下3个规则被选择用来覆盖当前正在处理的Layer的可见区域:
1. 如果Raster Scale较大的Tiling以及Low Res Tiling在Layer可见区域内的分块已经被分配了纹理资源,那么它们最优先选取来覆盖当前正在处理的Layer的可见区域。一个分块如果已经分配了纹理资源,那么调用用来描述它的一个Tiling对象的成员函数IsReadyToDraw的值就会等于true。这种分块已经光栅化完成,或者正在光栅化,它们是最快可以渲染出来的。
2. 如果按照第1个规则选取出来的分块不足于完全覆盖当前正在处理的Layer的可见区域,那么就会使用High Res Tiling的分块来继续覆盖。不过如果这个High Res Tiling存在Twin Tiling,并且该Twin Tiling在对应位置的分块还没有创建,或者已经创建,但是与High Res Tiling共享(意味着还没有光栅化),那么就会拒绝使用后者的分块来继续覆盖区域 。为什么会拒绝呢?因为Twin Tiling是属于CC Active Layer Tree的,在新生成的CC Pending Layer Tree还没有激活之前,上一次使用的CC Active Layer Tree仍然会用来渲染网页的内容。但是在这种情况下,CC Active Layer Tree要用到的分块还没有准备就绪(因为还没有创建,或者创建了但是还没有光栅化),所以应该优先考虑使用Low Res Tilling的分块来覆盖该位置,以便该位置的网页内容以最快速度显示出来,尽管显示出来的内容不是那么清晰,但是也比看不到内容要好。
上述3个规则构成了PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired在注释中标记的三个Pass。第一个Pass执行完成之后,本地变量missing_region记录了当前正在处理的Layer还有哪些可见区域还没有被分块覆盖。这些区域将进一步在第二个Pass和第三个Pass中进行覆盖。这是通过调用PictureLayerImpl类的成员函数MarkVisibleTilesAsRequired实现的。
注意,在第2个规则和第3个规则中,如果High Res Tiling和Low Res Tiling存在Twin Tiling,那么要求该Twin Tiling也是一个High Res Tiling或者Low Res Tiling。如果一个Tiling既不是High Res Tiling,也不是Low Res Tiling,那么它就一定是Non Ideal Tiling。Non Ideal Tiling的分块是允许随时回收的,但是如果我们在选择分块覆盖当前正在处理的Layer的可见区域时,受到了它们的影响,那么就会导致它们不能被回收。这意味着我们需要进一步地跟踪它们的状态,这将会给CC模块的资源管理带来复杂性。因此,为了避免出现这种复杂性,就要求当前正在处理的Layer的High Res Tiling和Low Res Tiling的Twin Tiling也必须是High Res Tiling和Low Res Tiling。
PictureLayerImpl类的成员函数MarkVisibleResourcesAsRequired使用了一种简单的方式判断当前正在处理的Layer的High Res Tiling和Low Res Tiling的Twin Tiling是否也是High Res Tiling和Low Res Tiling。找到当前正在处理的Layer的Twin Layer,如果后者的Tiling个数小于等于2,就意味着它们不是High Res Tiling,就是Low Res Tiling。这是因为:
1. 当一个Layer只有一个Tiling时,那么这个Tiling是High Res Tiling。
2. 当一个Layer只有两个Tiling时,那么其中一个Tiling是High Res Tiling,另一个是Low Res Tiling。
3. 当一个Layer只有两个以上Tiling时,那么就会存至少会存在一个Non Ideal Tiling。
接下来我们继续分析PictureLayerImpl类的成员函数MarkVisibleTilesAsRequired的实现,以便进一步了解上面提到的第2个规则和第3个规则,如下所示:
bool PictureLayerImpl::MarkVisibleTilesAsRequired(
PictureLayerTiling* tiling,
const PictureLayerTiling* optional_twin_tiling,
float contents_scale,
const gfx::Rect& rect,
const Region& missing_region) const {
bool twin_had_missing_tile = false;
for (PictureLayerTiling::CoverageIterator iter(tiling,
contents_scale,
rect);
iter;
++iter) {
Tile* tile = *iter;
// A null tile (i.e. missing recording) can just be skipped.
if (!tile)
continue;
// If the missing region doesn't cover it, this tile is fully
// covered by acceptable tiles at other scales.
if (!missing_region.Intersects(iter.geometry_rect()))
continue;
// If the twin tile doesn't exist (i.e. missing recording or so far away
// that it is outside the visible tile rect) or this tile is shared between
// with the twin, then this tile isn't required to prevent flashing.
if (optional_twin_tiling) {
Tile* twin_tile = optional_twin_tiling->TileAt(iter.i(), iter.j());
if (!twin_tile || twin_tile == tile) {
twin_had_missing_tile = true;
continue;
}
}
tile->MarkRequiredForActivation();
}
return twin_had_missing_tile;
}
这个函数定义在文件external/chromium_org/cc/layers/picture_layer_impl.cc中。
从前面的调用过程可以知道,第一个参数tiling描述的是当前正在处理的Layer的High Res Tiling或者Low Res Tiling,第二个参数optional_twin_tiling表示第一个参数tiling描述的Tiling的Twin Tiling,第四个参数rect描述的是当前正在处理的Layer的可见区域,第五个参数missing_region描述的是经过上面提到的第1个规则之后,当前正在处理的Layer剩下来的还没有被分块覆盖的可见区域。
PictureLayerImpl类的成员函数MarkVisibleTilesAsRequired遍历第一个参数tiling描述的Tiling在当前正在处理的Layer的可见区域上的所有分块。如果这些分块位于上述还没有覆盖的区域上,那么就将它标记为Required For Activation,这是通过调用与它们对应的Tile对象的成员函数MarkRequiredForActivation实现的。
不过如果参数optional_twin_tiling的值不等于NULL,也就是它指向了一个Twin Tiling,那么被标记为Required For Activation的分块还需要同时满足以下两个条件:
1. 参数optional_twin_tiling描述的Twin Tiling在相同位置的分块已经创建。
2. 参数optional_twin_tiling描述的Twin Tiling在相同位置的分块是非共享的,也就是不与参数tiling描述的Tiling共享。
假设参数tiling描述的是当前正在处理的Layer的High Res Tiling。如果上述两个条件之一不能满足,那么就意味着应该优先考虑当前正在处理的Layer的Low Res Tiling的分块覆盖可见区域。
假设参数tiling描述的是当前正在处理的Layer的Low Res Tiling。如果上述两个条件之一不能满足,那么就意味着应该优先考虑当前正在处理的Layer的Low Res Tiling的Twin Tiling的分块覆盖可见区域。注意,这时候不必将Twin Tiling的分块标记为Required For Activation,因为Twin Tiling本身就已经属于CC Active Layer Tree的。
接下来,我们再分析Tile类的成员函数MarkRequiredForActivation的实现,以便了解一个分块被标记为Required For Activation的过程,如下所示:
void Tile::MarkRequiredForActivation() {
if (priority_[PENDING_TREE].required_for_activation)
return;
priority_[PENDING_TREE].required_for_activation = true;
......
}
这个函数定义在文件external/chromium_org/cc/resources/tile.cc中。
从前面的分析可以知道,一个分块有两个Tile Priority,分别用来描述分块在CC Pending Layer Tree和CC Active Layer Tree中的优先级。只有属于CC Pending Layer Tree的分块标记为Required For Activation才有意义,因此Tile类的成员函数MarkRequiredForActivation将一个分块标记为Required For Activation,实际上是将该分块在CC Pending Layer Tree中的Tile Priority标记为Required For Activation。
至此,我们就分析完成了CC Layer Tree同步为CC Pending Layer Tree的过程。这个同步过程是由Compositor线程执行的,但是Main线程会处理睡眠状态。Compositor线程完成同步操作后,就会唤醒Main线程,让它继续执行其它的工作。与此同时,Compositor线程也会为刚刚得到的CC Pending Layer Tree创建分块,以及给这些分块设置优先级和Required For Activation标记。这些工作完成之后,调度器就可以请求Compositor线程对CC Pending Layer Tree执行光栅化操作了。这个光栅化过程我们在接下来的一篇文章中再详细分析,届时我们将会看到分块的优先级和Required For Activation标记是如何被使用的,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。