RenderLayer subtrees without any self-painting layer shouldn't be walked during painting
authorjchaffraix@webkit.org <jchaffraix@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Jun 2012 04:17:39 +0000 (04:17 +0000)
committerjchaffraix@webkit.org <jchaffraix@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 15 Jun 2012 04:17:39 +0000 (04:17 +0000)
https://bugs.webkit.org/show_bug.cgi?id=88888

Reviewed by Simon Fraser.

Performance optimization, covered by existing tests.

The gist of this change is to add a has-self-painting-layer-descendant flag (including an
invalidation logic) that is used to avoid walking subtrees without any self-painting layer.

On http://dglazkov.github.com/performance-tests/biggrid.html with a 100,000 rows
by 100 columns table, it brings the paint time during scrolling from ~45ms to ~6ms
on my machine. The test case is a pathologic example here but the optimization should
apply in other cases.

The new update logic piggy-backs on top of the existing updateVisibilityStatus() one that
got repurposed and renamed as part of this change.

* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::RenderLayer):
(WebCore::RenderLayer::addChild):
(WebCore::RenderLayer::removeChild):
(WebCore::RenderLayer::styleChanged):
These functions were updated to dirty / set the new flag.

(WebCore::RenderLayer::dirtyAncestorChainHasSelfPaintingLayerDescendantStatus):
(WebCore::RenderLayer::setAncestorChainHasSelfPaintingLayerDescendant):
Added those functions to handle setting / invalidating the new flag.

(WebCore::RenderLayer::updateSelfPaintingLayerAfterStyleChange):
Added this function to handle style update.

(WebCore::RenderLayer::paintLayer):
(WebCore::RenderLayer::paintLayerContentsAndReflection):
(WebCore::RenderLayer::paintLayerContents):
(WebCore::RenderLayer::paintList):
Changed this logic to bail out if we have no self-painting descendants. This is what
is giving the performance improvement. Also added some performance ASSERTs to ensure
the methods are not called when they shouldn't.

(WebCore::RenderLayer::updateDescendantDependentFlags):
Renamed from updateVisibilityStatus to account for the new usage.

(WebCore::RenderLayer::updateLayerPositions):
(WebCore::RenderLayer::updateLayerPositionsAfterScroll):
(WebCore::RenderLayer::collectLayers):
* rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateGraphicsLayerGeometry):
Updated after updateVisibilityStatus rename.

* rendering/RenderLayer.h:
(WebCore::RenderLayer::hasSelfPaintingLayerDescendant):
Added the declaration of the new functions as well as the new flag and dirty bit.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@120395 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/rendering/RenderLayer.cpp
Source/WebCore/rendering/RenderLayer.h
Source/WebCore/rendering/RenderLayerBacking.cpp

index 5780ddd..a83b692 100644 (file)
@@ -1,3 +1,59 @@
+2012-06-14  Julien Chaffraix  <jchaffraix@webkit.org>
+
+        RenderLayer subtrees without any self-painting layer shouldn't be walked during painting
+        https://bugs.webkit.org/show_bug.cgi?id=88888
+
+        Reviewed by Simon Fraser.
+
+        Performance optimization, covered by existing tests.
+
+        The gist of this change is to add a has-self-painting-layer-descendant flag (including an
+        invalidation logic) that is used to avoid walking subtrees without any self-painting layer.
+
+        On http://dglazkov.github.com/performance-tests/biggrid.html with a 100,000 rows
+        by 100 columns table, it brings the paint time during scrolling from ~45ms to ~6ms
+        on my machine. The test case is a pathologic example here but the optimization should
+        apply in other cases.
+
+        The new update logic piggy-backs on top of the existing updateVisibilityStatus() one that
+        got repurposed and renamed as part of this change.
+
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::RenderLayer):
+        (WebCore::RenderLayer::addChild):
+        (WebCore::RenderLayer::removeChild):
+        (WebCore::RenderLayer::styleChanged):
+        These functions were updated to dirty / set the new flag.
+
+        (WebCore::RenderLayer::dirtyAncestorChainHasSelfPaintingLayerDescendantStatus):
+        (WebCore::RenderLayer::setAncestorChainHasSelfPaintingLayerDescendant):
+        Added those functions to handle setting / invalidating the new flag.
+
+        (WebCore::RenderLayer::updateSelfPaintingLayerAfterStyleChange):
+        Added this function to handle style update.
+
+        (WebCore::RenderLayer::paintLayer):
+        (WebCore::RenderLayer::paintLayerContentsAndReflection):
+        (WebCore::RenderLayer::paintLayerContents):
+        (WebCore::RenderLayer::paintList):
+        Changed this logic to bail out if we have no self-painting descendants. This is what
+        is giving the performance improvement. Also added some performance ASSERTs to ensure
+        the methods are not called when they shouldn't.
+
+        (WebCore::RenderLayer::updateDescendantDependentFlags):
+        Renamed from updateVisibilityStatus to account for the new usage.
+
+        (WebCore::RenderLayer::updateLayerPositions):
+        (WebCore::RenderLayer::updateLayerPositionsAfterScroll):
+        (WebCore::RenderLayer::collectLayers):
+        * rendering/RenderLayerBacking.cpp:
+        (WebCore::RenderLayerBacking::updateGraphicsLayerGeometry):
+        Updated after updateVisibilityStatus rename.
+
+        * rendering/RenderLayer.h:
+        (WebCore::RenderLayer::hasSelfPaintingLayerDescendant):
+        Added the declaration of the new functions as well as the new flag and dirty bit.
+
 2012-06-14  Tony Payne  <tpayne@chromium.org>
 
        [chromium] Add iccjpeg and qcms to chromium port
