Bug 1500575 - Map inset clip paths to WR complex clip regions. r=kats
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 23 Oct 2018 00:10:00 +0000
changeset 490831 9140cba9a8f80c392dd57d7a89aa85baca30547d
parent 490830 3ef66c8d682c099b0ccb8bc41d2fd678dc39d29a
child 490832 26d5c8531a081dda749a6ddbcec3cb86fac39c78
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerskats
bugs1500575
milestone65.0a1
Bug 1500575 - Map inset clip paths to WR complex clip regions. r=kats Differential Revision: https://phabricator.services.mozilla.com/D9412
gfx/webrender_bindings/WebRenderTypes.h
layout/generic/nsFrame.cpp
layout/painting/DisplayItemClip.cpp
layout/painting/nsDisplayList.cpp
layout/reftests/svg/svg-integration/clip-path/reftest.list
layout/svg/nsSVGIntegrationUtils.cpp
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -498,16 +498,41 @@ static inline wr::BorderRadius ToBorderR
   wr::BorderRadius br;
   br.top_left = ToLayoutSize(topLeft);
   br.top_right = ToLayoutSize(topRight);
   br.bottom_left = ToLayoutSize(bottomLeft);
   br.bottom_right = ToLayoutSize(bottomRight);
   return br;
 }
 
