Bug 1080688 - Calculate SVG rect bounds using a simple rect transform rather than using a Moz2D Path. r=longsonr
authorJonathan Watt <jwatt@jwatt.org>
Sun, 26 Oct 2014 18:00:03 +0000
changeset 212403 f51420708a03f66574dc1abaedbab896ce13da5f
parent 212402 bd6397445f62f2b5ed25f13016f0feffcca296ee
child 212404 b339acb1f7fe74424fb5f09b16f3cfc7f3234dc4
push id27710
push usercbook@mozilla.com
push dateMon, 27 Oct 2014 14:52:01 +0000
treeherdermozilla-central@d65d20dc0ac2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslongsonr
bugs1080688
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1080688 - Calculate SVG rect bounds using a simple rect transform rather than using a Moz2D Path. r=longsonr
dom/svg/SVGRectElement.cpp
dom/svg/SVGRectElement.h
dom/svg/nsSVGPathGeometryElement.h
layout/style/nsStyleStruct.h
layout/svg/nsSVGPathGeometryFrame.cpp
--- a/dom/svg/SVGRectElement.cpp
+++ b/dom/svg/SVGRectElement.cpp
@@ -2,16 +2,18 @@
 /* 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/. */
 
 #include "mozilla/dom/SVGRectElement.h"
 #include "nsGkAtoms.h"
 #include "mozilla/dom/SVGRectElementBinding.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Rect.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include <algorithm>
 
 NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Rect)
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
@@ -103,16 +105,46 @@ SVGRectElement::GetLengthInfo()
 {
   return LengthAttributesInfo(mLengthAttributes, sLengthInfo,
                               ArrayLength(sLengthInfo));
 }
 
 //----------------------------------------------------------------------
 // nsSVGPathGeometryElement methods
 
