Property Trees & DispalyItem

//src/third_party/blink/renderer/core/paint/README.md


Property Tree:

DisplayItem:



什么是property tree:

Paint properties define characteristics of how a paint chunk should be drawn,
such as the transform it should be drawn with. To enable efficient updates,
a chunk's paint properties are described hierarchically. For instance, each
chunk is associated with a transform node, whose matrix should be multiplied by
its ancestor transform nodes in order to compute the final transformation matrix
to the screen. 

Property Trees & DispalyItem_第1张图片

Transform Tree

Each paint chunk is associated with a [transform node](TransformPaintPropertyNode.h),
which defines the coordinate space in which the content should be painted.

Each transform node has:

1* a 4x4 [`TransformationMatrix`](../../transforms/TransformationMatrix.h)
2* a 3-dimensional transform origin, which defines the origin relative to which
  the transformation matrix should be applied (e.g. a rotation applied with some
  transform origin will rotate the plane about that point)
3* a pointer to the parent node, which defines the coordinate space relative to
  which the above should be interpreted
4* a boolean indicating whether the transform should be projected into the plane
  of its parent (i.e., whether the total transform inherited from its parent
  should be flattened before this node's transform is applied and propagated to
  children)
5* an integer rendering context ID; content whose transform nodes share a
  rendering context ID should sort together

The parent node pointers link the transform nodes in a hierarchy (the *transform
tree*), which defines how the transform for any painted content can be
determined.

其中1、2、4、5已统一用State结构体封装。父节点指针继承自PaintPropertyNode,通过parent节点连接成Tree

 Clip Tree

### Clips

Each paint chunk is associated with a [clip node](ClipPaintPropertyNode.h),
which defines the raster region that will be applied on the canvas when
the chunk is rastered.

Each clip node has:

* A float rect with (optionally) rounded corner radius.
* An associated transform node, which the clip rect is based on.

The raster region defined by a node is the rounded rect transformed to the
root space, intersects with the raster region defined by its parent clip node
(if not root).

Effect Tree

### Effects

Each paint chunk is associated with an [effect node](EffectPaintPropertyNode.h),
which defines the effect (opacity, transfer mode, filter, mask, etc.) that
should be applied to the content before or as it is composited into the content
below.

Each effect node has:

* a floating-point opacity (from 0 to 1, inclusive)
* a pointer to the parent node, which will be applied to the result of this
  effect before or as it is composited into its parent in the effect tree

The paret node pointers link the effect nodes in a hierarchy (the *effect
tree*), which defines the dependencies between rasterization of different
contents.

One can imagine each effect node as corresponding roughly to a bitmap that is
drawn before being composited into another bitmap, though for implementation
reasons this may not be how it is actually implemented.

Scroll Tree 

### Scrolling

Each paint chunk is associated with a [scroll node](ScrollPaintPropertyNode.h)
which defines information about how a subtree scrolls so threads other than the
main thread can perform scrolling. Scroll information includes:

* Which directions, if any, are scrollable by the user.
* A reference to a [transform node](TransformPaintPropertyNode.h) which contains
a 2d scroll offset.
* The extent that can be scrolled. For example, an overflow clip of size 7x9
with scrolling contents of size 7x13 can scroll 4px vertically and none
horizontally.

To ensure geometry operations are simple and only deal with transforms, the
scroll offset is stored as a 2d transform in the transform tree.

blink为什么需要输出property tree:抽象出property tree可以

how a subtree scrolls so threads other than the
main thread can perform scrolling

也许在一定程度上可以提高renderer进程的效率


blink property trees在Layout Tree进行prepaint时构造,(paint时则会产生Displayitem list和创建Graphics Layer Tree)。

具体调用栈为

Property Trees & DispalyItem_第2张图片