+static inline wr::ComplexClipRegion ToComplexClipRegion(
+  const nsRect& aRect,
+  const nscoord* aRadii,
+  int32_t aAppUnitsPerDevPixel)
+{
+  wr::ComplexClipRegion ret;
+  ret.rect = ToRoundedLayoutRect(
+    LayoutDeviceRect::FromAppUnits(aRect, aAppUnitsPerDevPixel));
+  ret.radii = ToBorderRadius(
+    LayoutDeviceSize::FromAppUnits(
+      nsSize(aRadii[eCornerTopLeftX], aRadii[eCornerTopLeftY]),
+      aAppUnitsPerDevPixel),
+    LayoutDeviceSize::FromAppUnits(
+      nsSize(aRadii[eCornerTopRightX], aRadii[eCornerTopRightY]),
+      aAppUnitsPerDevPixel),
+    LayoutDeviceSize::FromAppUnits(
+      nsSize(aRadii[eCornerBottomLeftX], aRadii[eCornerBottomLeftY]),
+      aAppUnitsPerDevPixel),
+    LayoutDeviceSize::FromAppUnits(
+      nsSize(aRadii[eCornerBottomRightX], aRadii[eCornerBottomRightY]),
+      aAppUnitsPerDevPixel));
+  ret.mode = ClipMode::Clip;
+  return ret;
+}
+
 static inline wr::LayoutSideOffsets ToBorderWidths(float top, float right, float bottom, float left)
 {
   wr::LayoutSideOffsets bw;
   bw.top = top;
   bw.right = right;
   bw.bottom = bottom;
   bw.left = left;
   return bw;
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3180,17 +3180,17 @@ nsIFrame::BuildDisplayListForStackingCon
   }
 
   /* If there are any SVG effects, wrap the list up in an SVG effects item
    * (which also handles CSS group opacity). Note that we create an SVG effects
    * item even if resultList is empty, since a filter can produce graphical
    * output even if the element being filtered wouldn't otherwise do so.
    */
   if (usingSVGEffects) {
-    MOZ_ASSERT(usingFilter ||usingMask,
+    MOZ_ASSERT(usingFilter || usingMask,
                "Beside filter & mask/clip-path, what else effect do we have?");
 
     if (clipCapturedBy == ContainerItemType::eFilter) {
       clipState.Restore();
     }
     // Revert to the post-filter dirty rect.
     aBuilder->SetVisibleRect(visibleRectOutsideSVGEffects);
 
@@ -3204,17 +3204,17 @@ nsIFrame::BuildDisplayListForStackingCon
     if (usingMask) {
       DisplayListClipState::AutoSaveRestore maskClipState(aBuilder);
       // The mask should move with aBuilder->CurrentActiveScrolledRoot(), so
       // that's the ASR we prefer to use for the mask item. However, we can
       // only do this if the mask if clipped with respect to that ASR, because
       // an item always needs to have finite bounds with respect to its ASR.
       // If we weren't able to compute a clip for the mask, we fall back to
       // using containerItemASR, which is the lowest common ancestor clip of
-      // the mask's contents. That's not entirely crrect, but it satisfies
+      // the mask's contents. That's not entirely correct, but it satisfies
       // the base requirement of the ASR system (that items have finite bounds
       // wrt. their ASR).
       const ActiveScrolledRoot* maskASR = clipForMask.isSome()
                                         ? aBuilder->CurrentActiveScrolledRoot()
                                         : containerItemASR;
       /* List now emptied, so add the new list to the top. */
       resultList.AppendToTop(MakeDisplayItem<nsDisplayMasksAndClipPaths>(
         aBuilder, this, &resultList, maskASR));
--- a/layout/painting/DisplayItemClip.cpp
+++ b/layout/painting/DisplayItemClip.cpp
@@ -582,31 +582,15 @@ DisplayItemClip::ToString() const
 }
 
 void
 DisplayItemClip::ToComplexClipRegions(
   int32_t aAppUnitsPerDevPixel,
   const layers::StackingContextHelper& aSc,
   nsTArray<wr::ComplexClipRegion>& aOutArray) const
 {
-  for (uint32_t i = 0; i < mRoundedClipRects.Length(); i++) {
-    wr::ComplexClipRegion* region = aOutArray.AppendElement();
-    region->rect = wr::ToRoundedLayoutRect(LayoutDeviceRect::FromAppUnits(
-      mRoundedClipRects[i].mRect, aAppUnitsPerDevPixel));
-    const nscoord* radii = mRoundedClipRects[i].mRadii;
-    region->radii = wr::ToBorderRadius(
-      LayoutDeviceSize::FromAppUnits(
-        nsSize(radii[eCornerTopLeftX], radii[eCornerTopLeftY]),
-        aAppUnitsPerDevPixel),
-      LayoutDeviceSize::FromAppUnits(
-        nsSize(radii[eCornerTopRightX], radii[eCornerTopRightY]),
-        aAppUnitsPerDevPixel),
-      LayoutDeviceSize::FromAppUnits(
-        nsSize(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]),
-        aAppUnitsPerDevPixel),
-      LayoutDeviceSize::FromAppUnits(
-        nsSize(radii[eCornerBottomRightX], radii[eCornerBottomRightY]),
-        aAppUnitsPerDevPixel));
-    region->mode = wr::ClipMode::Clip;
+  for (const auto& clipRect : mRoundedClipRects) {
+    aOutArray.AppendElement(wr::ToComplexClipRegion(
+      clipRect.mRect, clipRect.mRadii, aAppUnitsPerDevPixel));
   }
 }
 
 } // namespace mozilla
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -18,16 +18,17 @@
 
 #include "gfxContext.h"
 #include "gfxUtils.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/KeyframeEffect.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/layers/PLayerTransaction.h"
+#include "mozilla/ShapeUtils.h"
 #include "nsCSSRendering.h"
 #include "nsCSSRenderingGradients.h"
 #include "nsISelectionController.h"
 #include "nsIPresShell.h"
 #include "nsRegion.h"
 #include "nsStyleStructInlines.h"
 #include "nsStyleTransformMatrix.h"
 #include "gfxMatrix.h"
@@ -9958,40 +9959,116 @@ nsDisplayMasksAndClipPaths::PaintWithCon
 
   nsSVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren);
 
   context->PopClip();
 
   nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result);
 }
 
