Bug 1386483 - Push, push, push the clips, caching all the way. r?jrmuizel,mstange draft
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 08 Aug 2017 15:43:29 -0400
changeset 642818 62c90eabd3ba2b5eaf60afe5cac74854c36de3c6
parent 642817 0b41784ec99d54ba9ca5764c17540a18dc353fe3
child 725108 79882b4fd32b83ad936799edf365b2213da45445
push id72874
push userkgupta@mozilla.com
push dateTue, 08 Aug 2017 19:43:50 +0000
reviewersjrmuizel, mstange
bugs1386483
milestone57.0a1
Bug 1386483 - Push, push, push the clips, caching all the way. r?jrmuizel,mstange This patch ensures that we push clips in WR for each display item, reflecting the display item's clip chain as computed in Gecko. A display item will often share part or most of its clip chain with other display items, so we try to reuse the corresponding WR clip ids as much as we can instead of defining new duplicated clips. MozReview-Commit-ID: LkBh8LIpQ4J
gfx/layers/wr/ScrollingLayersHelper.cpp
gfx/layers/wr/ScrollingLayersHelper.h
gfx/layers/wr/WebRenderLayerManager.cpp
gfx/layers/wr/WebRenderLayerManager.h
gfx/webrender_bindings/WebRenderTypes.h
--- a/gfx/layers/wr/ScrollingLayersHelper.cpp
+++ b/gfx/layers/wr/ScrollingLayersHelper.cpp
@@ -16,16 +16,17 @@ namespace mozilla {
 namespace layers {
 
 ScrollingLayersHelper::ScrollingLayersHelper(WebRenderLayer* aLayer,
                                              wr::DisplayListBuilder& aBuilder,
                                              const StackingContextHelper& aStackingContext)
   : mLayer(aLayer)
   , mBuilder(&aBuilder)
   , mPushedLayerLocalClip(false)
+  , mClipsPushed(0)
 {
   if (!mLayer->WrManager()->AsyncPanZoomEnabled()) {
     // If APZ is disabled then we don't need to push the scrolling clips. We
     // still want to push the layer's local clip though.
     PushLayerLocalClip(aStackingContext);
     return;
   }
 
@@ -102,16 +103,70 @@ ScrollingLayersHelper::ScrollingLayersHe
     Maybe<wr::WrClipId> clipId = mBuilder->TopmostClipId();
     // Default to 0 if there is no ancestor, because 0 refers to the root scrollframe.
     mBuilder->PushClipAndScrollInfo(scrollsWith.valueOr(0), clipId.ptrOr(nullptr));
   } else {
     PushLayerLocalClip(aStackingContext);
   }
 }
 
+ScrollingLayersHelper::ScrollingLayersHelper(nsDisplayItem* aItem,
+                                             wr::DisplayListBuilder& aBuilder,
+                                             const StackingContextHelper& aStackingContext,
+                                             WebRenderLayerManager::ClipIdMap& aCache)
+  : mLayer(nullptr)
+  , mBuilder(&aBuilder)
+  , mPushedLayerLocalClip(false)
+  , mClipsPushed(0)
+{
+  DefineAndPushChain(aItem->GetClipChain(), aBuilder, aStackingContext,
+      aItem->Frame()->PresContext()->AppUnitsPerDevPixel(), aCache);
+}
+
+void
+ScrollingLayersHelper::DefineAndPushChain(const DisplayItemClipChain* aChain,
+                                          wr::DisplayListBuilder& aBuilder,
+                                          const StackingContextHelper& aStackingContext,
+                                          int32_t aAppUnitsPerDevPixel,
+                                          WebRenderLayerManager::ClipIdMap& aCache)
+{
+  if (!aChain) {
+    return;
+  }
+  auto it = aCache.find(aChain);
+  Maybe<wr::WrClipId> clipId = (it != aCache.end() ? Some(it->second) : Nothing());
+  if (clipId && clipId == aBuilder.TopmostClipId()) {
+    // it was already in the cache and pushed on the WR clip stack, so we don't
+    // need to recurse any further.
+    return;
+  }
+  // Recurse up the clip chain to make sure all ancestor clips are defined and
+  // pushed onto the WR clip stack. Note that the recursion can invalidate the
+  // iterator `it`.
+  DefineAndPushChain(aChain->mParent, aBuilder, aStackingContext, aAppUnitsPerDevPixel, aCache);
+
+  if (!aChain->mClip.HasClip()) {
+    // This item in the chain is a no-op, skip over it
+    return;
+  }
+  if (!clipId) {
+    // If we don't have a clip id for this chain item yet, define the clip in WR
+    // and save the id
+    LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits(
+        aChain->mClip.GetClipRect(), aAppUnitsPerDevPixel);
+    // TODO: deal with rounded corners here
+    clipId = Some(aBuilder.DefineClip(aStackingContext.ToRelativeLayoutRect(clip)));
+    aCache[aChain] = clipId.value();
+  }
+  // Finally, push the clip onto the WR stack
+  MOZ_ASSERT(clipId);
+  aBuilder.PushClip(clipId.value());
+  mClipsPushed++;
+}
+
 void
 ScrollingLayersHelper::PushLayerLocalClip(const StackingContextHelper& aStackingContext)
 {
   Layer* layer = mLayer->GetLayer();
   Maybe<ParentLayerRect> clip;
   if (const Maybe<ParentLayerIntRect>& rect = layer->GetClipRect()) {
     clip = Some(IntRectToRect(rect.ref()));
   } else if (layer->GetMaskLayer()) {
@@ -143,16 +198,25 @@ ScrollingLayersHelper::PushLayerClip(con
     mask = maskWrLayer->RenderMaskLayer(aSc, maskLayer->GetTransform());
   }
   mBuilder->PushClip(mBuilder->DefineClip(
       aSc.ToRelativeLayoutRect(clipRect), nullptr, mask.ptrOr(nullptr)));
 }
 
 ScrollingLayersHelper::~ScrollingLayersHelper()
 {
+  if (!mLayer) {
+    // For layers-free mode
+    while (mClipsPushed > 0) {
+      mBuilder->PopClip();
+      mClipsPushed--;
+    }
+    return;
+  }
+
   Layer* layer = mLayer->GetLayer();
   if (!mLayer->WrManager()->AsyncPanZoomEnabled()) {
     if (mPushedLayerLocalClip) {
       mBuilder->PopClip();
     }
     return;
   }
 
--- a/gfx/layers/wr/ScrollingLayersHelper.h
+++ b/gfx/layers/wr/ScrollingLayersHelper.h
@@ -2,43 +2,56 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_SCROLLINGLAYERSHELPER_H
 #define GFX_SCROLLINGLAYERSHELPER_H
 
 #include "mozilla/Attributes.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
 
 namespace mozilla {
 
+struct DisplayItemClipChain;
+
 namespace wr {
 class DisplayListBuilder;
 }
 
 namespace layers {
 
 struct LayerClip;
 class StackingContextHelper;
 class WebRenderLayer;
 
 class MOZ_RAII ScrollingLayersHelper
 {
 public:
   ScrollingLayersHelper(WebRenderLayer* aLayer,
                         wr::DisplayListBuilder& aBuilder,
                         const StackingContextHelper& aSc);
+  ScrollingLayersHelper(nsDisplayItem* aItem,
+                        wr::DisplayListBuilder& aBuilder,
+                        const StackingContextHelper& aStackingContext,
+                        WebRenderLayerManager::ClipIdMap& aCache);
   ~ScrollingLayersHelper();
 
 private:
+  void DefineAndPushChain(const DisplayItemClipChain* aChain,
+                          wr::DisplayListBuilder& aBuilder,
+                          const StackingContextHelper& aStackingContext,
+                          int32_t aAppUnitsPerDevPixel,
+                          WebRenderLayerManager::ClipIdMap& aCache);
   void PushLayerLocalClip(const StackingContextHelper& aStackingContext);
   void PushLayerClip(const LayerClip& aClip,
                      const StackingContextHelper& aSc);
 
   WebRenderLayer* mLayer;
   wr::DisplayListBuilder* mBuilder;
   bool mPushedLayerLocalClip;
+  int mClipsPushed;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -259,21 +259,25 @@ WebRenderLayerManager::CreateWebRenderCo
       // If we're going to create a new layer data for this item, stash the
       // ASR so that if we recurse into a sublist they will know where to stop
       // walking up their ASR chain when building scroll metadata.
       if (forceNewLayerData) {
         mAsrStack.push_back(asr);
       }
     }
 
-    // Note: this call to CreateWebRenderCommands can recurse back into
-    // this function if the |item| is a wrapper for a sublist.
-    if (!item->CreateWebRenderCommands(aBuilder, aSc, mParentCommands, this,
-                                       aDisplayListBuilder)) {
-      PushItemAsImage(item, aBuilder, aSc, aDisplayListBuilder);
+    { // scope the ScrollingLayersHelper
+      ScrollingLayersHelper clip(item, aBuilder, aSc, mClipIdCache);
+
+      // Note: this call to CreateWebRenderCommands can recurse back into
+      // this function if the |item| is a wrapper for a sublist.
+      if (!item->CreateWebRenderCommands(aBuilder, aSc, mParentCommands, this,
+                                         aDisplayListBuilder)) {
+        PushItemAsImage(item, aBuilder, aSc, aDisplayListBuilder);
+      }
     }
 
     if (apzEnabled && forceNewLayerData) {
       // Pop the thing we pushed before the recursion, so the topmost item on
       // the stack is enclosing display item's ASR (or the stack is empty)
       mAsrStack.pop_back();
       const ActiveScrolledRoot* stopAtAsr =
           mAsrStack.empty() ? nullptr : mAsrStack.back();
@@ -619,16 +623,17 @@ WebRenderLayerManager::EndTransactionInt
       mLayerScrollData.back().InitializeRoot(mLayerScrollData.size() - 1);
       // Append the WebRenderLayerScrollData items into WebRenderScrollData
       // in reverse order, from topmost to bottommost. This is in keeping with
       // the semantics of WebRenderScrollData.
       for (auto i = mLayerScrollData.crbegin(); i != mLayerScrollData.crend(); i++) {
         mScrollData.AddLayerData(*i);
       }
       mLayerScrollData.clear();
+      mClipIdCache.clear();
     } else {
       for (auto iter = mLastCanvasDatas.Iter(); !iter.Done(); iter.Next()) {
         RefPtr<WebRenderCanvasData> canvasData = iter.Get()->GetKey();
         WebRenderCanvasRendererAsync* canvas = canvasData->GetCanvasRenderer();
         canvas->UpdateCompositableClient();
       }
     }
 
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -268,16 +268,31 @@ private:
   // inside a layers-free transaction.
   std::vector<WebRenderLayerScrollData> mLayerScrollData;
   // We use this as a temporary data structure to track the current display
   // item's ASR as we recurse in CreateWebRenderCommandsFromDisplayList. We
   // need this so that WebRenderLayerScrollData items that deeper in the
   // tree don't duplicate scroll metadata that their ancestors already have.
   std::vector<const ActiveScrolledRoot*> mAsrStack;
 
+public:
+  // Note: two DisplayItemClipChain* A and B might actually be "equal" (as per
+  // DisplayItemClipChain::Equal(A, B)) even though they are not the same pointer
+  // (A != B). In this hopefully-rare case, they will get separate entries
+  // in this map when in fact we could collapse them. However, to collapse
+  // them involves writing a custom hash function for the pointer type such that
+  // A and B hash to the same things whenever DisplayItemClipChain::Equal(A, B)
+  // is true, and that will incur a performance penalty for all the hashmap
+  // operations, so is probably not worth it. With the current code we might
+  // end up creating multiple clips in WR that are effectively identical but
+  // have separate clip ids. Hopefully this won't happen very often.
+  typedef std::unordered_map<const DisplayItemClipChain*, wr::WrClipId> ClipIdMap;
+private:
+  ClipIdMap mClipIdCache;
+
   // Layers that have been mutated. If we have an empty transaction
   // then a display item layer will no longer be valid
   // if it was a mutated layers.
   void AddMutatedLayer(Layer* aLayer);
   void ClearMutatedLayers();
   LayerRefArray mMutatedLayers;
   bool mTransactionIncomplete;
 
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -644,14 +644,18 @@ static inline wr::WrFilterOp ToWrFilterO
 }
 
 // Corresponds to an "internal" webrender clip id. That is, a
 // ClipId::Clip(x,pipeline_id) maps to a WrClipId{x}. We use a struct wrapper
 // instead of a typedef so that this is a distinct type from FrameMetrics::ViewID
 // and the compiler will catch accidental conversions between the two.
 struct WrClipId {
   uint64_t id;
+
+  bool operator==(const WrClipId& other) const {
+    return id == other.id;
+  }
 };
 
 } // namespace wr
 } // namespace mozilla
 
 #endif /* GFX_WEBRENDERTYPES_H */