From 765ff8463e5e94b8d4987cb4fc81b096ad68933f Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 9 Sep 2015 11:05:20 +0100 Subject: [PATCH] Add fast path for simple stack layouts Change the initial line loop to opportunistically position children in the in container with simple stacking params i.e. vertical/horizontal stacking on non-flexible STRETCH/FLEX_START aligned. This allows us to skip the main and cross axis loops (Loop C and D, respectively) partially and even completely in many common scenarios. In my benchamrks, this gives us about ~15% performance win in many setups. --- src/Layout.c | 75 +++++++++++++++++-- src/Layout.js | 75 +++++++++++++++++-- .../com/facebook/csslayout/LayoutEngine.java | 75 +++++++++++++++++-- 3 files changed, 201 insertions(+), 24 deletions(-) diff --git a/src/Layout.c b/src/Layout.c index 8169146119f..bab98467f96 100644 --- a/src/Layout.c +++ b/src/Layout.c @@ -590,6 +590,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction bool isNodeFlexWrap = isFlexWrap(node); + css_justify_t justifyContent = node->style.justify_content; + float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); @@ -636,19 +638,41 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction float totalFlexible = 0; int nonFlexibleChildrenCount = 0; + // Use the line loop to position children in the main axis for as long + // as they are using a simple stacking behaviour. Children that are + // immediately stacked in the initial loop will not be touched again + // in . + bool isSimpleStackMain = + (isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) || + (!isMainDimDefined && justifyContent != CSS_JUSTIFY_CENTER); + int firstComplexMain = (isSimpleStackMain ? childCount : startLine); + + // Use the initial line loop to position children in the cross axis for + // as long as they are relatively positioned with alignment STRETCH or + // FLEX_START. Children that are immediately stacked in the initial loop + // will not be touched again in . + bool isSimpleStackCross = true; + int firstComplexCross = childCount; + css_node_t* firstFlexChild = NULL; css_node_t* currentFlexChild = NULL; + float mainDim = leadingPaddingAndBorderMain; + float crossDim = 0; + float maxWidth; for (i = startLine; i < childCount; ++i) { child = node->get_child(node->context, i); + child->line_index = linesCount; child->next_absolute_child = NULL; child->next_flex_child = NULL; + css_align_t alignItem = getAlignItem(node, child); + // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass - if (getAlignItem(node, child) == CSS_ALIGN_STRETCH && + if (alignItem == CSS_ALIGN_STRETCH && child->style.position_type == CSS_POSITION_RELATIVE && isCrossDimDefined && !isDimDefined(child, crossAxis)) { @@ -753,6 +777,44 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction alreadyComputedNextLayout = 1; break; } + + // Disable simple stacking in the main axis for the current line as + // we found a non-trivial child-> The remaining children will be laid out + // in . + if (isSimpleStackMain && + (child->style.position_type != CSS_POSITION_RELATIVE || isFlex(child))) { + isSimpleStackMain = false; + firstComplexMain = i; + } + + // Disable simple stacking in the cross axis for the current line as + // we found a non-trivial child-> The remaining children will be laid out + // in . + if (isSimpleStackCross && + (child->style.position_type != CSS_POSITION_RELATIVE || + (alignItem != CSS_ALIGN_STRETCH && alignItem != CSS_ALIGN_FLEX_START) || + isUndefined(child->layout.dimensions[dim[crossAxis]]))) { + isSimpleStackCross = false; + firstComplexCross = i; + } + + if (isSimpleStackMain) { + child->layout.position[pos[mainAxis]] += mainDim; + if (isMainDimDefined) { + setTrailingPosition(node, child, mainAxis); + } + + mainDim += getDimWithMargin(child, mainAxis); + crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis))); + } + + if (isSimpleStackCross) { + child->layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross; + if (isCrossDimDefined) { + setTrailingPosition(node, child, crossAxis); + } + } + alreadyComputedNextLayout = 0; mainContentDim += nextContentDim; endLine = i + 1; @@ -833,8 +895,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction // We use justifyContent to figure out how to allocate the remaining // space available - } else if (node->style.justify_content != CSS_JUSTIFY_FLEX_START) { - css_justify_t justifyContent = node->style.justify_content; + } else if (justifyContent != CSS_JUSTIFY_FLEX_START) { if (justifyContent == CSS_JUSTIFY_CENTER) { leadingMainDim = remainingMainDim / 2; } else if (justifyContent == CSS_JUSTIFY_FLEX_END) { @@ -861,12 +922,10 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction // find their position. In order to do that, we accumulate data in // variables that are also useful to compute the total dimensions of the // container! - float crossDim = 0; - float mainDim = leadingMainDim + leadingPaddingAndBorderMain; + mainDim += leadingMainDim; - for (i = startLine; i < endLine; ++i) { + for (i = firstComplexMain; i < endLine; ++i) { child = node->get_child(node->context, i); - child->line_index = linesCount; if (child->style.position_type == CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { @@ -912,7 +971,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction } // Position elements in the cross axis - for (i = startLine; i < endLine; ++i) { + for (i = firstComplexCross; i < endLine; ++i) { child = node->get_child(node->context, i); if (child->style.position_type == CSS_POSITION_ABSOLUTE && diff --git a/src/Layout.js b/src/Layout.js index 5295c84073a..e97258e987b 100755 --- a/src/Layout.js +++ b/src/Layout.js @@ -458,6 +458,8 @@ var computeLayout = (function() { var/*bool*/ isNodeFlexWrap = isFlexWrap(node); + var/*css_justify_t*/ justifyContent = getJustifyContent(node); + var/*float*/ leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); var/*float*/ leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); var/*float*/ paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); @@ -504,19 +506,41 @@ var computeLayout = (function() { var/*float*/ totalFlexible = 0; var/*int*/ nonFlexibleChildrenCount = 0; + // Use the line loop to position children in the main axis for as long + // as they are using a simple stacking behaviour. Children that are + // immediately stacked in the initial loop will not be touched again + // in . + var/*bool*/ isSimpleStackMain = + (isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) || + (!isMainDimDefined && justifyContent != CSS_JUSTIFY_CENTER); + var/*int*/ firstComplexMain = (isSimpleStackMain ? childCount : startLine); + + // Use the initial line loop to position children in the cross axis for + // as long as they are relatively positioned with alignment STRETCH or + // FLEX_START. Children that are immediately stacked in the initial loop + // will not be touched again in . + var/*bool*/ isSimpleStackCross = true; + var/*int*/ firstComplexCross = childCount; + var/*css_node_t**/ firstFlexChild = null; var/*css_node_t**/ currentFlexChild = null; + var/*float*/ mainDim = leadingPaddingAndBorderMain; + var/*float*/ crossDim = 0; + var/*float*/ maxWidth; for (i = startLine; i < childCount; ++i) { child = node.children[i]; + child.lineIndex = linesCount; child.nextAbsoluteChild = null; child.nextFlexChild = null; + var/*css_align_t*/ alignItem = getAlignItem(node, child); + // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass - if (getAlignItem(node, child) === CSS_ALIGN_STRETCH && + if (alignItem === CSS_ALIGN_STRETCH && getPositionType(child) === CSS_POSITION_RELATIVE && isCrossDimDefined && !isDimDefined(child, crossAxis)) { @@ -621,6 +645,44 @@ var computeLayout = (function() { alreadyComputedNextLayout = 1; break; } + + // Disable simple stacking in the main axis for the current line as + // we found a non-trivial child. The remaining children will be laid out + // in . + if (isSimpleStackMain && + (getPositionType(child) != CSS_POSITION_RELATIVE || isFlex(child))) { + isSimpleStackMain = false; + firstComplexMain = i; + } + + // Disable simple stacking in the cross axis for the current line as + // we found a non-trivial child. The remaining children will be laid out + // in . + if (isSimpleStackCross && + (getPositionType(child) != CSS_POSITION_RELATIVE || + (alignItem !== CSS_ALIGN_STRETCH && alignItem != CSS_ALIGN_FLEX_START) || + isUndefined(child.layout[dim[crossAxis]]))) { + isSimpleStackCross = false; + firstComplexCross = i; + } + + if (isSimpleStackMain) { + child.layout[pos[mainAxis]] += mainDim; + if (isMainDimDefined) { + setTrailingPosition(node, child, mainAxis); + } + + mainDim += getDimWithMargin(child, mainAxis); + crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis))); + } + + if (isSimpleStackCross) { + child.layout[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross; + if (isCrossDimDefined) { + setTrailingPosition(node, child, crossAxis); + } + } + alreadyComputedNextLayout = 0; mainContentDim += nextContentDim; endLine = i + 1; @@ -701,8 +763,7 @@ var computeLayout = (function() { // We use justifyContent to figure out how to allocate the remaining // space available - } else if (getJustifyContent(node) !== CSS_JUSTIFY_FLEX_START) { - var/*css_justify_t*/ justifyContent = getJustifyContent(node); + } else if (justifyContent !== CSS_JUSTIFY_FLEX_START) { if (justifyContent === CSS_JUSTIFY_CENTER) { leadingMainDim = remainingMainDim / 2; } else if (justifyContent === CSS_JUSTIFY_FLEX_END) { @@ -729,12 +790,10 @@ var computeLayout = (function() { // find their position. In order to do that, we accumulate data in // variables that are also useful to compute the total dimensions of the // container! - var/*float*/ crossDim = 0; - var/*float*/ mainDim = leadingMainDim + leadingPaddingAndBorderMain; + mainDim += leadingMainDim; - for (i = startLine; i < endLine; ++i) { + for (i = firstComplexMain; i < endLine; ++i) { child = node.children[i]; - child.lineIndex = linesCount; if (getPositionType(child) === CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { @@ -780,7 +839,7 @@ var computeLayout = (function() { } // Position elements in the cross axis - for (i = startLine; i < endLine; ++i) { + for (i = firstComplexCross; i < endLine; ++i) { child = node.children[i]; if (getPositionType(child) === CSS_POSITION_ABSOLUTE && diff --git a/src/java/src/com/facebook/csslayout/LayoutEngine.java b/src/java/src/com/facebook/csslayout/LayoutEngine.java index 68dc7fdb83b..2ed0eb85ce5 100644 --- a/src/java/src/com/facebook/csslayout/LayoutEngine.java +++ b/src/java/src/com/facebook/csslayout/LayoutEngine.java @@ -407,6 +407,8 @@ private static void layoutNodeImpl( boolean isNodeFlexWrap = isFlexWrap(node); + CSSJustify justifyContent = node.style.justifyContent; + float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); @@ -453,19 +455,41 @@ private static void layoutNodeImpl( float totalFlexible = 0; int nonFlexibleChildrenCount = 0; + // Use the line loop to position children in the main axis for as long + // as they are using a simple stacking behaviour. Children that are + // immediately stacked in the initial loop will not be touched again + // in . + boolean isSimpleStackMain = + (isMainDimDefined && justifyContent == CSSJustify.FLEX_START) || + (!isMainDimDefined && justifyContent != CSSJustify.CENTER); + int firstComplexMain = (isSimpleStackMain ? childCount : startLine); + + // Use the initial line loop to position children in the cross axis for + // as long as they are relatively positioned with alignment STRETCH or + // FLEX_START. Children that are immediately stacked in the initial loop + // will not be touched again in . + boolean isSimpleStackCross = true; + int firstComplexCross = childCount; + CSSNode firstFlexChild = null; CSSNode currentFlexChild = null; + float mainDim = leadingPaddingAndBorderMain; + float crossDim = 0; + float maxWidth; for (i = startLine; i < childCount; ++i) { child = node.getChildAt(i); + child.lineIndex = linesCount; child.nextAbsoluteChild = null; child.nextFlexChild = null; + CSSAlign alignItem = getAlignItem(node, child); + // Pre-fill cross axis dimensions when the child is using stretch before // we call the recursive layout pass - if (getAlignItem(node, child) == CSSAlign.STRETCH && + if (alignItem == CSSAlign.STRETCH && child.style.positionType == CSSPositionType.RELATIVE && isCrossDimDefined && !isDimDefined(child, crossAxis)) { @@ -570,6 +594,44 @@ private static void layoutNodeImpl( alreadyComputedNextLayout = 1; break; } + + // Disable simple stacking in the main axis for the current line as + // we found a non-trivial child. The remaining children will be laid out + // in . + if (isSimpleStackMain && + (child.style.positionType != CSSPositionType.RELATIVE || isFlex(child))) { + isSimpleStackMain = false; + firstComplexMain = i; + } + + // Disable simple stacking in the cross axis for the current line as + // we found a non-trivial child. The remaining children will be laid out + // in . + if (isSimpleStackCross && + (child.style.positionType != CSSPositionType.RELATIVE || + (alignItem != CSSAlign.STRETCH && alignItem != CSSAlign.FLEX_START) || + isUndefined(child.layout.dimensions[dim[crossAxis]]))) { + isSimpleStackCross = false; + firstComplexCross = i; + } + + if (isSimpleStackMain) { + child.layout.position[pos[mainAxis]] += mainDim; + if (isMainDimDefined) { + setTrailingPosition(node, child, mainAxis); + } + + mainDim += getDimWithMargin(child, mainAxis); + crossDim = Math.max(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis))); + } + + if (isSimpleStackCross) { + child.layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross; + if (isCrossDimDefined) { + setTrailingPosition(node, child, crossAxis); + } + } + alreadyComputedNextLayout = 0; mainContentDim += nextContentDim; endLine = i + 1; @@ -650,8 +712,7 @@ private static void layoutNodeImpl( // We use justifyContent to figure out how to allocate the remaining // space available - } else if (node.style.justifyContent != CSSJustify.FLEX_START) { - CSSJustify justifyContent = node.style.justifyContent; + } else if (justifyContent != CSSJustify.FLEX_START) { if (justifyContent == CSSJustify.CENTER) { leadingMainDim = remainingMainDim / 2; } else if (justifyContent == CSSJustify.FLEX_END) { @@ -678,12 +739,10 @@ private static void layoutNodeImpl( // find their position. In order to do that, we accumulate data in // variables that are also useful to compute the total dimensions of the // container! - float crossDim = 0; - float mainDim = leadingMainDim + leadingPaddingAndBorderMain; + mainDim += leadingMainDim; - for (i = startLine; i < endLine; ++i) { + for (i = firstComplexMain; i < endLine; ++i) { child = node.getChildAt(i); - child.lineIndex = linesCount; if (child.style.positionType == CSSPositionType.ABSOLUTE && isPosDefined(child, leading[mainAxis])) { @@ -729,7 +788,7 @@ private static void layoutNodeImpl( } // Position elements in the cross axis - for (i = startLine; i < endLine; ++i) { + for (i = firstComplexCross; i < endLine; ++i) { child = node.getChildAt(i); if (child.style.positionType == CSSPositionType.ABSOLUTE &&