Bug 1429932 - Part 4: Determine when AttemptPartialUpdate made no changes, and return the result to the caller. r=miko
☠☠ backed out by d32017c991aa ☠ ☠
authorMatt Woodrow <mwoodrow@mozilla.com>
Fri, 12 Jan 2018 11:46:23 +1300
changeset 748702 72852fdf476c4f586eba85e352c31fb34451a156
parent 748701 09e5e25d48ee3b911b351f9e7f29755080d865fd
child 748703 2f801e78f7987557cd71abc0b25800453c7138d9
push id97228
push usersfraser@mozilla.com
push dateTue, 30 Jan 2018 10:21:04 +0000
reviewersmiko
bugs1429932
milestone60.0a1
Bug 1429932 - Part 4: Determine when AttemptPartialUpdate made no changes, and return the result to the caller. r=miko MozReview-Commit-ID: 6S8WkzHcxP5
layout/base/nsLayoutUtils.cpp
layout/painting/RetainedDisplayListBuilder.cpp
layout/painting/RetainedDisplayListBuilder.h
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3630,16 +3630,17 @@ GetOrCreateRetainedDisplayListBuilder(ns
 
 nsresult
 nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
                           const nsRegion& aDirtyRegion, nscolor aBackstop,
                           nsDisplayListBuilderMode aBuilderMode,
                           PaintFrameFlags aFlags)
 {
   AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame", GRAPHICS);
+  typedef RetainedDisplayListBuilder::PartialUpdateResult PartialUpdateResult;
 
 #ifdef MOZ_DUMP_PAINTING
   if (!gPaintCountStack) {
     gPaintCountStack = new nsTArray<int>();
     ClearOnShutdown(&gPaintCountStack);
 
     gPaintCountStack->AppendElement(0);
   }
@@ -3787,16 +3788,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
   }
 
   builder.ClearHaveScrollableDisplayPort();
   if (builder.IsPaintingToWindow()) {
     MaybeCreateDisplayPortInFirstScrollFrameEncountered(aFrame, builder);
   }
 
   nsRect visibleRect = visibleRegion.GetBounds();
+  PartialUpdateResult updateState = PartialUpdateResult::Failed;
 
   {
     AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame:BuildDisplayList",
                         GRAPHICS);
     AUTO_PROFILER_TRACING("Paint", "DisplayList");
 
     PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList);
     TimeStamp dlStart = TimeStamp::Now();
@@ -3835,50 +3837,49 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
           nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell));
 
       DisplayListChecker beforeMergeChecker;
       DisplayListChecker afterMergeChecker;
 
       // Attempt to do a partial build and merge into the existing list.
       // This calls BuildDisplayListForStacking context on a subset of the
       // viewport.
-      bool merged = false;
-
       if (useRetainedBuilder) {
         if (gfxPrefs::LayoutVerifyRetainDisplayList()) {
           beforeMergeChecker.Set(&list, "BM");
         }
-        merged = retainedBuilder->AttemptPartialUpdate(aBackstop);
-        if (merged && beforeMergeChecker) {
+        updateState = retainedBuilder->AttemptPartialUpdate(aBackstop);
+        if ((updateState != PartialUpdateResult::Failed) && beforeMergeChecker) {
           afterMergeChecker.Set(&list, "AM");
         }
       }
 
-      if (merged &&
+      if ((updateState != PartialUpdateResult::Failed) &&
           (gfxPrefs::LayoutDisplayListBuildTwice() || afterMergeChecker)) {
-        merged = false;
+        updateState = PartialUpdateResult::Failed;
         if (gfxPrefs::LayersDrawFPS()) {
           if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
             if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
               pt->dl2Ms() = (TimeStamp::Now() - dlStart).ToMilliseconds();
             }
           }
         }
         dlStart = TimeStamp::Now();
       }
 
-      if (!merged) {
+      if (updateState == PartialUpdateResult::Failed) {
         list.DeleteAll(&builder);
         builder.EnterPresShell(aFrame);
         builder.SetDirtyRect(visibleRect);
         builder.ClearWindowDraggingRegion();
         aFrame->BuildDisplayListForStackingContext(&builder, &list);
         AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
 
         builder.LeavePresShell(aFrame, &list);
+        updateState = PartialUpdateResult::Updated;
 
         if (afterMergeChecker) {
           DisplayListChecker nonRetainedChecker(&list, "NR");
           std::stringstream ss;
           ss << "**** Differences between retained-after-merged (AM) and "
              << "non-retained (NR) display lists:";
           if (!nonRetainedChecker.CompareList(afterMergeChecker, ss)) {
             ss << "\n\n*** non-retained display items:";
@@ -3904,16 +3905,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
       if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
         if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
           pt->dlMs() = (TimeStamp::Now() - dlStart).ToMilliseconds();
         }
       }
     }
   }
 