void PrePaintTreeWalk::Walk(const LayoutObject& object) {
  ...

  WalkInternal(object, context());

  for (const LayoutObject* child = object.SlowFirstChild(); child;
       child = child->NextSibling()) {
    if (child->IsLayoutMultiColumnSpannerPlaceholder()) {
      child->GetMutableForPainting().ClearPaintFlags();
      continue;
    }
    Walk(*child);
  }
  ...
}
void PrePaintTreeWalk::WalkInternal(const LayoutObject& object,
                                    PrePaintTreeWalkContext& context) {
  PaintInvalidatorContext& paint_invalidator_context =
      context.paint_invalidator_context;
  ...
  base::Optional property_tree_builder;
  bool property_changed = false;
  if (context.tree_builder_context) {
    property_tree_builder.emplace(object, *context.tree_builder_context);
    // create Transform, Clip and Effect Trees
    property_changed = property_tree_builder->UpdateForSelf();
    ...
  }

  ...
  InvalidatePaintForHitTesting(object, context);

  if (context.tree_builder_context) {
    // create Scroll Tree
    property_changed |= property_tree_builder->UpdateForChildren();
    InvalidatePaintLayerOptimizationsIfNeeded(object, context);
    ...
  }
  // set Property Trees's state to Graphics Layer
  CompositingLayerPropertyUpdater::Update(object);
  ...
  }
}

 通过先序遍历Layout Layer Tree创建四种tree,具体通过创建FragmentPaintPropertyTreeBuilder,用其创建每一个节点。FragmentPaintPropertyTreeBuilder保存了ObjectPaintProperties* properties_中,它指向LayoutObject的fragment_。

FragmentPaintPropertyTreeBuilder(
      const LayoutObject& object,
      PaintPropertyTreeBuilderContext& full_context,
      PaintPropertyTreeBuilderFragmentContext& context,
      FragmentData& fragment_data)
      : object_(object),
        full_context_(full_context),
        context_(context),
        fragment_data_(fragment_data),
        properties_(fragment_data.PaintProperties()) {}


fragment_data = &object_.GetMutableForPainting().FirstFragment();
Each `PaintLayer`'s `LayoutObject` has one or more `FragmentData` objects (see
below for more on fragments). Every `FragmentData` has an
`ObjectPaintProperties` object if any property nodes are induced by itDuring paint, each display item will be associated with a property
tree state.

其中,核心函数为

void FragmentPaintPropertyTreeBuilder::UpdateForSelf() {
  ...
  if (properties_) {
    UpdateTransform();
    UpdateClipPathClip(false);
    UpdateEffect();
    UpdateLinkHighlightEffect();
    UpdateClipPathClip(true);  // Special pass for SPv1 composited clip-path.
    UpdateCssClip();
    UpdateFilter();
    UpdateOverflowControlsClip();
  }
  UpdateLocalBorderBoxContext();
}

通过下面的宏(不用虚函数方式是为了提高效率吗)可以统一对所有Property Trees进行创建

#define ADD_NODE(type, function, variable)                                   \
  const type##PaintPropertyNode* function() const { return variable.get(); } \
  UpdateResult Update##function(const type##PaintPropertyNode& parent,       \
                                type##PaintPropertyNode::State&& state) {    \
    auto result = Update(variable, parent, std::move(state));                \
    DCHECK(!is_immutable_ || result.Unchanged())                             \
        << "Value changed while immutable. New state:\n"                     \
        << *variable;                                                        \
    return result;                                                           \
  }                      

对于Transform Tree 调用

 static scoped_refptr Create(
      const TransformPaintPropertyNode& parent,
      State&& state) {
    return base::AdoptRef(new TransformPaintPropertyNode(
        &parent, std::move(state), false /* is_parent_alias */));
  }

// Indicates whether this node is an alias for its parent. Parent aliases are
// nodes that do not affect rendering and are ignored for the purposes of
// display item list generation.

对Clip Tree 调用

static scoped_refptr Create(
      const ClipPaintPropertyNode& parent,
      State&& state) {
    return base::AdoptRef(new ClipPaintPropertyNode(
        &parent, std::move(state), false /* is_parent_alias */));
}

对Effect Tree 调用

  static scoped_refptr Create(
      const EffectPaintPropertyNode& parent,
      State&& state) {
    return base::AdoptRef(
        new EffectPaintPropertyNode(&parent, std::move(state)));
  }

