Bug 772726. Part 7: Avoid specifying "repeat" mode when rendering CSS gradients if we don't need it. r=padenot
authorRobert O'Callahan <robert@ocallahan.org>
Sat, 08 Sep 2012 00:32:21 +1200
changeset 107683 6ffdab09eff621f3bc33521e65c36be4af213dcc
parent 107682 23ee0b1ce2507092498c3c32c5367f279d1f5cae
child 107684 5daf1f385a84739da287334ae04a6f9b2acf891d
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerspadenot
bugs772726
milestone18.0a1
Bug 772726. Part 7: Avoid specifying "repeat" mode when rendering CSS gradients if we don't need it. r=padenot D2D repeating gradients rasterize slightly differently to non-repeating gradients even when the rendered area falls within a single tile, so a repeating gradient compared to a non-repeating gradient (say one drawn in a canvas) can cause reftest failures.
layout/base/nsCSSRendering.cpp
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -275,52 +275,59 @@ protected:
 };
 
 struct GradientCacheKey : public PLDHashEntryHdr {
   typedef const GradientCacheKey& KeyType;
   typedef const GradientCacheKey* KeyTypePointer;
   enum { ALLOW_MEMMOVE = true };
   const nsRefPtr<nsStyleGradient> mGradient;
   const gfxSize mGradientSize;
-
-  GradientCacheKey(nsStyleGradient* aGradient, const gfxSize& aGradientSize)
-    : mGradient(aGradient), mGradientSize(aGradientSize)
+  enum { SINGLE_CELL = 0x01 };
+  const uint32_t mFlags;
+
+  GradientCacheKey(nsStyleGradient* aGradient, const gfxSize& aGradientSize,
+                   uint32_t aFlags)
+    : mGradient(aGradient), mGradientSize(aGradientSize), mFlags(aFlags)
   { }
 
   GradientCacheKey(const GradientCacheKey* aOther)
-    : mGradient(aOther->mGradient), mGradientSize(aOther->mGradientSize)
+    : mGradient(aOther->mGradient), mGradientSize(aOther->mGradientSize),
+      mFlags(aOther->mFlags)
   { }
 
   static PLDHashNumber
   HashKey(const KeyTypePointer aKey)
   {
     PLDHashNumber hash = 0;
     hash = AddToHash(hash, aKey->mGradientSize.width);
     hash = AddToHash(hash, aKey->mGradientSize.height);
+    hash = AddToHash(hash, aKey->mFlags);
     hash = aKey->mGradient->Hash(hash);
     return hash;
   }
 
   bool KeyEquals(KeyTypePointer aKey) const
   {
     return (*aKey->mGradient == *mGradient) &&
-           (aKey->mGradientSize == mGradientSize);
+           (aKey->mGradientSize == mGradientSize) &&
+           (aKey->mFlags == mFlags);
   }
   static KeyTypePointer KeyToPointer(KeyType aKey)
   {
     return &aKey;
   }
 };
 
 /**
  * This class is what is cached. It need to be allocated in an object separated
  * to the cache entry to be able to be tracked by the nsExpirationTracker.
  * */
 struct GradientCacheData {
-  GradientCacheData(gfxPattern* aPattern, bool aCoversTile, GradientCacheKey aKey)
+  GradientCacheData(gfxPattern* aPattern, bool aCoversTile,
+                    const GradientCacheKey& aKey)
     : mPattern(aPattern), mCoversTile(aCoversTile), mKey(aKey)
   {}
 
   GradientCacheData(const GradientCacheData& aOther)
     : mPattern(aOther.mPattern),
       mCoversTile(aOther.mCoversTile),
       mKey(aOther.mKey)
   { }
@@ -363,54 +370,56 @@ class GradientCache MOZ_FINAL : public n
 
     virtual void NotifyExpired(GradientCacheData* aObject)
     {
       // This will free the gfxPattern.
       RemoveObject(aObject);
       mHashEntries.Remove(aObject->mKey);
     }
 
-    GradientCacheData* Lookup(nsStyleGradient* aKey, const gfxSize& aGradientSize)
+    GradientCacheData* Lookup(nsStyleGradient* aKey, const gfxSize& aGradientSize,
+                              uint32_t aFlags)
     {
       // We don't cache gradient that have Calc value, because the Calc object
       // can be deallocated by the time we want to compute the hash, and thus we
       // would have a dangling pointer in some nsStyleCoord in the
       // nsStyleGradient that are in the hash table.
       if (aKey->HasCalc()) {
         return nullptr;
       }
 
-      GradientCacheData* gradient = mHashEntries.Get(GradientCacheKey(aKey, aGradientSize));
+      GradientCacheData* gradient =
+        mHashEntries.Get(GradientCacheKey(aKey, aGradientSize, aFlags));
 
       if (gradient) {
         MarkUsed(gradient);
       }
 
       return gradient;
     }
 
     // Returns true if we successfully register the gradient in the cache, false
     // otherwise.
-    bool RegisterEntry(nsStyleGradient* aKey, const gfxSize& aGradientSize, GradientCacheData* aValue)
+    bool RegisterEntry(GradientCacheData* aValue)
     {
       // We don't cache gradient that have Calc values (see
       // GradientCache::Lookup).
-      if (aKey->HasCalc()) {
+      if (aValue->mKey.mGradient->HasCalc()) {
         return false;
       }
       nsresult rv = AddObject(aValue);
       if (NS_FAILED(rv)) {
         // We are OOM, and we cannot track this object. We don't want stall
         // entries in the hash table (since the expiration tracker is responsible
         // for removing the cache entries), so we avoid putting that entry in the
         // table, which is a good things considering we are short on memory
         // anyway, we probably don't want to retain things.
         return false;
       }
-      mHashEntries.Put(GradientCacheKey(aKey, aGradientSize), aValue);
+      mHashEntries.Put(aValue->mKey, aValue);
       return true;
     }
 
   protected:
     uint32_t mTimerPeriod;
     static const uint32_t MAX_GENERATION_MS = 10000;
     /**
      * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
@@ -2006,19 +2015,23 @@ nsCSSRendering::PaintGradient(nsPresCont
   Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
   if (aOneCellArea.IsEmpty())
     return;
 
   gfxContext *ctx = aRenderingContext.ThebesContext();
   nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
   gfxRect oneCellArea =
     nsLayoutUtils::RectToGfxRect(aOneCellArea, appUnitsPerPixel);
-
   bool gradientRegistered = true;
-  GradientCacheData* pattern = gGradientCache->Lookup(aGradient, oneCellArea.Size());
+  uint32_t flags = 0;
+  if (aOneCellArea.Contains(aFillArea)) {
+    flags |= GradientCacheKey::SINGLE_CELL;
+  }
+  GradientCacheData* pattern =
+    gGradientCache->Lookup(aGradient, oneCellArea.Size(), flags);
 
   if (pattern == nullptr) {
     // Compute "gradient line" start and end relative to oneCellArea
     gfxPoint lineStart, lineEnd;
     double radiusX = 0, radiusY = 0; // for radial gradients only
     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
       ComputeLinearGradientLine(aPresContext, aGradient, oneCellArea.Size(),
                                 &lineStart, &lineEnd);
@@ -2188,20 +2201,21 @@ nsCSSRendering::PaintGradient(nsPresCont
       }
 
       gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
                                        gradientEnd.x, gradientEnd.y);
 
       // When the gradient line is parallel to the x axis from the left edge
       // to the right edge of a tile, then we can repeat by just repeating the
       // gradient.
-      if ((gradientStart.y == gradientEnd.y && gradientStart.x == 0 &&
-           gradientEnd.x == oneCellArea.width) ||
-          (gradientStart.x == gradientEnd.x && gradientStart.y == 0 &&
-           gradientEnd.y == oneCellArea.height)) {
+      if (!(flags & GradientCacheKey::SINGLE_CELL) &&
+          ((gradientStart.y == gradientEnd.y && gradientStart.x == 0 &&
+            gradientEnd.x == oneCellArea.width) ||
+           (gradientStart.x == gradientEnd.x && gradientStart.y == 0 &&
+            gradientEnd.y == oneCellArea.height))) {
         forceRepeatToCoverTiles = true;
       }
     } else {
       NS_ASSERTION(firstStop >= 0.0,
                    "Negative stops not allowed for radial gradients");
 
       // To form an ellipse, we'll stretch a circle vertically, if necessary.
       // So our radii are based on radiusX.
@@ -2248,18 +2262,19 @@ nsCSSRendering::PaintGradient(nsPresCont
       }
     }
 
     // Set repeat mode. Default cairo extend mode is PAD.
     if (aGradient->mRepeating || forceRepeatToCoverTiles) {
       gradientPattern->SetExtend(gfxPattern::EXTEND_REPEAT);
     }
     // Register the gradient newly computed in the cache.
-    pattern = new GradientCacheData(gradientPattern, forceRepeatToCoverTiles, GradientCacheKey(aGradient, oneCellArea.Size()));
-    gradientRegistered = gGradientCache->RegisterEntry(aGradient, oneCellArea.Size(), pattern);
+    pattern = new GradientCacheData(gradientPattern, forceRepeatToCoverTiles,
+      GradientCacheKey(aGradient, oneCellArea.Size(), flags));
+    gradientRegistered = gGradientCache->RegisterEntry(pattern);
   }
 
   // Paint gradient tiles. This isn't terribly efficient, but doing it this
   // way is simple and sure to get pixel-snapping right. We could speed things
   // up by drawing tiles into temporary surfaces and copying those to the
   // destination, but after pixel-snapping tiles may not all be the same size.
   nsRect dirty;
   if (!dirty.IntersectRect(aDirtyRect, aFillArea))