+  MOZ_ASSERT(updateState != PartialUpdateResult::Failed);
   builder.Check();
 
   Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME,
                                  startBuildDisplayList);
 
   bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint();
 #ifdef MOZ_DUMP_PAINTING
   FILE* savedDumpFile = gfxUtils::sDumpPaintFile;
--- a/layout/painting/RetainedDisplayListBuilder.cpp
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -97,49 +97,55 @@ SelectAGRForFrame(nsIFrame* aFrame, Anim
 }
 
 // Removes any display items that belonged to a frame that was deleted,
 // and mark frames that belong to a different AGR so that get their
 // items built again.
 // TODO: We currently descend into all children even if we don't have an AGR
 // to mark, as child stacking contexts might. It would be nice if we could
 // jump into those immediately rather than walking the entire thing.
-void
+bool
 RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList,
                                                   AnimatedGeometryRoot* aAGR)
 {
+  bool modified = false;
   nsDisplayList saved;
   while (nsDisplayItem* i = aList->RemoveBottom()) {
     if (i->HasDeletedFrame() || !i->CanBeReused()) {
       i->Destroy(&mBuilder);
+      modified = true;
       continue;
     }
 
     nsIFrame* f = i->Frame();
 
     if (i->GetChildren()) {
-      PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR));
+      if (PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR))) {
+        modified = true;
+      }
     }
 
     // TODO: We should be able to check the clipped bounds relative
     // to the common AGR (of both the existing item and the invalidated
     // frame) and determine if they can ever intersect.
     if (aAGR && i->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
       mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
+      modified = true;
     }
 
     // TODO: This is here because we sometimes reuse the previous display list
     // completely. For optimization, we could only restore the state for reused
     // display items.
     i->RestoreState();
 
     saved.AppendToTop(i);
   }
   aList->AppendToTop(&saved);
   aList->RestoreState();
+  return modified;
 }
 
 bool IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond)
 {
   return aFirst->Frame() == aSecond->Frame() &&
          aFirst->GetPerFrameKey() == aSecond->GetPerFrameKey();
 }
 
@@ -381,22 +387,24 @@ void UpdateASR(nsDisplayItem* aItem,
  * We then match A, add the new version the merged list and delete the old version.
  *
  * We then process the remainder of the old list, B is added (since it is valid,
  * and hasn't been mark as reused), C is destroyed since it's marked as reused and
  * is already present in the merged list.
  *
  * Merged List: C, A, B
  */
-void
+bool
 RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList,
                                               nsDisplayList* aOldList,
                                               nsDisplayList* aOutList,
                                               Maybe<const ActiveScrolledRoot*>& aOutContainerASR)
 {
+  bool modified = aNewList->IsEmpty() ? false : true;
+
   nsDisplayList merged;
   const auto UseItem = [&](nsDisplayItem* aItem) {
     const ActiveScrolledRoot* itemClipASR =
       aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr;
 
     const ActiveScrolledRoot* finiteBoundsASR = ActiveScrolledRoot::PickDescendant(
       itemClipASR, aItem->GetActiveScrolledRoot());
     if (!aOutContainerASR) {
@@ -530,31 +538,35 @@ RetainedDisplayListBuilder::MergeDisplay
       if (old->GetChildren()) {
         // We are calling MergeDisplayLists() to ensure that the display items
         // with modified or deleted children will be correctly handled.
         // Passing an empty new display list as an argument skips the merging
         // loop above and jumps back here.
         nsDisplayList empty;
         Maybe<const ActiveScrolledRoot*> containerASRForChildren;
 
-        MergeDisplayLists(&empty, old->GetChildren(),
-                          old->GetChildren(), containerASRForChildren);
+        if (MergeDisplayLists(&empty, old->GetChildren(),
+                              old->GetChildren(), containerASRForChildren)) {
+          modified = true;
+        }
         UpdateASR(old, containerASRForChildren);
         old->UpdateBounds(&mBuilder);
       }
       if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
         MergeLayerEventRegions(old, nullptr);
       }
       ReuseItem(old);
     } else {
       old->Destroy(&mBuilder);
+      modified = true;
     }
   }
 
   aOutList->AppendToTop(&merged);
+  return modified;
 }
 
 static void
 TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
   nsTArray<nsIFrame*>* aModifiedFrames,
   nsTArray<nsIFrame*>* aFramesWithProps,
   nsIFrame* aRootFrame)
 {
@@ -1008,18 +1020,18 @@ private:
 void
 RetainedDisplayListBuilder::ClearFramesWithProps()
 {
   AutoClearFramePropsArray modifiedFrames;
   AutoClearFramePropsArray framesWithProps;
   GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames());
 }
 