对Scroll Tree 调用

  static scoped_refptr Create(
      const ScrollPaintPropertyNode& parent,
      State&& state) {
    return base::AdoptRef(
        new ScrollPaintPropertyNode(&parent, std::move(state)));
  }

到这其实我们基本得到了四棵Property Trees,但是对于树我们是非常关心它的根节点的,通过它才能访问和更新整个这个树。那么根节点在哪里设置的呢?

回到PrePaintTreeWalk::Walk

void PrePaintTreeWalk::Walk(LocalFrameView& frame_view) {
  ...
  // ancestor_overflow_paint_layer does not cross frame boundaries.
  context().ancestor_overflow_paint_layer = nullptr;
  if (context().tree_builder_context) {
    PaintPropertyTreeBuilder::SetupContextForFrame(
        frame_view, *context().tree_builder_context);
  }
  ...
    Walk(*view);
  ...
}

通过PaintPropertyTreeBuilder::SetupContextForFrame调用

PaintPropertyTreeBuilderFragmentContext::
    PaintPropertyTreeBuilderFragmentContext()
    : current_effect(&EffectPaintPropertyNode::Root()) {
  current.clip = absolute_position.clip = fixed_position.clip =
      &ClipPaintPropertyNode::Root();
  current.transform = absolute_position.transform = fixed_position.transform =
      &TransformPaintPropertyNode::Root();
  current.scroll = absolute_position.scroll = fixed_position.scroll =
      &ScrollPaintPropertyNode::Root();
}

也就是在这里为4个Property Tree设置了root,以后都可以通过XXXPaintPropertyNode::Root()的方式访问对应的root,

其创建的方式也比较fancy,root定义了一个静态变量,用lambda创建,以Transform Tree的root为例

const TransformPaintPropertyNode& TransformPaintPropertyNode::Root() {
  DEFINE_STATIC_REF(
      TransformPaintPropertyNode, root,
      base::AdoptRef(new TransformPaintPropertyNode(
          nullptr,
          State{TransformationMatrix(), FloatPoint3D(), false,
                BackfaceVisibility::kVisible, 0, CompositingReason::kNone,
                CompositorElementId(), &ScrollPaintPropertyNode::Root()},
          true /* is_parent_alias */)));
  return *root;
}


#define DEFINE_STATIC_REF(type, name, arguments)  \
  static type* name = [](scoped_refptr o) { \
    if (o)                                        \
      o->AddRef();                                \
    return o.get();                               \
  }(arguments);

而CC使用的Property Tree的结构和blink使用的结构不完全相同(不清楚为什么不把blink和cc的Displayitem格式统一,额外搞一步转换) ,通过PropertyTreeManager转换

void PaintArtifactCompositor::Update(
    scoped_refptr paint_artifact,
    CompositorElementIdSet& composited_element_ids,
    TransformPaintPropertyNode* viewport_scale_node) {
  ...
  for (auto& pending_layer : pending_layers) {
    ...
    //  convert blink property tree node into cc property tree node
    int transform_id =
        property_tree_manager.EnsureCompositorTransformNode(transform);
    int clip_id = property_tree_manager.EnsureCompositorClipNode(clip);
    int effect_id = property_tree_manager.SwitchToEffectNodeWithSynthesizedClip(
        *property_state.Effect(), *clip);
    // The compositor scroll node is not directly stored in the property tree
    // state but can be created via the scroll offset translation node.
    const auto& scroll_translation =
        ScrollTranslationForPendingLayer(*paint_artifact, pending_layer);
    int scroll_id =
        property_tree_manager.EnsureCompositorScrollNode(&scroll_translation);
    ...
    layer->set_property_tree_sequence_number(g_s_property_tree_sequence_number);
    layer->SetTransformTreeIndex(transform_id);
    layer->SetScrollTreeIndex(scroll_id);
    layer->SetClipTreeIndex(clip_id);
    layer->SetEffectTreeIndex(effect_id);
    ...
  }
  property_tree_manager.Finalize();
  ...
}

而cc的property tree存储在LayerTreeHost的成员PropertyTrees中,而LayerTreeHost就是用来管理CC Layer Tree的。