index 27c7d5c..ec3632c 100644 (file)
@@ -129,6 +129,8 @@ RenderLayer::RenderLayer(RenderBoxModelObject* renderer)
     : m_inResizeMode(false)
     , m_scrollDimensionsDirty(true)
     , m_normalFlowListDirty(true)
+    , m_hasSelfPaintingLayerDescendant(false)
+    , m_hasSelfPaintingLayerDescendantDirty(false)
     , m_isRootLayer(renderer->isRenderView())
     , m_usedTransparency(false)
     , m_paintingInsideReflection(false)
@@ -363,7 +365,7 @@ void RenderLayer::updateLayerPositions(LayoutPoint* offsetFromRoot, UpdateLayerP
     }
     positionOverflowControls(toSize(roundedIntPoint(offset)));
 
-    updateVisibilityStatus();
+    updateDescendantDependentFlags();
 
     if (flags & UpdatePagination)
         updatePagination();
@@ -442,6 +444,30 @@ LayoutRect RenderLayer::repaintRectIncludingNonCompositingDescendants() const
     return repaintRect;
 }
 
+void RenderLayer::setAncestorChainHasSelfPaintingLayerDescendant()
+{
+    for (RenderLayer* layer = this; layer; layer = layer->parent()) {
+        if (!layer->m_hasSelfPaintingLayerDescendantDirty && layer->hasSelfPaintingLayerDescendant())
+            break;
+
+        layer->m_hasSelfPaintingLayerDescendantDirty = false;
+        layer->m_hasSelfPaintingLayerDescendant = true;
+    }
+}
+
+void RenderLayer::dirtyAncestorChainHasSelfPaintingLayerDescendantStatus()
+{
+    for (RenderLayer* layer = this; layer; layer = layer->parent()) {
+        layer->m_hasSelfPaintingLayerDescendantDirty = true;
+        // If we have reached a self-painting layer, we know our parent should have a self-painting descendant
+        // in this case, there is no need to dirty our ancestors further.
+        if (layer->isSelfPaintingLayer()) {
+            ASSERT(!parent() || parent()->m_hasSelfPaintingLayerDescendantDirty || parent()->hasSelfPaintingLayerDescendant());
+            break;
+        }
+    }
+}
+
 void RenderLayer::computeRepaintRects(LayoutPoint* offsetFromRoot)
 {
     ASSERT(!m_visibleContentStatusDirty);
@@ -464,7 +490,7 @@ void RenderLayer::updateLayerPositionsAfterScroll(UpdateLayerPositionsAfterScrol
 {
     // FIXME: This shouldn't be needed, but there are some corner cases where
     // these flags are still dirty. Update so that the check below is valid.
-    updateVisibilityStatus();
+    updateDescendantDependentFlags();
 
     // If we have no visible content and no visible descendants, there is no point recomputing
     // our rectangles as they will be empty. If our visibility changes, we are expected to
@@ -656,18 +682,25 @@ void RenderLayer::dirtyVisibleDescendantStatus()
     }
 }
 
-void RenderLayer::updateVisibilityStatus()
+void RenderLayer::updateDescendantDependentFlags()
 {
-    if (m_visibleDescendantStatusDirty) {
+    if (m_visibleDescendantStatusDirty || m_hasSelfPaintingLayerDescendantDirty) {
         m_hasVisibleDescendant = false;
+        m_hasSelfPaintingLayerDescendant = false;
         for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) {
-            child->updateVisibilityStatus();        
-            if (child->m_hasVisibleContent || child->m_hasVisibleDescendant) {
-                m_hasVisibleDescendant = true;
+            child->updateDescendantDependentFlags();
+
+            bool hasVisibleDescendant = child->m_hasVisibleContent || child->m_hasVisibleDescendant;
+            bool hasSelfPaintingLayerDescendant = child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant();
+
+            m_hasVisibleDescendant |= hasVisibleDescendant;
+            m_hasSelfPaintingLayerDescendant |= hasSelfPaintingLayerDescendant;
+
+            if (m_hasVisibleDescendant && m_hasSelfPaintingLayerDescendant)
                 break;
-            }
         }
         m_visibleDescendantStatusDirty = false;
+        m_hasSelfPaintingLayerDescendantDirty = false;
     }
 
     if (m_visibleContentStatusDirty) {
@@ -1242,10 +1275,13 @@ void RenderLayer::addChild(RenderLayer* child, RenderLayer* beforeChild)
         child->dirtyStackingContextZOrderLists();
     }
 
-    child->updateVisibilityStatus();
+    child->updateDescendantDependentFlags();
     if (child->m_hasVisibleContent || child->m_hasVisibleDescendant)
         childVisibilityChanged(true);
-    
+
+    if (child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant())
+        setAncestorChainHasSelfPaintingLayerDescendant();
+
 #if USE(ACCELERATED_COMPOSITING)
     compositor()->layerWasAdded(this, child);
 #endif
@@ -1282,10 +1318,13 @@ RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild)
     oldChild->setNextSibling(0);
     oldChild->setParent(0);
     
-    oldChild->updateVisibilityStatus();
+    oldChild->updateDescendantDependentFlags();
     if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant)
         childVisibilityChanged(false);
