Bug 930451 - Part 1: Factor out CSS gradient cache into a new class gfxGradientCache. r=roc, a=
authorAndreas Gal <gal@uci.edu>
Fri, 08 Nov 2013 17:50:39 +1300
changeset 161280 019969972ed0d4f34a2ab5973b0fd2ebcb15ca11
parent 161279 99414b9538100a4221f75a0c61a67cc384320915
child 161281 a3bb77bde73f69a15e2c22904fd263ee158b4bab
push idunknown
push userunknown
push dateunknown
reviewersroc
bugs930451
milestone27.0a2
Bug 930451 - Part 1: Factor out CSS gradient cache into a new class gfxGradientCache. r=roc, a=
gfx/thebes/gfxGradientCache.cpp
gfx/thebes/gfxGradientCache.h
gfx/thebes/gfxPlatform.cpp
gfx/thebes/moz.build
layout/base/nsCSSRendering.cpp
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxGradientCache.cpp
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/gfx/2D.h"
+#include "nsTArray.h"
+#include "pldhash.h"
+#include "nsExpirationTracker.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Telemetry.h"
+#include "gfxGradientCache.h"
+#include <time.h>
+
+namespace mozilla {
+namespace gfx {
+
+using namespace mozilla;
+
+struct GradientCacheKey : public PLDHashEntryHdr {
+  typedef const GradientCacheKey& KeyType;
+  typedef const GradientCacheKey* KeyTypePointer;
+  enum { ALLOW_MEMMOVE = true };
+  const nsTArray<GradientStop> mStops;
+  ExtendMode mExtend;
+  BackendType mBackendType;
+
+  GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, BackendType aBackendType)
+    : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType)
+  { }
+
+  GradientCacheKey(const GradientCacheKey* aOther)
+    : mStops(aOther->mStops), mExtend(aOther->mExtend), mBackendType(aOther->mBackendType)
+  { }
+
+  union FloatUint32
+  {
+    float    f;
+    uint32_t u;
+  };
+
+  static PLDHashNumber
+  HashKey(const KeyTypePointer aKey)
+  {
+    PLDHashNumber hash = 0;
+    FloatUint32 convert;
+    hash = AddToHash(hash, aKey->mBackendType);
+    hash = AddToHash(hash, aKey->mExtend);
+    for (uint32_t i = 0; i < aKey->mStops.Length(); i++) {
+      hash = AddToHash(hash, aKey->mStops[i].color.ToABGR());
+      // Use the float bits as hash, except for the cases of 0.0 and -0.0 which both map to 0
+      convert.f = aKey->mStops[i].offset;
+      hash = AddToHash(hash, convert.f ? convert.u : 0);
+    }
+    return hash;
+  }
+
+  bool KeyEquals(KeyTypePointer aKey) const
+  {
+    bool sameStops = true;
+    if (aKey->mStops.Length() != mStops.Length()) {
+      sameStops = false;
+    } else {
+      for (uint32_t i = 0; i < mStops.Length(); i++) {
+        if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() ||
+            mStops[i].offset != aKey->mStops[i].offset) {
+          sameStops = false;
+          break;
+        }
+      }
+    }
+
+    return sameStops &&
+           (aKey->mBackendType == mBackendType) &&
+           (aKey->mExtend == mExtend);
+  }
+  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(GradientStops* aStops, const GradientCacheKey& aKey)
+    : mStops(aStops),
+      mKey(aKey)
+  {}
+
+  GradientCacheData(const GradientCacheData& aOther)
+    : mStops(aOther.mStops),
+      mKey(aOther.mKey)
+  { }
+
+  nsExpirationState *GetExpirationState() {
+    return &mExpirationState;
+  }
+
+  nsExpirationState mExpirationState;
+  const RefPtr<GradientStops> mStops;
+  GradientCacheKey mKey;
+};
+
+/**
+ * This class implements a cache with no maximum size, that retains the
+ * gfxPatterns used to draw the gradients.
+ *
+ * The key is the nsStyleGradient that defines the gradient, and the size of the
+ * gradient.
+ *
+ * The value is the gfxPattern, and whether or not we perform an optimization
+ * based on the actual gradient property.
+ *
+ * An entry stays in the cache as long as it is used often. As long as a cache
+ * entry is in the cache, all the references it has are guaranteed to be valid:
+ * the nsStyleRect for the key, the gfxPattern for the value.
+ */
+class GradientCache MOZ_FINAL : public nsExpirationTracker<GradientCacheData,4>
+{
+  public:
+    GradientCache()
+      : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS)
+    {
+      srand(time(nullptr));
+      mTimerPeriod = rand() % MAX_GENERATION_MS + 1;
+      Telemetry::Accumulate(Telemetry::GRADIENT_RETENTION_TIME, mTimerPeriod);
+    }
+
+    virtual void NotifyExpired(GradientCacheData* aObject)
+    {
+      // This will free the gfxPattern.
+      RemoveObject(aObject);
+      mHashEntries.Remove(aObject->mKey);
+    }
+
+    GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, BackendType aBackendType)
+    {
+      GradientCacheData* gradient =
+        mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType));
+
+      if (gradient) {
+        MarkUsed(gradient);
+      }
+
+      return gradient;
+    }
+
+    // Returns true if we successfully register the gradient in the cache, false
+    // otherwise.
+    bool RegisterEntry(GradientCacheData* aValue)
+    {
+      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(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.
+     * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
+     */
+    nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
+};
+
+static GradientCache* gGradientCache = nullptr;
+
+GradientStops *
+gfxGradientCache::GetGradientStops(DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend)
+{
+  if (!gGradientCache) {
+    gGradientCache = new GradientCache();
+  }
+  GradientCacheData* cached = gGradientCache->Lookup(aStops, aExtend, aDT->GetType());
+  return cached ? cached->mStops : nullptr;
+}
+
+GradientStops *
+gfxGradientCache::GetOrCreateGradientStops(DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend)
+{
+  RefPtr<GradientStops> gs = GetGradientStops(aDT, aStops, aExtend);
+  if (!gs) {
+    gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
+    if (!gs) {
+      return nullptr;
+    }
+    GradientCacheData *cached = new GradientCacheData(gs, GradientCacheKey(aStops, aExtend, aDT->GetType()));
+    if (!gGradientCache->RegisterEntry(cached)) {
+      delete cached;
+    }
+  }
+  return gs;
+}
+
+void
+gfxGradientCache::Shutdown()
+{
+  delete gGradientCache;
+  gGradientCache = nullptr;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxGradientCache.h
@@ -0,0 +1,32 @@
+
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_GRADIENT_CACHE_H
+#define GFX_GRADIENT_CACHE_H
+
+#include "nsTArray.h"
+#include "gfxPattern.h"
+#include "mozilla/gfx/2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class gfxGradientCache {
+public:
+  static gfx::GradientStops *GetGradientStops(gfx::DrawTarget *aDT,
+					      nsTArray<gfx::GradientStop>& aStops,
+					      gfx::ExtendMode aExtend);
+  static gfx::GradientStops *GetOrCreateGradientStops(gfx::DrawTarget *aDT,
+						      nsTArray<gfx::GradientStop>& aStops,
+						      gfx::ExtendMode aExtend);
+
+  static void Shutdown();
+};
+
+}
+}
+
+#endif
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -36,16 +36,17 @@
 #include "nsGkAtoms.h"
 #include "gfxPlatformFontList.h"
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "nsUnicodeProperties.h"
 #include "harfbuzz/hb.h"
 #include "gfxGraphiteShaper.h"
 #include "gfx2DGlue.h"