然后就进一步收集DisplayItemList,它是DisplayItem的集合。

什么是DisplayItem:

## Display items

A display item is the smallest unit of a display list in Blink. Each display
item is identified by an ID consisting of:

* an opaque pointer to the *display item client* that produced it
* a type (from the `DisplayItem::Type` enum)
class PLATFORM_EXPORT DisplayItem {
  DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();

 public:
  ...
  // Ids are for matching new DisplayItems with existing DisplayItems.
  struct Id {
    ...
    const DisplayItemClient& client;
    const Type type;
    const unsigned fragment;
  };
  Id GetId() const { return Id(*client_, GetType(), fragment_); }
  ...

  const DisplayItemClient* client_;
  FloatRect visual_rect_;
  float outset_for_raster_effects_;

  static_assert(kTypeLast < (1 << 8), "DisplayItem::Type should fit in 8 bits");
  unsigned type_ : 8;
  unsigned derived_size_ : 8;  // size of the actual derived class
  unsigned fragment_ : 14;
  unsigned is_cacheable_ : 1;
  unsigned is_tombstone_ : 1;
};

 DisplayItemList间接继承自ContiguousContainerBase,具有成员Vector elements_;

A Quick Overview of Chrome's Rendering Path

举个栗子,如下的网页


Hello World

将产生如下的DisplayItemList,包含3个DispalyItems:

Property Trees & DispalyItem_第3张图片

从client_包含了对LayoutObject的关联,可以看出其对应的内容,第一个对应与Layout的根节点,另一个对应于InlineTextBox,还有一个对应于LayoutImage。

从这里看,Displayitem的绘制指令,其实只包含LayoutObject的位置和大小信息,也就是还需要结合property trees。

Displayitem的创建过程如下

Property Trees & DispalyItem_第4张图片

其中不同的LayoutObject会有不同入口的函数,比如Graphicslayer对应于ViewPainter::PaintBoxDecorationBackground,inlineText对应于InlineTextBoxPainter::Paint,而LayoutImage对应于ImagePainter::PaintReplaced,等等。相同的是它们都会在栈上创建base::DrawingRecorder,在离开对应函数时,调用DrawingRecorder的析构函数,从而创建DisplayItem并Append到DisplayItemList上,而new_display_item_list_为PaintController的成员,是控制整个paint流程的。如下:

  template 
  void CreateAndAppend(Args&&... args) {
    ...

    if (DisplayItemConstructionIsDisabled())
      return;

    EnsureNewDisplayItemListInitialCapacity();
    DisplayItemClass& display_item =
        new_display_item_list_.AllocateAndConstruct(
            std::forward(args)...);
    display_item.SetFragment(current_fragment_);
    // will cache Displayitem and modify PaintChunk if needed
    ProcessNewItem(display_item);
  }

其中ProcessNewItem也非常重要,它会把PropertyTreeState相同的DisplayItem组合成以个PaintChunk,保存在PaintChunker中,而new_paint_chunks_也是PaintController的成员,如下:

void PaintController::ProcessNewItem(DisplayItem& display_item) {
  if (IsSkippingCache() && usage_ == kMultiplePaints)
    display_item.Client().Invalidate(PaintInvalidationReason::kUncacheable);

      new_paint_chunks_.IncrementDisplayItemIndex(display_item);
  auto& last_chunk = new_paint_chunks_.LastChunk();

  last_chunk.outset_for_raster_effects =
      std::max(last_chunk.outset_for_raster_effects,
               display_item.OutsetForRasterEffects());
  ...
  }
}

具体通过如下合并相同DisplayItem