-    
+
+    if (oldChild->isSelfPaintingLayer() || oldChild->hasSelfPaintingLayerDescendant())
+        dirtyAncestorChainHasSelfPaintingLayerDescendantStatus();
+
     return oldChild;
 }
 
@@ -2882,7 +2921,7 @@ void RenderLayer::paintLayer(RenderLayer* rootLayer, GraphicsContext* context,
 #endif
 
     // Non self-painting leaf layers don't need to be painted as their renderer() should properly paint itself.
-    if (!isSelfPaintingLayer() && !firstChild())
+    if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant())
         return;
 
     if (shouldSuppressPaintingLayer(this))
@@ -2952,6 +2991,8 @@ void RenderLayer::paintLayerContentsAndReflection(RenderLayer* rootLayer, Graphi
                         RenderObject* paintingRoot, RenderRegion* region, OverlapTestRequestMap* overlapTestRequests,
                         PaintLayerFlags paintFlags)
 {
+    ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant());
+
     PaintLayerFlags localPaintFlags = paintFlags & ~(PaintLayerAppliedTransform);
 
     // Paint the reflection first if we have one.
@@ -2971,6 +3012,8 @@ void RenderLayer::paintLayerContents(RenderLayer* rootLayer, GraphicsContext* co
                         RenderObject* paintingRoot, RenderRegion* region, OverlapTestRequestMap* overlapTestRequests,
                         PaintLayerFlags paintFlags)
 {
+    ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant());
+
     PaintLayerFlags localPaintFlags = paintFlags & ~(PaintLayerAppliedTransform);
     bool haveTransparency = localPaintFlags & PaintLayerHaveTransparency;
     bool isSelfPaintingLayer = this->isSelfPaintingLayer();
