Commit 2c576851 authored by Koji Ishii's avatar Koji Ishii Committed by Commit Bot

[LayoutNG] Reuse line boxes when it is easy to resolve BFC offset

This patch allows reusing line boxes when it is easy to
resolve BFC offset. It was one of the most common criteria
that prevents reusing.

HandleInFlow resolves BFC for non-empty inline formatting
context. The reusability increases by moving the logic to
after that.

This patch improves line-layout-repeat-append by ~60%.

Bug: 636993
Change-Id: I90d63addd934f6966c76baa1e4dd312b68f6df4e
Reviewed-on: https://chromium-review.googlesource.com/c/1314076Reviewed-by: default avatarEmil A Eklund <eae@chromium.org>
Reviewed-by: default avatarMorten Stenshorne <mstensho@chromium.org>
Reviewed-by: default avatarIan Kilpatrick <ikilpatrick@chromium.org>
Commit-Queue: Koji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606360}
parent 6c2cc182
...@@ -512,11 +512,8 @@ scoped_refptr<NGLayoutResult> NGBlockLayoutAlgorithm::Layout() { ...@@ -512,11 +512,8 @@ scoped_refptr<NGLayoutResult> NGBlockLayoutAlgorithm::Layout() {
// Try to reuse line box fragments from cached fragments if possible. // Try to reuse line box fragments from cached fragments if possible.
// When possible, this adds fragments to |container_builder_| and update // When possible, this adds fragments to |container_builder_| and update
// |previous_inflow_position| and |BreakToken()|. // |previous_inflow_position| and |BreakToken()|.
NGLayoutInputNode first_child = Node().FirstChild();
TryReuseFragmentsFromCache(first_child, &previous_inflow_position);
scoped_refptr<const NGBreakToken> previous_inline_break_token; scoped_refptr<const NGBreakToken> previous_inline_break_token;
NGBlockChildIterator child_iterator(first_child, BreakToken()); NGBlockChildIterator child_iterator(Node().FirstChild(), BreakToken());
for (auto entry = child_iterator.NextChild(); for (auto entry = child_iterator.NextChild();
NGLayoutInputNode child = entry.node; NGLayoutInputNode child = entry.node;
entry = child_iterator.NextChild(previous_inline_break_token.get())) { entry = child_iterator.NextChild(previous_inline_break_token.get())) {
...@@ -537,7 +534,8 @@ scoped_refptr<NGLayoutResult> NGBlockLayoutAlgorithm::Layout() { ...@@ -537,7 +534,8 @@ scoped_refptr<NGLayoutResult> NGBlockLayoutAlgorithm::Layout() {
// all the way to the root of the fragmentation context without finding // all the way to the root of the fragmentation context without finding
// any such container, we have no valid class A break point, and if a // any such container, we have no valid class A break point, and if a
// forced break was requested, none will be inserted. // forced break was requested, none will be inserted.
container_builder_.SetInitialBreakBefore(child.Style().BreakBefore()); if (!child.IsInline())
container_builder_.SetInitialBreakBefore(child.Style().BreakBefore());
bool success = bool success =
child.CreatesNewFormattingContext() child.CreatesNewFormattingContext()
...@@ -716,64 +714,75 @@ scoped_refptr<NGLayoutResult> NGBlockLayoutAlgorithm::Layout() { ...@@ -716,64 +714,75 @@ scoped_refptr<NGLayoutResult> NGBlockLayoutAlgorithm::Layout() {
return container_builder_.ToBoxFragment(); return container_builder_.ToBoxFragment();
} }
bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( const NGPaintFragment* NGBlockLayoutAlgorithm::ReusableLineBoxContainer(
NGLayoutInputNode child, NGInlineNode inline_node) const {
NGPreviousInflowPosition* previous_inflow_position) {
// Block boxes are cached in LayoutBox and that partial reuse is not needed.
if (!child.IsInline())
return false;
NGInlineNode inline_node = ToNGInlineNode(child);
// If floats are intruding into this node, re-layout may be needed. // If floats are intruding into this node, re-layout may be needed.
if (!exclusion_space_.IsEmpty() || !unpositioned_floats_.IsEmpty()) if (!exclusion_space_.IsEmpty() || !unpositioned_floats_.IsEmpty())
return false; return nullptr;
// Cached fragments are not for intermediate layout. // Cached fragments are not for intermediate layout.
if (constraint_space_.IsIntermediateLayout()) if (constraint_space_.IsIntermediateLayout())
return false; return nullptr;
// Block fragmentation is not supported yet. // Block fragmentation is not supported yet.
if (constraint_space_.HasBlockFragmentation()) if (constraint_space_.HasBlockFragmentation())
return false; return nullptr;
// Laying out from a break token is not supported yet, because this logic // Laying out from a break token is not supported yet, because this logic
// synthesize a break token. // synthesize a break token.
if (BreakToken()) if (BreakToken())
return false; return nullptr;
// Resolving BFC requires additional logic and is not supported yet.
if (!container_builder_.BfcBlockOffset())
return false;
// Re-use from a NGPaintFragment, because currently dirty flags are on // Re-use from a NGPaintFragment, because currently dirty flags are on
// NGPaintFragment. // NGPaintFragment.
const NGPaintFragment* paint_fragment = inline_node.PaintFragment(); const NGPaintFragment* paint_fragment = inline_node.PaintFragment();
if (!paint_fragment) if (!paint_fragment)
return false; return nullptr;
if (!inline_node.PrepareReuseFragments(constraint_space_)) if (!inline_node.PrepareReuseFragments(constraint_space_))
return false; return nullptr;
return paint_fragment;
}
const NGBreakToken* NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache(
NGInlineNode inline_node,
NGPreviousInflowPosition* previous_inflow_position,
bool* aborted_out) {
const NGPaintFragment* lineboxes = ReusableLineBoxContainer(inline_node);
if (!lineboxes)
return nullptr;
// Following is a copy of logic from HandleInFlow(). They need to keep in
// sync.
if (inline_node.IsEmptyInline())
return nullptr;
if (!ResolveBfcBlockOffset(previous_inflow_position)) {
*aborted_out = true;
return nullptr;
}
DCHECK(container_builder_.BfcBlockOffset());
WritingMode writing_mode = container_builder_.GetWritingMode(); WritingMode writing_mode = container_builder_.GetWritingMode();
TextDirection direction = container_builder_.Direction(); TextDirection direction = container_builder_.Direction();
DCHECK_EQ(writing_mode, paint_fragment->Style().GetWritingMode()); DCHECK_EQ(writing_mode, lineboxes->Style().GetWritingMode());
DCHECK_EQ(direction, paint_fragment->Style().Direction()); DCHECK_EQ(direction, lineboxes->Style().Direction());
const NGPhysicalSize outer_size = paint_fragment->Size(); const NGPhysicalSize outer_size = lineboxes->Size();
struct FragmentWithLogicalOffset { struct FragmentWithLogicalOffset {
const NGPhysicalFragment& fragment; const NGPhysicalFragment& fragment;
NGLogicalOffset offset; NGLogicalOffset offset;
}; };
Vector<FragmentWithLogicalOffset, 64> fragments; Vector<FragmentWithLogicalOffset, 64> fragments;
fragments.ReserveInitialCapacity(paint_fragment->Children().size()); fragments.ReserveInitialCapacity(lineboxes->Children().size());
for (const NGPaintFragment* child : paint_fragment->Children()) { for (const NGPaintFragment* child : lineboxes->Children()) {
if (child->IsDirty()) if (child->IsDirty())
break; break;
// Abort if there are floats, oof, or list marker. They need re-layout. // Abort if there are floats, oof, or list marker. They need re-layout.
const NGPhysicalFragment& child_fragment = child->PhysicalFragment(); const NGPhysicalFragment& child_fragment = child->PhysicalFragment();
if (!child_fragment.IsLineBox()) if (!child_fragment.IsLineBox())
return false; return nullptr;
NGLogicalOffset logical_offset = child->Offset().ConvertToLogical( NGLogicalOffset logical_offset = child->Offset().ConvertToLogical(
writing_mode, direction, outer_size, child_fragment.Size()); writing_mode, direction, outer_size, child_fragment.Size());
...@@ -781,7 +790,7 @@ bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( ...@@ -781,7 +790,7 @@ bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache(
FragmentWithLogicalOffset{child_fragment, logical_offset}); FragmentWithLogicalOffset{child_fragment, logical_offset});
} }
if (fragments.IsEmpty()) if (fragments.IsEmpty())
return false; return nullptr;
// TODO(kojii): Running the normal layout code at least once for this child // TODO(kojii): Running the normal layout code at least once for this child
// helps reducing the code to setup internal states after the reuse. Remove // helps reducing the code to setup internal states after the reuse. Remove
...@@ -791,7 +800,7 @@ bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( ...@@ -791,7 +800,7 @@ bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache(
if (fragments.back().fragment.BreakToken()->IsFinished()) { if (fragments.back().fragment.BreakToken()->IsFinished()) {
fragments.Shrink(fragments.size() - 1); fragments.Shrink(fragments.size() - 1);
if (fragments.IsEmpty()) if (fragments.IsEmpty())
return false; return nullptr;
} }
for (const auto& fragment : fragments) { for (const auto& fragment : fragments) {
...@@ -805,17 +814,12 @@ bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache( ...@@ -805,17 +814,12 @@ bool NGBlockLayoutAlgorithm::TryReuseFragmentsFromCache(
last_fragment.fragment.Size().ConvertToLogical(writing_mode).block_size; last_fragment.fragment.Size().ConvertToLogical(writing_mode).block_size;
previous_inflow_position->logical_block_offset = used_block_size; previous_inflow_position->logical_block_offset = used_block_size;
// Setup a break token so that this algorithm can start laying out the rest. // In order to layout the rest of lines, return the break token from the last
// Only creating one new break token is supported for now. // reused line box.
DCHECK(!break_token_);
NGBreakToken* last_break_token = last_fragment.fragment.BreakToken(); NGBreakToken* last_break_token = last_fragment.fragment.BreakToken();
DCHECK(last_break_token); DCHECK(last_break_token);
DCHECK(!last_break_token->IsFinished()); DCHECK(!last_break_token->IsFinished());
NGBreakTokenVector child_break_tokens; return last_break_token;
child_break_tokens.push_back(last_break_token);
break_token_ =
NGBlockBreakToken::Create(Node(), used_block_size, child_break_tokens);
return true;
} }
void NGBlockLayoutAlgorithm::HandleOutOfFlowPositioned( void NGBlockLayoutAlgorithm::HandleOutOfFlowPositioned(
...@@ -1201,6 +1205,17 @@ bool NGBlockLayoutAlgorithm::HandleInflow( ...@@ -1201,6 +1205,17 @@ bool NGBlockLayoutAlgorithm::HandleInflow(
DCHECK(!child.IsOutOfFlowPositioned()); DCHECK(!child.IsOutOfFlowPositioned());
DCHECK(!child.CreatesNewFormattingContext()); DCHECK(!child.CreatesNewFormattingContext());
if (child.IsInline() && !child_break_token) {
DCHECK(!*previous_inline_break_token);
bool aborted = false;
*previous_inline_break_token = TryReuseFragmentsFromCache(
ToNGInlineNode(child), previous_inflow_position, &aborted);
if (*previous_inline_break_token)
return true;
if (aborted)
return false;
}
bool is_non_empty_inline = bool is_non_empty_inline =
child.IsInline() && !ToNGInlineNode(child).IsEmptyInline(); child.IsInline() && !ToNGInlineNode(child).IsEmptyInline();
bool has_clearance_past_adjoining_floats = bool has_clearance_past_adjoining_floats =
...@@ -1213,6 +1228,9 @@ bool NGBlockLayoutAlgorithm::HandleInflow( ...@@ -1213,6 +1228,9 @@ bool NGBlockLayoutAlgorithm::HandleInflow(
// pending floats. There are two situations where this is necessary: // pending floats. There are two situations where this is necessary:
// 1. If the child is to be cleared by adjoining floats. // 1. If the child is to be cleared by adjoining floats.
// 2. If the child is a non-empty inline. // 2. If the child is a non-empty inline.
//
// Note this logic is copied to TryReuseFragmentsFromCache(), they need to
// keep in sync.
if (has_clearance_past_adjoining_floats || is_non_empty_inline) { if (has_clearance_past_adjoining_floats || is_non_empty_inline) {
if (!ResolveBfcBlockOffset(previous_inflow_position)) if (!ResolveBfcBlockOffset(previous_inflow_position))
return false; return false;
......
...@@ -129,12 +129,17 @@ class CORE_EXPORT NGBlockLayoutAlgorithm ...@@ -129,12 +129,17 @@ class CORE_EXPORT NGBlockLayoutAlgorithm
const NGInflowChildData& child_data, const NGInflowChildData& child_data,
const NGLayoutResult&) const; const NGLayoutResult&) const;
// Find the container of reusable line boxes. Returns nullptr if there are no
// reusable line boxes.
const NGPaintFragment* ReusableLineBoxContainer(NGInlineNode child) const;
// Try to reuse part of cached fragments. When reusing is possible, this // Try to reuse part of cached fragments. When reusing is possible, this
// function adds part of cached fragments to |container_builder_|, update // function adds part of cached fragments to |container_builder_|, update
// |break_token_| to continue layout from the last reused fragment, and // |break_token_| to continue layout from the last reused fragment, and
// returns |true|. Otherwise returns |false|. // returns |true|. Otherwise returns |false|.
bool TryReuseFragmentsFromCache(NGLayoutInputNode child, const NGBreakToken* TryReuseFragmentsFromCache(NGInlineNode child,
NGPreviousInflowPosition*); NGPreviousInflowPosition*,
bool* abort_out);
void HandleOutOfFlowPositioned(const NGPreviousInflowPosition&, NGBlockNode); void HandleOutOfFlowPositioned(const NGPreviousInflowPosition&, NGBlockNode);
void HandleFloat(const NGPreviousInflowPosition&, void HandleFloat(const NGPreviousInflowPosition&,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment