Bug 1340317 - Support box shadow outer offset and spread radius in WebRender box shadows. r=lsalzman
authorMason Chang <mchang@mozilla.com>
Thu, 16 Feb 2017 15:05:34 -0800
changeset 373502 f0b3007831a55d93e35f421be931bcb15c9b1ef2
parent 373031 e4e9dcee968361244f840c6ef64b035e33e5855b
child 373503 18f136bf78f86a6bf3b872636ce255fb863a83cc
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman
bugs1340317
milestone54.0a1
Bug 1340317 - Support box shadow outer offset and spread radius in WebRender box shadows. r=lsalzman
gfx/webrender_bindings/WebRenderTypes.h
gfx/webrender_bindings/webrender_ffi.h
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRendering.h
layout/painting/nsDisplayList.cpp
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -222,16 +222,24 @@ static inline WrRect ToWrRect(const gfx:
 }
 
 template<class T>
 static inline WrRect ToWrRect(const gfx::IntRectTyped<T>& rect)
 {
   return ToWrRect(IntRectToRect(rect));
 }
 
+static inline WrPoint ToWrPoint(const gfx::Point& point)
+{
+  WrPoint p;
+  p.x = point.x;
+  p.y = point.y;
+  return p;
+}
+
 struct ByteBuffer
 {
   ByteBuffer(size_t aLength, uint8_t* aData)
     : mLength(aLength)
     , mData(aData)
     , mOwned(false)
   {}
 
--- a/gfx/webrender_bindings/webrender_ffi.h
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -278,16 +278,17 @@ struct WrPoint
 {
   float x;
   float y;
 
   bool operator==(const WrPoint& aRhs) const {
     return x == aRhs.x && y == aRhs.y;
   }
 
+  operator mozilla::gfx::Point() const { return mozilla::gfx::Point(x, y); }
 };
 
 struct WrImageMask
 {
   WrImageKey image;
   WrRect rect;
   bool repeat;
 
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -1364,49 +1364,85 @@ nsCSSRendering::EndFrameTreesLocked()
 {
   NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
   --gFrameTreeLockCount;
   if (gFrameTreeLockCount == 0) {
     gInlineBGData->Reset();
   }
 }
 
+bool
+nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame,
+                                        bool& aMaybeHasBorderRadius)
+{
+  const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
+  nsITheme::Transparency transparency;
+  if (aFrame->IsThemed(styleDisplay, &transparency)) {
+    aMaybeHasBorderRadius = false;
+    // For opaque (rectangular) theme widgets we can take the generic
+    // border-box path with border-radius disabled.
+    return transparency != nsITheme::eOpaque;
+  }
+
+  aMaybeHasBorderRadius = true;
+  return false;
+}
+
+gfx::Color
+nsCSSRendering::GetShadowColor(nsCSSShadowItem* aShadow,
+                               nsIFrame* aFrame,
+                               float aOpacity)
+{
+  // Get the shadow color; if not specified, use the foreground color
+  nscolor shadowColor;
+  if (aShadow->mHasColor)
+    shadowColor = aShadow->mColor;
+  else
+    shadowColor = aFrame->StyleColor()->mColor;
+
+  Color color = Color::FromABGR(shadowColor);
+  color.a *= aOpacity;
+  return color;
+}
+
+nsRect
+nsCSSRendering::GetShadowRect(const nsRect aFrameArea,
+                              bool aNativeTheme,
+                              nsIFrame* aForFrame)
+{
+  nsRect frameRect = aNativeTheme ?
+    aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() :
+    aFrameArea;
+  Sides skipSides = aForFrame->GetSkipSides();
+  frameRect = ::BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
+
+  // Explicitly do not need to account for the spread radius here
+  // Webrender does it for us or PaintBoxShadow will for non-WR
+  return frameRect;
+}
+
 void
 nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
                                     nsRenderingContext& aRenderingContext,
                                     nsIFrame* aForFrame,
                                     const nsRect& aFrameArea,
                                     const nsRect& aDirtyRect,
                                     float aOpacity)
 {
   DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
   nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
   if (!shadows)
     return;
 
   bool hasBorderRadius;
-  bool nativeTheme; // mutually exclusive with hasBorderRadius
+  // mutually exclusive with hasBorderRadius
+  bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
   const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
-  nsITheme::Transparency transparency;
-  if (aForFrame->IsThemed(styleDisplay, &transparency)) {
-    // We don't respect border-radius for native-themed widgets
-    hasBorderRadius = false;
-    // For opaque (rectangular) theme widgets we can take the generic
-    // border-box path with border-radius disabled.
-    nativeTheme = transparency != nsITheme::eOpaque;
-  } else {
-    nativeTheme = false;
-    hasBorderRadius = true; // we'll update this below
-  }
-
-  nsRect frameRect = nativeTheme ?
-    aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() :
-    aFrameArea;
-  Sides skipSides = aForFrame->GetSkipSides();
-  frameRect = ::BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
+
+  nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
 
   // Get any border radius, since box-shadow must also have rounded corners if
   // the frame does.
   RectCornerRadii borderRadii;
   const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
   if (hasBorderRadius) {
     nscoord twipsRadii[8];
     NS_ASSERTION(aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
@@ -1460,25 +1496,17 @@ nsCSSRendering::PaintBoxShadowOuter(nsPr
     shadowRectPlusBlur.Inflate(
       nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel));
 
     Rect shadowGfxRectPlusBlur =
       NSRectToRect(shadowRectPlusBlur, twipsPerPixel);
     shadowGfxRectPlusBlur.RoundOut();
     MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
 
-    // Set the shadow color; if not specified, use the foreground color
-    nscolor shadowColor;
-    if (shadowItem->mHasColor)
-      shadowColor = shadowItem->mColor;
-    else
-      shadowColor = aForFrame->StyleColor()->mColor;
-
-    Color gfxShadowColor(Color::FromABGR(shadowColor));
-    gfxShadowColor.a *= aOpacity;
+    Color gfxShadowColor = GetShadowColor(shadowItem, aForFrame, aOpacity);
 
     if (nativeTheme) {
       nsContextBoxBlur blurringArea;
 
       // When getting the widget shape from the native theme, we're going
       // to draw the widget into the shadow surface to create a mask.
       // We need to ensure that there actually *is* a shadow surface
       // and that we're not going to draw directly into renderContext.
@@ -1542,16 +1570,17 @@ nsCSSRendering::PaintBoxShadowOuter(nsPr
           AppendRectToPath(builder, innerClipRect);
         }
         RefPtr<Path> path = builder->Finish();
         renderContext->Clip(path);
       }
 
       // Clip the shadow so that we only get the part that applies to aForFrame.
       nsRect fragmentClip = shadowRectPlusBlur;
+      Sides skipSides = aForFrame->GetSkipSides();
       if (!skipSides.IsEmpty()) {
         if (skipSides.Left()) {
           nscoord xmost = fragmentClip.XMost();
           fragmentClip.x = aFrameArea.x;
           fragmentClip.width = xmost - fragmentClip.x;
         }
         if (skipSides.Right()) {
           nscoord xmost = fragmentClip.XMost();
@@ -1735,21 +1764,17 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
     DrawTarget* drawTarget = renderContext->GetDrawTarget();
 
     // Clip the context to the area of the frame's padding rect, so no part of the
     // shadow is painted outside. Also cut out anything beyond where the inset shadow
     // will be.
     Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
     shadowGfxRect.Round();
 
-    // Set the shadow color; if not specified, use the foreground color
-    Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
-                                          shadowItem->mColor :
-                                          aForFrame->StyleColor()->mColor);
-
+    Color shadowColor = GetShadowColor(shadowItem, aForFrame, 1.0);
     renderContext->Save();
 
     // This clips the outside border radius.
     // clipRectRadii is the border radius inside the inset shadow.
     if (hasBorderRadius) {
       RefPtr<Path> roundedRect =
         MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
       renderContext->Clip(roundedRect);
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -349,16 +349,17 @@ struct nsBackgroundLayerState {
   /**
    * The background-repeat property space keyword computes the
    * repeat size which is image size plus spacing.
    */
   nsSize mRepeatSize;
 };
 
 struct nsCSSRendering {
+  typedef mozilla::gfx::Color Color;
   typedef mozilla::gfx::CompositionOp CompositionOp;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Float Float;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::Rect Rect;
   typedef mozilla::gfx::Size Size;
   typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
   typedef mozilla::image::DrawResult DrawResult;
@@ -374,16 +375,27 @@ struct nsCSSRendering {
    */
   static void Shutdown();
   
   static void PaintBoxShadowInner(nsPresContext* aPresContext,
                                   nsRenderingContext& aRenderingContext,
                                   nsIFrame* aForFrame,
                                   const nsRect& aFrameArea);
 
+  static nsRect GetShadowRect(const nsRect aFrameArea,
+                              bool aNativeTheme,
+                              nsIFrame* aForFrame);
+  static mozilla::gfx::Color GetShadowColor(nsCSSShadowItem* aShadow,
+                                   nsIFrame* aFrame,
+                                   float aOpacity);
+  // Returns if the frame has a themed frame.
+  // aMaybeHasBorderRadius will return false if we can early detect
+  // that we don't have a border radius.
+  static bool HasBoxShadowNativeTheme(nsIFrame* aFrame,
+                                      bool& aMaybeHasBorderRadius);
   static void PaintBoxShadowOuter(nsPresContext* aPresContext,
                                   nsRenderingContext& aRenderingContext,
                                   nsIFrame* aForFrame,
                                   const nsRect& aFrameArea,
                                   const nsRect& aDirtyRect,
                                   float aOpacity = 1.0);
 
   static void ComputePixelRadii(const nscoord *aAppUnitsRadii,
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -4766,55 +4766,64 @@ nsDisplayBoxShadowOuter::CreateWebRender
   nsRegion visible = aLayer->GetVisibleRegion().ToAppUnits(appUnitsPerDevPixel);
 
   ComputeDisjointRectangles(visible, &rects);
 
   nsCSSShadowArray* shadows = mFrame->StyleEffects()->mBoxShadow;
   if (!shadows)
     return;
 
+  bool hasBorderRadius;
+  bool nativeTheme = nsCSSRendering::HasBoxShadowNativeTheme(mFrame,
+                                                             hasBorderRadius);
+
   // Everything here is in app units, change to device units.
   for (uint32_t i = 0; i < rects.Length(); ++i) {
     Rect clipRect = NSRectToRect(rects[i], appUnitsPerDevPixel);
-    Rect gfxBorderRect = NSRectToRect(borderRect, appUnitsPerDevPixel);
-
-    Rect deviceClipRect = aLayer->RelativeToParent(clipRect);
-    Rect deviceBoxRect = aLayer->RelativeToParent(gfxBorderRect);
-
-    for (uint32_t j = shadows->Length(); j > 0; --j) {
-      nsCSSShadowItem* shadowItem = shadows->ShadowAt(j - 1);
-      nscoord blurRadius = shadowItem->mRadius;
-      float gfxBlurRadius = blurRadius / appUnitsPerDevPixel;
-
-      // TODO: Have to refactor our nsCSSRendering
-      // to get the acual rects correct.
-      nscolor shadowColor;
-      if (shadowItem->mHasColor)
-        shadowColor = shadowItem->mColor;
-      else
-        shadowColor = mFrame->StyleColor()->mColor;
-
-      Color gfxShadowColor(Color::FromABGR(shadowColor));
-      gfxShadowColor.a *= mOpacity;
-
-      WrPoint offset;
-      offset.x = shadowItem->mXOffset;
-      offset.y = shadowItem->mYOffset;
+    nsCSSShadowArray* shadows = mFrame->StyleEffects()->mBoxShadow;
+
+    for (uint32_t j = shadows->Length(); j  > 0; j--) {
+      nsCSSShadowItem* shadow = shadows->ShadowAt(j - 1);
+      // Don't need the full size of the shadow rect like we do in
+      // nsCSSRendering since WR takes care of calculations for blur
+      // and spread radius.
+      nsRect shadowRect = nsCSSRendering::GetShadowRect(borderRect,
+                                                        nativeTheme,
+                                                        mFrame);
+      gfx::Color shadowColor = nsCSSRendering::GetShadowColor(shadow,
+                                                              mFrame,
+                                                              mOpacity);
+      shadowRect.MoveBy(shadow->mXOffset, shadow->mYOffset);
+
+      // Now translate everything to device pixels.
+      Point shadowOffset;
+      shadowOffset.x = (shadow->mXOffset / appUnitsPerDevPixel);
+      shadowOffset.y = (shadow->mYOffset / appUnitsPerDevPixel);
+
+      Rect deviceBoxRect = NSRectToRect(shadowRect, appUnitsPerDevPixel);
+      deviceBoxRect = aLayer->RelativeToParent(deviceBoxRect);
+
+      Rect deviceClipRect = aLayer->RelativeToParent(clipRect + shadowOffset);
+
+      float blurRadius = shadow->mRadius / appUnitsPerDevPixel;
+      // TODO: Calculate the border radius here.
+      float borderRadius = 0.0;
+      float spreadRadius = shadow->mSpread / appUnitsPerDevPixel;
 
       aCommands.AppendElement(OpDPPushBoxShadow(
                               wr::ToWrRect(deviceBoxRect),
                               wr::ToWrRect(deviceClipRect),
                               wr::ToWrRect(deviceBoxRect),
-                              offset,
-                              wr::ToWrColor(gfxShadowColor),
-                              gfxBlurRadius,
-                              0,
-                              0,
+                              wr::ToWrPoint(shadowOffset),
+                              wr::ToWrColor(shadowColor),
+                              blurRadius,
+                              spreadRadius,
+                              borderRadius,
                               WrBoxShadowClipMode::Outset
-      ));
+                              ));
     }
   }
 }
 
 void
 nsDisplayBoxShadowOuter::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                                    const nsDisplayItemGeometry* aGeometry,
                                                    nsRegion* aInvalidRegion)