@@ -3165,6 +3208,9 @@ void RenderLayer::paintList(Vector<RenderLayer*>* list, RenderLayer* rootLayer,
     if (!list)
         return;
 
+    if (!hasSelfPaintingLayerDescendant())
+        return;
+
 #if !ASSERT_DISABLED
     LayerListMutationDetector mutationChecker(this);
 #endif
@@ -4538,7 +4584,7 @@ void RenderLayer::updateNormalFlowList()
 
 void RenderLayer::collectLayers(bool includeHiddenLayers, Vector<RenderLayer*>*& posBuffer, Vector<RenderLayer*>*& negBuffer)
 {
-    updateVisibilityStatus();
+    updateDescendantDependentFlags();
 
     // Overflow layers are just painted by their enclosing layers, so they don't get put in zorder lists.
     bool includeHiddenLayer = includeHiddenLayers || (m_hasVisibleContent || (m_hasVisibleDescendant && isStackingContext()));
@@ -4674,6 +4720,21 @@ bool RenderLayer::shouldBeSelfPaintingLayer() const
         || renderer()->isRenderIFrame();
 }
 
+void RenderLayer::updateSelfPaintingLayerAfterStyleChange(const RenderStyle*)
+{
+    bool isSelfPaintingLayer = shouldBeSelfPaintingLayer();
+    if (m_isSelfPaintingLayer == isSelfPaintingLayer)
+        return;
+
+    m_isSelfPaintingLayer = isSelfPaintingLayer;
+    if (!parent())
+        return;
+    if (isSelfPaintingLayer)
+        parent()->setAncestorChainHasSelfPaintingLayerDescendant();
+    else
+        parent()->dirtyAncestorChainHasSelfPaintingLayerDescendantStatus();
+}
+
 void RenderLayer::updateStackingContextsAfterStyleChange(const RenderStyle* oldStyle)
 {
     if (!oldStyle)
@@ -4744,8 +4805,6 @@ void RenderLayer::styleChanged(StyleDifference, const RenderStyle* oldStyle)
         dirtyStackingContextZOrderLists();
     }
 
-    m_isSelfPaintingLayer = shouldBeSelfPaintingLayer();
-
     if (renderer()->style()->overflowX() == OMARQUEE && renderer()->style()->marqueeBehavior() != MNONE && renderer()->isBox()) {
         if (!m_marquee)
             m_marquee = new RenderMarquee(this);
@@ -4756,6 +4815,7 @@ void RenderLayer::styleChanged(StyleDifference, const RenderStyle* oldStyle)
         m_marquee = 0;
     }
 
+    updateSelfPaintingLayerAfterStyleChange(oldStyle);
     updateStackingContextsAfterStyleChange(oldStyle);
     updateScrollbarsAfterStyleChange(oldStyle);
 
@@ -4781,7 +4841,7 @@ void RenderLayer::styleChanged(StyleDifference, const RenderStyle* oldStyle)
 #endif
 
 #if USE(ACCELERATED_COMPOSITING)
-    updateVisibilityStatus();
+    updateDescendantDependentFlags();
     updateTransform();
 
     if (compositor()->updateLayerCompositingState(this))
index 08791b9..5a5e244 100644 (file)
@@ -444,6 +444,10 @@ public:
     void setHasVisibleContent(bool);
     void dirtyVisibleContentStatus();
 
+    // FIXME: We should ASSERT(!m_hasSelfPaintingLayerDescendantDirty); here but we hit the same bugs as visible content above.
+    // Part of the issue is with subtree relayout: we don't check if our ancestors have some descendant flags dirty, missing some updates.
+    bool hasSelfPaintingLayerDescendant() const { return m_hasSelfPaintingLayerDescendant; }
+
     // Gets the nearest enclosing positioned ancestor layer (also includes
     // the <html> layer and the root layer).
     RenderLayer* enclosingPositionedAncestor() const;
@@ -651,6 +655,9 @@ private:
     bool isStackingContext(const RenderStyle* style) const { return !style->hasAutoZIndex() || isRootLayer(); }
     bool isDirtyStackingContext() const { return m_zOrderListsDirty && isStackingContext(); }
 
+    void setAncestorChainHasSelfPaintingLayerDescendant();
+    void dirtyAncestorChainHasSelfPaintingLayerDescendantStatus();
+
     void computeRepaintRects(LayoutPoint* offsetFromRoot = 0);
     void clearRepaintRects();
 
@@ -660,6 +667,7 @@ private:
 
     bool shouldRepaintAfterLayout() const;
 
+    void updateSelfPaintingLayerAfterStyleChange(const RenderStyle* oldStyle);
     void updateStackingContextsAfterStyleChange(const RenderStyle* oldStyle);
 
     void updateScrollbarsAfterStyleChange(const RenderStyle* oldStyle);
@@ -776,7 +784,8 @@ private:
 
     void childVisibilityChanged(bool newVisibility);
     void dirtyVisibleDescendantStatus();
-    void updateVisibilityStatus();
+
+    void updateDescendantDependentFlags();
 
     // This flag is computed by RenderLayerCompositor, which knows more about 3d hierarchies than we do.
     void setHas3DTransformedDescendant(bool b) { m_has3DTransformedDescendant = b; }
@@ -872,6 +881,11 @@ protected:
 
     bool m_isSelfPaintingLayer : 1;
 
+    // If have no self-painting descendants, we don't have to walk our children during painting. This can lead to
+    // significant savings, especially if the tree has lots of non-self-painting layers grouped together (e.g. table cells).
+    bool m_hasSelfPaintingLayerDescendant : 1;
+    bool m_hasSelfPaintingLayerDescendantDirty : 1;
+
     const bool m_isRootLayer : 1;
 
     bool m_usedTransparency : 1; // Tracks whether we need to close a transparent layer, i.e., whether
index d55c431..c87bbcd 100644 (file)
@@ -424,7 +424,7 @@ void RenderLayerBacking::updateGraphicsLayerGeometry()
     updateLayerFilters(renderer()->style());
 #endif
     
-    m_owningLayer->updateVisibilityStatus();
+    m_owningLayer->updateDescendantDependentFlags();
 
     // m_graphicsLayer is the corresponding GraphicsLayer for this RenderLayer and its non-compositing
     // descendants. So, the visibility flag for m_graphicsLayer should be true if there are any