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. 

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)。

具體調用棧爲

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<PaintPropertyTreeBuilder> 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<TransformPaintPropertyNode> 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<ClipPaintPropertyNode> Create(
      const ClipPaintPropertyNode& parent,
      State&& state) {
    return base::AdoptRef(new ClipPaintPropertyNode(
        &parent, std::move(state), false /* is_parent_alias */));
}

對Effect Tree 調用

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

對Scroll Tree 調用

  static scoped_refptr<ScrollPaintPropertyNode> 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<type> o) { \
    if (o)                                        \
      o->AddRef();                                \
    return o.get();                               \
  }(arguments);

而CC使用的Property Tree的結構和blink使用的結構不完全相同(不清楚爲什麼不把blink和cc的Displayitem格式統一,額外搞一步轉換) ,通過PropertyTreeManager轉換

void PaintArtifactCompositor::Update(
    scoped_refptr<const PaintArtifact> 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<void*> elements_;

A Quick Overview of Chrome's Rendering Path

舉個栗子,如下的網頁

<html>
Hello World
<img src="chrome_big.jpg" />
</html>

將產生如下的DisplayItemList,包含3個DispalyItems:

從client_包含了對LayoutObject的關聯,可以看出其對應的內容,第一個對應與Layout的根節點,另一個對應於InlineTextBox,還有一個對應於LayoutImage。

從這裏看,Displayitem的繪製指令,其實只包含LayoutObject的位置和大小信息,也就是還需要結合property trees。

Displayitem的創建過程如下

其中不同的LayoutObject會有不同入口的函數,比如Graphicslayer對應於ViewPainter::PaintBoxDecorationBackground,inlineText對應於InlineTextBoxPainter::Paint,而LayoutImage對應於ImagePainter::PaintReplaced,等等。相同的是它們都會在棧上創建base::DrawingRecorder,在離開對應函數時,調用DrawingRecorder的析構函數,從而創建DisplayItem並Append到DisplayItemList上,而new_display_item_list_爲PaintController的成員,是控制整個paint流程的。如下:

  template <typename DisplayItemClass, typename... Args>
  void CreateAndAppend(Args&&... args) {
    ...

    if (DisplayItemConstructionIsDisabled())
      return;

    EnsureNewDisplayItemListInitialCapacity();
    DisplayItemClass& display_item =
        new_display_item_list_.AllocateAndConstruct<DisplayItemClass>(
            std::forward<Args>(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需要的格式, 

scoped_refptr<cc::DisplayItemList> PaintChunksToCcLayer::Convert(
    const PaintChunkSubset& paint_chunks,
    ...,
    const DisplayItemList& display_items,
    ...) {
  auto cc_list = base::MakeRefCounted<cc::DisplayItemList>(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<DisplayItemList> {
 public:
  ...
 private:
  // RTree stores indices into the paint op buffer.
  // TODO(vmpstr): Update the rtree to store offsets instead.
  RTree<size_t> 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<gfx::Rect> visual_rects_;
  // Byte offsets associated with each of the ops.
  std::vector<size_t> offsets_;
  ...
};

其中paint_op_buffer存儲了具體的繪製指令,而紅黑樹rtree_存儲了每個paintchunk的索引號,和對應在paint_op_buffer_中的offset,這樣可以常數時間訪問所需的displayitem。(visual_rects_還不太明白是幹哈的


最終轉換好的DisplayItemList存儲在ContentLayerClientImpl中,其存儲於PaintArtifactCompositor,其存儲於LocalFrameView。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章