-bool
-RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop)
+auto
+RetainedDisplayListBuilder::AttemptPartialUpdate(nscolor aBackstop) -> PartialUpdateResult
 {
   mBuilder.RemoveModifiedWindowDraggingRegion();
   if (mBuilder.ShouldSyncDecodeImages()) {
     MarkFramesWithItemsAndImagesModified(&mList);
   }
 
   mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
 
@@ -1051,22 +1063,27 @@ RetainedDisplayListBuilder::AttemptParti
   }
 
   nsRect modifiedDirty;
   AnimatedGeometryRoot* modifiedAGR = nullptr;
   if (!shouldBuildPartial ||
       !ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty,
                            &modifiedAGR, framesWithProps.Frames())) {
     mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList);
-    return false;
+    return PartialUpdateResult::Failed;
   }
 
   modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
 
-  PreProcessDisplayList(&mList, modifiedAGR);
+  PartialUpdateResult result = PartialUpdateResult::NoChange;
+  if (PreProcessDisplayList(&mList, modifiedAGR) ||
+      !modifiedDirty.IsEmpty() ||
+      !framesWithProps.IsEmpty()) {
+    result = PartialUpdateResult::Updated;
+  }
 
   nsDisplayList modifiedDL;
   if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) {
     mBuilder.SetDirtyRect(modifiedDirty);
     mBuilder.SetPartialUpdate(true);
     mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL);
     nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(),
                                            nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
@@ -1085,17 +1102,23 @@ RetainedDisplayListBuilder::AttemptParti
     // printf_stderr("Skipping display list building since nothing needed to be done\n");
   }
 
   // |modifiedDL| can sometimes be empty here. We still perform the
   // display list merging to prune unused items (for example, items that
   // are not visible anymore) from the old list.
   // TODO: Optimization opportunity. In this case, MergeDisplayLists()
   // unnecessarily creates a hashtable of the old items.
+  // TODO: Ideally we could skip this if result is NoChange, but currently when
+  // we call RestoreState on nsDisplayWrapList it resets the clip to the base
+  // clip, and we need the UpdateBounds call (within MergeDisplayLists) to
+  // move it to the correct inner clip.
   Maybe<const ActiveScrolledRoot*> dummy;
-  MergeDisplayLists(&modifiedDL, &mList, &mList, dummy);
+  if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
+    result = PartialUpdateResult::Updated;
+  }
 
   //printf_stderr("Painting --- Merged list:\n");
   //nsFrame::PrintDisplayList(&mBuilder, mList);
 
   mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList);
-  return true;
+  return result;
 }
--- a/layout/painting/RetainedDisplayListBuilder.h
+++ b/layout/painting/RetainedDisplayListBuilder.h
@@ -20,32 +20,38 @@ struct RetainedDisplayListBuilder {
   {
     mList.DeleteAll(&mBuilder);
   }
 
   nsDisplayListBuilder* Builder() { return &mBuilder; }
 
   nsDisplayList* List() { return &mList; }
 
-  bool AttemptPartialUpdate(nscolor aBackstop);
+  enum class PartialUpdateResult {
+    Failed,
+    NoChange,
+    Updated
+  };
+
+  PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop);
 
   /**
    * Iterates through the display list builder reference frame document and
    * subdocuments, and clears the modified frame lists from the root frames.
    * Also clears the frame properties set by RetainedDisplayListBuilder for all
    * the frames in the modified frame lists.
    */
   void ClearFramesWithProps();
 
   NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder)
 
 private:
-  void PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR);
+  bool PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR);
 
-  void MergeDisplayLists(nsDisplayList* aNewList,
+  bool MergeDisplayLists(nsDisplayList* aNewList,
                          nsDisplayList* aOldList,
                          nsDisplayList* aOutList,
                          mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR);
 
   bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames,
                             nsRect* aOutDirty,
                             AnimatedGeometryRoot** aOutModifiedAGR,
                             nsTArray<nsIFrame*>& aOutFramesWithProps);