+bool
+SVGRectElement::GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
+                                  const Matrix& aTransform)
+{
+  Rect r;
+  Float rx, ry;
+  GetAnimatedLengthValues(&r.x, &r.y, &r.width, &r.height, &rx, &ry, nullptr);
+
+  if (r.IsEmpty()) {
+    // Rendering of the element disabled
+    r.SetEmpty(); // make sure width/height are actually zero
+    *aBounds = r;
+    return true;
+  }
+
+  rx = std::max(rx, 0.0f);
+  ry = std::max(ry, 0.0f);
+
+  if (rx != 0 || ry != 0) {
+    return false;
+  }
+
+  if (aStrokeWidth > 0.f) {
+    r.Inflate(aStrokeWidth / 2.f);
+  }
+
+  *aBounds = aTransform.TransformBounds(r);
+  return true;
+}
+
 void
 SVGRectElement::GetAsSimplePath(SimplePath* aSimplePath)
 {
   float x, y, width, height, rx, ry;
   GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
 
   if (width <= 0 || height <= 0) {
     aSimplePath->Reset();
--- a/dom/svg/SVGRectElement.h
+++ b/dom/svg/SVGRectElement.h
@@ -25,16 +25,18 @@ protected:
   friend nsresult (::NS_NewSVGRectElement(nsIContent **aResult,
                                           already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
 public:
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
+  virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
+                                 const Matrix& aTransform) MOZ_OVERRIDE;
   virtual void GetAsSimplePath(SimplePath* aSimplePath) MOZ_OVERRIDE;
   virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
   // WebIDL
   already_AddRefed<SVGAnimatedLength> X();
   already_AddRefed<SVGAnimatedLength> Y();
--- a/dom/svg/nsSVGPathGeometryElement.h
+++ b/dom/svg/nsSVGPathGeometryElement.h
@@ -29,16 +29,17 @@ struct nsSVGMark {
 typedef mozilla::dom::SVGGraphicsElement nsSVGPathGeometryElementBase;
 
 class nsSVGPathGeometryElement : public nsSVGPathGeometryElementBase
 {
 protected:
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::FillRule FillRule;
   typedef mozilla::gfx::Float Float;
+  typedef mozilla::gfx::Matrix Matrix;
   typedef mozilla::gfx::Path Path;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::PathBuilder PathBuilder;
   typedef mozilla::gfx::Rect Rect;
 
 public:
   explicit nsSVGPathGeometryElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
@@ -64,16 +65,21 @@ public:
    * This could be moved up to a more general class so it can be used for non-leaf
    * elements, but that would require care and for now there's no need.
    */
   bool GeometryDependsOnCoordCtx();
 
   virtual bool IsMarkable();
   virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks);
 
+  virtual bool GetGeometryBounds(Rect* aBounds, Float aStrokeWidth,
+                                 const Matrix& aTransform) {
+    return false;
+  }
+
   /**
    * For use with GetAsSimplePath.
    */
   class SimplePath
   {
   public:
     SimplePath()
       : mType(NONE)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -3035,16 +3035,20 @@ struct nsStyleSVGReset {
     return NS_CombineHint(nsChangeHint_NeedReflow,
                           nsChangeHint_ClearAncestorIntrinsics);
   }
 
   bool HasFilters() const {
     return mFilters.Length() > 0;
   }
 
+  bool HasNonScalingStroke() const {
+    return mVectorEffect == NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE;
+  }
+
   nsStyleClipPath mClipPath;          // [reset]
   nsTArray<nsStyleFilter> mFilters;   // [reset]
   nsCOMPtr<nsIURI> mMask;             // [reset]
   nscolor          mStopColor;        // [reset]
   nscolor          mFloodColor;       // [reset]
   nscolor          mLightingColor;    // [reset]
 
   float            mStopOpacity;      // [reset]
--- a/layout/svg/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -458,123 +458,142 @@ nsSVGPathGeometryFrame::GetBBoxContribut
   if (aToBBoxUserspace.IsSingular()) {
     // XXX ReportToConsole
     return bbox;
   }
 
   nsSVGPathGeometryElement* element =
     static_cast<nsSVGPathGeometryElement*>(mContent);
 
-  RefPtr<DrawTarget> tmpDT;
-#ifdef XP_WIN
-  // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
-  // when whole number results are expected, even in the case of trivial
-  // calculations. To avoid that and meet the expectations of web content we
-  // have to use a CAIRO DrawTarget. The most efficient way to do that is to
-  // wrap the cached cairo_surface_t from ScreenReferenceSurface():
-  nsRefPtr<gfxASurface> refSurf =
-    gfxPlatform::GetPlatform()->ScreenReferenceSurface();
-  tmpDT = gfxPlatform::GetPlatform()->
-    CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
-#else
-  tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-#endif
+  bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
+                 ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
+                  StyleSVG()->mFill.mType != eStyleSVGPaintType_None);
+
+  bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
+                   ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
+                    nsSVGUtils::HasStroke(this));
 
-  FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
-  RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
-  if (!pathInUserSpace) {
-    return bbox;
-  }
-  RefPtr<Path> pathInBBoxSpace;
-  if (aToBBoxUserspace.IsIdentity()) {
-    pathInBBoxSpace = pathInUserSpace;
-  } else {
-    RefPtr<PathBuilder> builder =
-      pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
-    pathInBBoxSpace = builder->Finish();
-    if (!pathInBBoxSpace) {
-      return bbox;
+  bool gotSimpleBounds = false;
+  if (!StyleSVGReset()->HasNonScalingStroke()) {
+    Float strokeWidth = getStroke ? nsSVGUtils::GetStrokeWidth(this) : 0.f;
+    Rect simpleBounds;
+    gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeWidth,
+                                                 aToBBoxUserspace);
+    if (gotSimpleBounds) {
+      bbox = simpleBounds;
     }
   }
 
-  // Be careful when replacing the following logic to get the fill and stroke
-  // extents independently (instead of computing the stroke extents from the
-  // path extents). You may think that you can just use the stroke extents if
-  // there is both a fill and a stroke. In reality it's necessary to calculate
-  // both the fill and stroke extents, and take the union of the two. There are
-  // two reasons for this:
-  //
-  // # Due to stroke dashing, in certain cases the fill extents could actually
-  //   extend outside the stroke extents.
-  // # If the stroke is very thin, cairo won't paint any stroke, and so the
-  //   stroke bounds that it will return will be empty.
+  if (!gotSimpleBounds) {
+    // Get the bounds using a Moz2D Path object (more expensive):
+    RefPtr<DrawTarget> tmpDT;
+#ifdef XP_WIN
+    // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
+    // when whole number results are expected, even in the case of trivial
+    // calculations. To avoid that and meet the expectations of web content we
+    // have to use a CAIRO DrawTarget. The most efficient way to do that is to
+    // wrap the cached cairo_surface_t from ScreenReferenceSurface():
+    nsRefPtr<gfxASurface> refSurf =
+      gfxPlatform::GetPlatform()->ScreenReferenceSurface();
+    tmpDT = gfxPlatform::GetPlatform()->
+      CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
+#else
+    tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+#endif
 
-  Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
-  if (!pathBBoxExtents.IsFinite()) {
-    // This can happen in the case that we only have a move-to command in the
-    // path commands, in which case we know nothing gets rendered.
-    return bbox;
-  }
+    FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
+    RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
+    if (!pathInUserSpace) {
+      return bbox;
+    }
+    RefPtr<Path> pathInBBoxSpace;
+    if (aToBBoxUserspace.IsIdentity()) {
+      pathInBBoxSpace = pathInUserSpace;
+    } else {
+      RefPtr<PathBuilder> builder =
+        pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
+      pathInBBoxSpace = builder->Finish();
+      if (!pathInBBoxSpace) {
+        return bbox;
+      }
+    }
 
-  // Account for fill:
-  if ((aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
-      ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
-       StyleSVG()->mFill.mType != eStyleSVGPaintType_None)) {
-    bbox = pathBBoxExtents;
-  }
+    // Be careful when replacing the following logic to get the fill and stroke
+    // extents independently (instead of computing the stroke extents from the
+    // path extents). You may think that you can just use the stroke extents if
+    // there is both a fill and a stroke. In reality it's necessary to
+    // calculate both the fill and stroke extents, and take the union of the
+    // two. There are two reasons for this:
+    //
+    // # Due to stroke dashing, in certain cases the fill extents could
+    //   actually extend outside the stroke extents.
+    // # If the stroke is very thin, cairo won't paint any stroke, and so the
+    //   stroke bounds that it will return will be empty.
 
-  // Account for stroke:
-  if ((aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
-      ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
-       nsSVGUtils::HasStroke(this))) {
+    Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
+    if (!pathBBoxExtents.IsFinite()) {
+      // This can happen in the case that we only have a move-to command in the
+      // path commands, in which case we know nothing gets rendered.
+      return bbox;
+    }
+
+    // Account for fill:
+    if (getFill) {
+      bbox = pathBBoxExtents;
+    }
+
+    // Account for stroke:
+    if (getStroke) {
 #if 0
-    // This disabled code is how we would calculate the stroke bounds using
-    // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing it
-    // there are two problems that prevent us from using it.
-    //
-    // First, it seems that some of the Moz2D backends are really dumb. Not
-    // only do some GetStrokeOptions() implementations sometimes significantly
-    // overestimate the stroke bounds, but if an argument is passed for the
-    // aTransform parameter then they just return bounds-of-transformed-bounds.
-    // These two things combined can lead the bounds to be unacceptably
-    // oversized, leading to massive over-invalidation.
-    //
-    // Second, the way we account for non-scaling-stroke by transforming the
-    // path using the transform to the outer-<svg> element is not compatible
-    // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale into
-    // aToBBoxUserspace and then scales the bounds that we return.
-    SVGContentUtils::AutoStrokeOptions strokeOptions;
-    SVGContentUtils::GetStrokeOptions(&strokeOptions, element, StyleContext(),
-                                      nullptr, SVGContentUtils::eIgnoreStrokeDashing);
-    Rect strokeBBoxExtents;
-    gfxMatrix userToOuterSVG;
-    if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
-      Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
-      outerSVGToUser.Invert();
-      Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
-      RefPtr<PathBuilder> builder =
-        pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
-      RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
-      strokeBBoxExtents =
-        pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
-    } else {
-      strokeBBoxExtents =
-        pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
-    }
-    MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
-    bbox.UnionEdges(strokeBBoxExtents);
+      // This disabled code is how we would calculate the stroke bounds using
+      // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
+      // it there are two problems that prevent us from using it.
+      //
+      // First, it seems that some of the Moz2D backends are really dumb. Not
+      // only do some GetStrokeOptions() implementations sometimes
+      // significantly overestimate the stroke bounds, but if an argument is
+      // passed for the aTransform parameter then they just return bounds-of-
+      // transformed-bounds.  These two things combined can lead the bounds to
+      // be unacceptably oversized, leading to massive over-invalidation.
+      //
+      // Second, the way we account for non-scaling-stroke by transforming the
+      // path using the transform to the outer-<svg> element is not compatible
+      // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale
+      // into aToBBoxUserspace and then scales the bounds that we return.
+      SVGContentUtils::AutoStrokeOptions strokeOptions;
+      SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
+                                        StyleContext(), nullptr,
+                                        SVGContentUtils::eIgnoreStrokeDashing);
+      Rect strokeBBoxExtents;
+      gfxMatrix userToOuterSVG;
+      if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+        Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
+        outerSVGToUser.Invert();
+        Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
+        RefPtr<PathBuilder> builder =
+          pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
+        RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
+        strokeBBoxExtents =
+          pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
+      } else {
+        strokeBBoxExtents =
+          pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
+      }
+      MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
+      bbox.UnionEdges(strokeBBoxExtents);
 #else
     // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
-    gfxRect strokeBBoxExtents =
-      nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
-                                                this,
-                                                ThebesMatrix(aToBBoxUserspace));
-    MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
-    bbox.UnionEdges(strokeBBoxExtents);
+      gfxRect strokeBBoxExtents =
+        nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
+                                                  this,
+                                                  ThebesMatrix(aToBBoxUserspace));
+      MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
+      bbox.UnionEdges(strokeBBoxExtents);
 #endif
+    }
   }
 
   // Account for markers:
   if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
       static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
 
     float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
     MarkerProperties properties = GetMarkerProperties(this);