+static Maybe<wr::WrClipId>
+CreateSimpleClipRegion(const nsDisplayMasksAndClipPaths& aDisplayItem,
+                       wr::DisplayListBuilder& aBuilder)
+{
+  nsIFrame* frame = aDisplayItem.Frame();
+  auto* style = frame->StyleSVGReset();
+  MOZ_ASSERT(style->HasClipPath() || style->HasMask());
+  if (style->HasMask()) {
+    return Nothing();
+  }
+
+  // TODO(emilio): We should be able to still generate a clip and pass the
+  // opacity down to StackingContextHelper instead.
+  if (frame->StyleEffects()->mOpacity != 1.0) {
+    return Nothing();
+  }
+
+  auto& clipPath = style->mClipPath;
+  if (clipPath.GetType() != StyleShapeSourceType::Shape) {
+    return Nothing();
+  }
+
+  // TODO(emilio): We should be able to also simplify most of the circle() and
+  // ellipse() shapes.
+  auto& shape = clipPath.GetBasicShape();
+  if (shape->GetShapeType() != StyleBasicShapeType::Inset) {
+    return Nothing();
+  }
+
+  const nsRect refBox =
+    nsLayoutUtils::ComputeGeometryBox(frame, clipPath.GetReferenceBox());
+
+  const nsRect insetRect =
+    ShapeUtils::ComputeInsetRect(shape, refBox) + aDisplayItem.ToReferenceFrame();
+
+  auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+
+  nscoord radii[8] = { 0 };
+  AutoTArray<wr::ComplexClipRegion, 1> clipRegions;
+  if (ShapeUtils::ComputeInsetRadii(shape, insetRect, refBox, radii)) {
+    clipRegions.AppendElement(wr::ToComplexClipRegion(
+      insetRect, radii, appUnitsPerDevPixel));
+  }
+
+  auto rect = wr::ToRoundedLayoutRect(
+    LayoutDeviceRect::FromAppUnits(insetRect, appUnitsPerDevPixel));
+  wr::WrClipId clipId =
+    aBuilder.DefineClip(Nothing(), rect, &clipRegions, nullptr);
+  return Some(clipId);
+}
+
+static Maybe<wr::WrClipId>
+CreateWRClipPathAndMasks(nsDisplayMasksAndClipPaths* aDisplayItem,
+                         const LayoutDeviceRect& aBounds,
+                         wr::IpcResourceUpdateQueue& aResources,
+                         wr::DisplayListBuilder& aBuilder,
+                         const StackingContextHelper& aSc,
+                         layers::WebRenderLayerManager* aManager,
+                         nsDisplayListBuilder* aDisplayListBuilder)
+{
+  if (auto clip = CreateSimpleClipRegion(*aDisplayItem, aBuilder)) {
+    return clip;
+  }
+
+  Maybe<wr::WrImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage(
+    aDisplayItem, aBuilder, aResources, aSc, aDisplayListBuilder, aBounds);
+  if (!mask) {
+    return Nothing();
+  }
+
+  wr::WrClipId clipId =
+    aBuilder.DefineClip(Nothing(),
+                        wr::ToRoundedLayoutRect(aBounds),
+                        nullptr,
+                        mask.ptr());
+
+  return Some(clipId);
+}
 
 bool
 nsDisplayMasksAndClipPaths::CreateWebRenderCommands(
   mozilla::wr::DisplayListBuilder& aBuilder,
   mozilla::wr::IpcResourceUpdateQueue& aResources,
   const StackingContextHelper& aSc,
   mozilla::layers::WebRenderLayerManager* aManager,
   nsDisplayListBuilder* aDisplayListBuilder)
 {
   bool snap;
-  float appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+  auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
   nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap);
   LayoutDeviceRect bounds =
     LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel);
 
-  Maybe<wr::WrImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage(
-    this, aBuilder, aResources, aSc, aDisplayListBuilder, bounds);
+  Maybe<wr::WrClipId> clip =
+    CreateWRClipPathAndMasks(
+      this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder);
+
   Maybe<StackingContextHelper> layer;
   const StackingContextHelper* sc = &aSc;