+#include "gfxGradientCache.h"
 
 #include "nsUnicodeRange.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTArray.h"
 #include "nsILocaleService.h"
 
 #include "nsWeakReference.h"
 
@@ -450,16 +451,17 @@ gfxPlatform::Init()
 
 void
 gfxPlatform::Shutdown()
 {
     // These may be called before the corresponding subsystems have actually
     // started up. That's OK, they can handle it.
     gfxFontCache::Shutdown();
     gfxFontGroup::Shutdown();
+    gfxGradientCache::Shutdown();
     gfxGraphiteShaper::Shutdown();
 #if defined(XP_MACOSX) || defined(XP_WIN) // temporary, until this is implemented on others
     gfxPlatformFontList::Shutdown();
 #endif
 
     // Free the various non-null transforms and loaded profiles
     ShutdownCMS();
 
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -19,16 +19,17 @@ EXPORTS += [
     'gfxContext.h',
     'gfxDrawable.h',
     'gfxFailure.h',
     'gfxFont.h',
     'gfxFontConstants.h',
     'gfxFontFeatures.h',
     'gfxFontTest.h',
     'gfxFontUtils.h',
+    'gfxGradientCache.h',
     'gfxImageSurface.h',
     'gfxLineSegment.h',
     'gfxMatrix.h',
     'gfxPath.h',
     'gfxPattern.h',
     'gfxPlatform.h',
     'gfxPoint.h',
     'gfxPoint3D.h',
@@ -240,16 +241,17 @@ SOURCES += [
     'gfxCachedTempSurface.cpp',
     'gfxContext.cpp',
     'gfxDrawable.cpp',
     'gfxFont.cpp',
     'gfxFontFeatures.cpp',
     'gfxFontMissingGlyphs.cpp',
     'gfxFontTest.cpp',
     'gfxFontUtils.cpp',
+    'gfxGradientCache.cpp',
     'gfxGraphiteShaper.cpp',
     'gfxHarfBuzzShaper.cpp',
     'gfxImageSurface.cpp',
     'gfxMatrix.cpp',
     'gfxPath.cpp',
     'gfxPattern.cpp',
     'gfxPlatform.cpp',
     'gfxPlatformFontList.cpp',
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -44,20 +44,22 @@
 #include "gfxDrawable.h"
 #include "GeckoProfiler.h"
 #include "nsCSSRenderingBorders.h"
 #include "mozilla/css/ImageLoader.h"
 #include "ImageContainer.h"
 #include "mozilla/Telemetry.h"
 #include "gfxUtils.h"
 #include "gfxColor.h"
+#include "gfxGradientCache.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::css;
+using namespace mozilla::gfx;
 using mozilla::image::ImageOps;
 using mozilla::CSSSizeOrRatio;
 
 static int gFrameTreeLockCount = 0;
 
 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
 // recalculating this for each frame in a continuation (perf), hold
 // a cache of various coordinate information that we need in order
@@ -289,174 +291,16 @@ protected:
 // and a Thebes color
 struct ColorStop {
   ColorStop(double aPosition, gfxRGBA aColor) :
     mPosition(aPosition), mColor(aColor) {}
   double mPosition; // along the gradient line; 0=start, 1=end
   gfxRGBA mColor;
 };
 
-struct GradientCacheKey : public PLDHashEntryHdr {
-  typedef const GradientCacheKey& KeyType;
-  typedef const GradientCacheKey* KeyTypePointer;
-  enum { ALLOW_MEMMOVE = true };
-  const nsTArray<gfx::GradientStop> mStops;
-  const bool mRepeating;
-  const gfx::BackendType mBackendType;
-
-  GradientCacheKey(const nsTArray<gfx::GradientStop>& aStops, const bool aRepeating, const gfx::BackendType aBackendType)
-    : mStops(aStops), mRepeating(aRepeating), mBackendType(aBackendType)
-  { }
-
-  GradientCacheKey(const GradientCacheKey* aOther)
-    : mStops(aOther->mStops), mRepeating(aOther->mRepeating), mBackendType(aOther->mBackendType)
-  { }
-
-  union FloatUint32
-  {
-    float    f;
-    uint32_t u;
-  };
-
-  static PLDHashNumber
-  HashKey(const KeyTypePointer aKey)
-  {
-    PLDHashNumber hash = 0;
-    FloatUint32 convert;
-    hash = AddToHash(hash, aKey->mBackendType);
-    hash = AddToHash(hash, aKey->mRepeating);
-    for (uint32_t i = 0; i < aKey->mStops.Length(); i++) {
-      hash = AddToHash(hash, aKey->mStops[i].color.ToABGR());
-      // Use the float bits as hash, except for the cases of 0.0 and -0.0 which both map to 0
-      convert.f = aKey->mStops[i].offset;
-      hash = AddToHash(hash, convert.f ? convert.u : 0);
-    }
-    return hash;
-  }
-
-  bool KeyEquals(KeyTypePointer aKey) const
-  {
-    bool sameStops = true;
-    if (aKey->mStops.Length() != mStops.Length()) {
-      sameStops = false;
-    } else {
-      for (uint32_t i = 0; i < mStops.Length(); i++) {
-        if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() ||
-            mStops[i].offset != aKey->mStops[i].offset) {
-          sameStops = false;
-          break;
-        }
-      }
-    }
-
-    return sameStops &&
-           (aKey->mBackendType == mBackendType) &&
-           (aKey->mRepeating == mRepeating);
-  }
-  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(mozilla::gfx::GradientStops* aStops, const GradientCacheKey& aKey)
-    : mStops(aStops),
-      mKey(aKey)
-  {}
-
-  GradientCacheData(const GradientCacheData& aOther)
-    : mStops(aOther.mStops),
-      mKey(aOther.mKey)
-  { }
-
-  nsExpirationState *GetExpirationState() {
-    return &mExpirationState;
-  }
-
-  nsExpirationState mExpirationState;
-  const mozilla::RefPtr<mozilla::gfx::GradientStops> mStops;
-  GradientCacheKey mKey;
-};
-
-/**
- * This class implements a cache with no maximum size, that retains the
- * gfxPatterns used to draw the gradients.
- *
- * The key is the nsStyleGradient that defines the gradient, and the size of the
- * gradient.
- *
- * The value is the gfxPattern, and whether or not we perform an optimization
- * based on the actual gradient property.
- *
- * An entry stays in the cache as long as it is used often. As long as a cache
- * entry is in the cache, all the references it has are guaranteed to be valid:
- * the nsStyleRect for the key, the gfxPattern for the value.
- */
-class GradientCache MOZ_FINAL : public nsExpirationTracker<GradientCacheData,4>
-{
-  public:
-    GradientCache()
-      : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS)
-    {
-      srand(time(nullptr));
-      mTimerPeriod = rand() % MAX_GENERATION_MS + 1;
-      Telemetry::Accumulate(Telemetry::GRADIENT_RETENTION_TIME, mTimerPeriod);
-    }
-
-    virtual void NotifyExpired(GradientCacheData* aObject)
-    {
-      // This will free the gfxPattern.
-      RemoveObject(aObject);
-      mHashEntries.Remove(aObject->mKey);
-    }
-
-    GradientCacheData* Lookup(const nsTArray<gfx::GradientStop>& aStops, bool aRepeating, gfx::BackendType aBackendType)
-    {
-      GradientCacheData* gradient =
-        mHashEntries.Get(GradientCacheKey(aStops, aRepeating, aBackendType));
-
-      if (gradient) {
-        MarkUsed(gradient);
-      }
-
-      return gradient;
-    }
-
-    // Returns true if we successfully register the gradient in the cache, false
-    // otherwise.
-    bool RegisterEntry(GradientCacheData* aValue)
-    {
-      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(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.
-     * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
-     */
-    nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
-};
-
 /* Local functions */
 static void DrawBorderImage(nsPresContext* aPresContext,
                             nsRenderingContext& aRenderingContext,
                             nsIFrame* aForFrame,
                             const nsRect& aBorderArea,
                             const nsStyleBorder& aStyleBorder,
                             const nsRect& aDirtyRect);
 
@@ -472,34 +316,30 @@ static void DrawBorderImageComponent(nsR
                                      const nsStyleBorder& aStyleBorder,
                                      uint8_t aIndex);
 
 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
                               nscolor aBackgroundColor,
                               nscolor aBorderColor);
 
 static InlineBackgroundData* gInlineBGData = nullptr;
-static GradientCache* gGradientCache = nullptr;
 
 // Initialize any static variables used by nsCSSRendering.
 void nsCSSRendering::Init()
 {
   NS_ASSERTION(!gInlineBGData, "Init called twice");
   gInlineBGData = new InlineBackgroundData();
-  gGradientCache = new GradientCache();
   nsCSSBorderRenderer::Init();
 }
 
 // Clean up any global variables used by nsCSSRendering.
 void nsCSSRendering::Shutdown()
 {
   delete gInlineBGData;
   gInlineBGData = nullptr;
-  delete gGradientCache;
-  gGradientCache = nullptr;
   nsCSSBorderRenderer::Shutdown();
 }
 
 /**
  * Make a bevel color
  */
 static nscolor
 MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
@@ -2155,25 +1995,16 @@ nsCSSRendering::PaintGradient(nsPresCont
 
   gfxContext *ctx = aRenderingContext.ThebesContext();
   nscoord appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
   gfxRect oneCellArea =
     nsLayoutUtils::RectToGfxRect(aOneCellArea, appUnitsPerPixel);
 
   bool cellContainsFill = aOneCellArea.Contains(aFillArea);
 
-  gfx::BackendType backendType = gfx::BACKEND_NONE;
-  if (ctx->IsCairo()) {
-    backendType = gfx::BACKEND_CAIRO;
-  } else {
-    gfx::DrawTarget* dt = ctx->GetDrawTarget();
-    NS_ASSERTION(dt, "If we are not using Cairo, we should have a draw target.");
-    backendType = dt->GetType();
-  }
-
   // 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);
   } else {
     ComputeRadialGradientLine(aPresContext, aGradient, oneCellArea.Size(),
@@ -2438,28 +2269,22 @@ nsCSSRendering::PaintGradient(nsPresCont
     // which is a lookup table used to evaluate the gradient. This surface can use
     // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
     // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
     // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
     nsTArray<gfx::GradientStop> rawStops(stops.Length());
     rawStops.SetLength(stops.Length());
     for(uint32_t i = 0; i < stops.Length(); i++) {
       rawStops[i].color = gfx::Color(stops[i].mColor.r, stops[i].mColor.g, stops[i].mColor.b, stops[i].mColor.a);
-      rawStops[i].offset =  stopScale * (stops[i].mPosition - stopOrigin);
+      rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin);
     }
-    GradientCacheData* cached = gGradientCache->Lookup(rawStops, isRepeat, backendType);
-    mozilla::RefPtr<mozilla::gfx::GradientStops> gs = cached ? cached->mStops : nullptr;
-    if (!gs) {
-      // CreateGradientStops is expensive (possibly lazily)
-      gs = ctx->GetDrawTarget()->CreateGradientStops(rawStops.Elements(), stops.Length(), isRepeat ? gfx::EXTEND_REPEAT : gfx::EXTEND_CLAMP);
-      cached = new GradientCacheData(gs, GradientCacheKey(rawStops, isRepeat, backendType));
-      if (!gGradientCache->RegisterEntry(cached)) {
-        delete cached;
-      }
-    }
+    mozilla::RefPtr<mozilla::gfx::GradientStops> gs =
+      gfxGradientCache::GetOrCreateGradientStops(ctx->GetDrawTarget(),
+                                                 rawStops,
+                                                 isRepeat ? gfx::EXTEND_REPEAT : gfx::EXTEND_CLAMP);
     gradientPattern->SetColorStops(gs);
   } else {
     for (uint32_t i = 0; i < stops.Length(); i++) {
       double pos = stopScale*(stops[i].mPosition - stopOrigin);
       gradientPattern->AddColorStop(pos, stops[i].mColor);
     }
     // Set repeat mode. Default cairo extend mode is PAD.
     if (isRepeat) {