bool PaintChunker::IncrementDisplayItemIndex(const DisplayItem& item) {
  bool item_forces_new_chunk = item.IsForeignLayer() || item.IsScrollHitTest();
  if (item_forces_new_chunk)
    force_new_chunk_ = true;

  size_t new_chunk_begin_index;
  if (chunks_.IsEmpty()) {
    new_chunk_begin_index = 0;
  } else {
    auto& last_chunk = LastChunk();
    if (!force_new_chunk_ && current_properties_ == last_chunk.properties) {
      // Continue the current chunk.
      last_chunk.end_index++;
      // We don't create a new chunk when UpdateCurrentPaintChunkProperties()
      // just changed |next_chunk_id_| but not |current_properties_|. Clear
      // |next_chunk_id_| which has been ignored.
      next_chunk_id_ = base::nullopt;
      return false;
    }
    new_chunk_begin_index = last_chunk.end_index;
  }

  chunks_.emplace_back(new_chunk_begin_index, new_chunk_begin_index + 1,
                       next_chunk_id_ ? *next_chunk_id_ : item.GetId(),
                       current_properties_);
  next_chunk_id_ = base::nullopt;

  // When forcing a new chunk, we still need to force new chunk for the next
  // display item. Otherwise reset force_new_chunk_ to false.
  if (!item_forces_new_chunk)
    force_new_chunk_ = false;

  return true;
}

可以看出,PaintChunk(仅仅是Displayitem的Id(*client_, GetType(), fragment_),而不是Displalyitem本身; )在PaintChunker中线性存贮,而这一步合并不是全局的,而是每次新产生的DisplayItem和之前的PaintChunk的PropertyTreeState形同则合并到统一PaintChunk,即增加该PaintChunk的end_index,否则新加一个PaintChunk。(这一步只是粗略的合并,之后还有进一步合并,还是说此处有进一步优化的可能呢?

而最终产生的DisplayItemList和PaintChunk会被打包成PaintArtifact,方便调用,当然它也是PaintControl的成员。

void PaintController::CommitNewDisplayItems() {
  ...
  // The new list will not be appended to again so we can release unused memory.
  new_display_item_list_.ShrinkToFit();

  current_paint_artifact_ =
      PaintArtifact::Create(std::move(new_display_item_list_),
                            new_paint_chunks_.ReleasePaintChunks());
  ...
}

Chromium之后会使用slimming paint v2,所以我们关注RuntimeEnabledFeatures::SlimmingPaintV2Enabled()的流程,打开此特性(可以使用的特性的名字在//src/third_party/blink/renderer/platform/runtime_enabled_features.json5的data中查询)。并在开启chrome时添加如下参数:

chrome --enable-blink-features=SlimmingPaintV2

而CC使用的displayitem的结构和blink使用的displayitem结构也不完全相同,所以会把PaintChunk转换成CC需要的格式, Property Trees & DispalyItem_第5张图片

scoped_refptr PaintChunksToCcLayer::Convert(
    const PaintChunkSubset& paint_chunks,
    ...,
    const DisplayItemList& display_items,
    ...) {
  auto cc_list = base::MakeRefCounted(hint);
  ConvertInto(paint_chunks, layer_state, layer_offset, FloatSize(),
              display_items, *cc_list);
  ...
  cc_list->Finalize();
  return cc_list;
}

参数中paint_chunks和displayitems正是取自于前面的PaintArtifact,而根据以上内容转换成cc所需要的cc::DisplayItemList,比较核心的成员如下

class CC_PAINT_EXPORT DisplayItemList
    : public base::RefCountedThreadSafe {
 public:
  ...
 private:
  // RTree stores indices into the paint op buffer.
  // TODO(vmpstr): Update the rtree to store offsets instead.
  RTree rtree_;
  DiscardableImageMap image_map_;
  PaintOpBuffer paint_op_buffer_;

  // The visual rects associated with each of the display items in the
  // display item list. These rects are intentionally kept separate because they
  // are used to decide which ops to walk for raster.
  std::vector visual_rects_;
  // Byte offsets associated with each of the ops.
  std::vector offsets_;
  ...
};

其中paint_op_buffer存储了具体的绘制指令,而红黑树rtree_存储了每个paintchunk的索引号,和对应在paint_op_buffer_中的offset,这样可以常数时间访问所需的displayitem。(visual_rects_还不太明白是干哈的


最终转换好的DisplayItemList存储在ContentLayerClientImpl中,其存储于PaintArtifactCompositor,其存储于LocalFrameView。

你可能感兴趣的:(chromium)