-  if (mask) {
-    auto layoutBounds = wr::ToRoundedLayoutRect(bounds);
-    wr::WrClipId clipId =
-      aBuilder.DefineClip(Nothing(), layoutBounds, nullptr, mask.ptr());
-
+  if (clip) {
     // Create a new stacking context to attach the mask to, ensuring the mask is
     // applied to the aggregate, and not the individual elements.
 
     // The stacking context shouldn't have any offset.
     bounds.MoveTo(0, 0);
 
     layer.emplace(aSc,
                   aBuilder,
@@ -10001,28 +10078,28 @@ nsDisplayMasksAndClipPaths::CreateWebRen
                   /*aAnimation: */ nullptr,
                   /*aOpacity: */ nullptr,
                   /*aTransform: */ nullptr,
                   /*aPerspective: */ nullptr,
                   /*aMixBlendMode: */ gfx::CompositionOp::OP_OVER,
                   /*aBackfaceVisible: */ true,
                   /*aIsPreserve3D: */ false,
                   /*aTransformForScrollData: */ Nothing(),
-                  /*aClipNodeId: */ &clipId);
+                  /*aClipNodeId: */ clip.ptr());
     sc = layer.ptr();
     // The whole stacking context will be clipped by us, so no need to have any
     // parent for the children context's clip.
     aManager->CommandBuilder().PushOverrideForASR(GetActiveScrolledRoot(),
                                                   Nothing());
   }
 
   nsDisplayEffectsBase::CreateWebRenderCommands(
     aBuilder, aResources, *sc, aManager, aDisplayListBuilder);
 
-  if (mask) {
+  if (clip) {
     aManager->CommandBuilder().PopOverrideForASR(GetActiveScrolledRoot());
   }
 
   return true;
 }
 
 Maybe<nsRect>
 nsDisplayMasksAndClipPaths::GetClipWithRespectToASR(
--- a/layout/reftests/svg/svg-integration/clip-path/reftest.list
+++ b/layout/reftests/svg/svg-integration/clip-path/reftest.list
@@ -46,16 +46,16 @@ fuzzy-if(webrender,64-64,1106-1106) == c
 fuzzy-if(webrender,64-64,1106-1106) == clip-path-ellipse-006.html clip-path-ellipse-001-ref.html
 fuzzy-if(webrender,64-64,1106-1106) == clip-path-ellipse-007.html clip-path-ellipse-001-ref.html
 fuzzy-if(webrender,64-64,1106-1106) == clip-path-ellipse-008.html clip-path-ellipse-001-ref.html
 
 == clip-path-inset-001a.html clip-path-inset-001-ref.html
 == clip-path-inset-001b.html clip-path-inset-001-ref.html
 == clip-path-inset-001c.html clip-path-inset-001-ref.html
 # Anti-aliasing behavior for masking and borders is different
-fuzzy(0-64,0-146) fuzzy-if(webrender,90-90,132-132) == clip-path-inset-002a.html clip-path-inset-002-ref.html
-fuzzy(0-64,0-146) fuzzy-if(webrender,90-90,132-132) == clip-path-inset-002b.html clip-path-inset-002-ref.html
-fuzzy(0-64,0-146) fuzzy-if(webrender,90-90,132-132) == clip-path-inset-002c.html clip-path-inset-002-ref.html
+fuzzy-if(!webrender,0-64,0-146) == clip-path-inset-002a.html clip-path-inset-002-ref.html
+fuzzy-if(!webrender,0-64,0-146) == clip-path-inset-002b.html clip-path-inset-002-ref.html
+fuzzy-if(!webrender,0-64,0-146) == clip-path-inset-002c.html clip-path-inset-002-ref.html
 fuzzy(0-64,0-340) fuzzy-if(webrender,104-104,311-311) == clip-path-inset-003.html clip-path-inset-003-ref.html
 
 == clip-path-stroke-001.html clip-path-stroke-001-ref.html
 
 == clip-path-transform-001.html clip-path-transform-001-ref.html
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -176,17 +176,17 @@ nsSVGIntegrationUtils::UsingEffectsForFr
   const nsStyleSVGReset *style = aFrame->StyleSVGReset();
   return aFrame->StyleEffects()->HasFilters() ||
          style->HasClipPath() || style->HasMask();
 }
 
 bool
 nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame)
 {
-  const nsStyleSVGReset *style = aFrame->StyleSVGReset();
+  const nsStyleSVGReset* style = aFrame->StyleSVGReset();
   return style->HasClipPath() || style->HasMask();
 }
 
 nsPoint
 nsSVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame)
 {
   if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
     // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the