1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/gfx/2D.h"
7 #include "nsTArray.h"
8 #include "PLDHashTable.h"
9 #include "nsExpirationTracker.h"
10 #include "nsClassHashtable.h"
11 #include "gfxGradientCache.h"
12 #include <time.h>
13 
14 namespace mozilla {
15 namespace gfx {
16 
17 using namespace mozilla;
18 
19 struct GradientCacheKey : public PLDHashEntryHdr {
20   typedef const GradientCacheKey& KeyType;
21   typedef const GradientCacheKey* KeyTypePointer;
22   enum { ALLOW_MEMMOVE = true };
23   const CopyableTArray<GradientStop> mStops;
24   ExtendMode mExtend;
25   BackendType mBackendType;
26 
GradientCacheKeymozilla::gfx::GradientCacheKey27   GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend,
28                    BackendType aBackendType)
29       : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {}
30 
GradientCacheKeymozilla::gfx::GradientCacheKey31   explicit GradientCacheKey(const GradientCacheKey* aOther)
32       : mStops(aOther->mStops),
33         mExtend(aOther->mExtend),
34         mBackendType(aOther->mBackendType) {}
35 
36   GradientCacheKey(GradientCacheKey&& aOther) = default;
37 
38   union FloatUint32 {
39     float f;
40     uint32_t u;
41   };
42 
HashKeymozilla::gfx::GradientCacheKey43   static PLDHashNumber HashKey(const KeyTypePointer aKey) {
44     PLDHashNumber hash = 0;
45     FloatUint32 convert;
46     hash = AddToHash(hash, int(aKey->mBackendType));
47     hash = AddToHash(hash, int(aKey->mExtend));
48     for (uint32_t i = 0; i < aKey->mStops.Length(); i++) {
49       hash = AddToHash(hash, aKey->mStops[i].color.ToABGR());
50       // Use the float bits as hash, except for the cases of 0.0 and -0.0 which
51       // both map to 0
52       convert.f = aKey->mStops[i].offset;
53       hash = AddToHash(hash, convert.f ? convert.u : 0);
54     }
55     return hash;
56   }
57 
KeyEqualsmozilla::gfx::GradientCacheKey58   bool KeyEquals(KeyTypePointer aKey) const {
59     bool sameStops = true;
60     if (aKey->mStops.Length() != mStops.Length()) {
61       sameStops = false;
62     } else {
63       for (uint32_t i = 0; i < mStops.Length(); i++) {
64         if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() ||
65             mStops[i].offset != aKey->mStops[i].offset) {
66           sameStops = false;
67           break;
68         }
69       }
70     }
71 
72     return sameStops && (aKey->mBackendType == mBackendType) &&
73            (aKey->mExtend == mExtend);
74   }
KeyToPointermozilla::gfx::GradientCacheKey75   static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
76 };
77 
78 /**
79  * This class is what is cached. It need to be allocated in an object separated
80  * to the cache entry to be able to be tracked by the nsExpirationTracker.
81  * */
82 struct GradientCacheData {
GradientCacheDatamozilla::gfx::GradientCacheData83   GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey)
84       : mStops(aStops), mKey(std::move(aKey)) {}
85 
86   GradientCacheData(GradientCacheData&& aOther) = default;
87 
GetExpirationStatemozilla::gfx::GradientCacheData88   nsExpirationState* GetExpirationState() { return &mExpirationState; }
89 
90   nsExpirationState mExpirationState;
91   const RefPtr<GradientStops> mStops;
92   GradientCacheKey mKey;
93 };
94 
95 /**
96  * This class implements a cache with no maximum size, that retains the
97  * gfxPatterns used to draw the gradients.
98  *
99  * The key is the nsStyleGradient that defines the gradient, and the size of the
100  * gradient.
101  *
102  * The value is the gfxPattern, and whether or not we perform an optimization
103  * based on the actual gradient property.
104  *
105  * An entry stays in the cache as long as it is used often. As long as a cache
106  * entry is in the cache, all the references it has are guaranteed to be valid:
107  * the nsStyleRect for the key, the gfxPattern for the value.
108  */
109 class GradientCache final : public nsExpirationTracker<GradientCacheData, 4> {
110  public:
GradientCache()111   GradientCache()
112       : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS,
113                                                   "GradientCache") {
114     srand(time(nullptr));
115   }
116 
NotifyExpired(GradientCacheData * aObject)117   virtual void NotifyExpired(GradientCacheData* aObject) override {
118     // This will free the gfxPattern.
119     RemoveObject(aObject);
120     mHashEntries.Remove(aObject->mKey);
121   }
122 
Lookup(const nsTArray<GradientStop> & aStops,ExtendMode aExtend,BackendType aBackendType)123   GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops,
124                             ExtendMode aExtend, BackendType aBackendType) {
125     GradientCacheData* gradient =
126         mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType));
127 
128     if (gradient) {
129       MarkUsed(gradient);
130     }
131 
132     return gradient;
133   }
134 
135   // Returns true if we successfully register the gradient in the cache, false
136   // otherwise.
RegisterEntry(GradientCacheData * aValue)137   bool RegisterEntry(GradientCacheData* aValue) {
138     nsresult rv = AddObject(aValue);
139     if (NS_FAILED(rv)) {
140       // We are OOM, and we cannot track this object. We don't want stall
141       // entries in the hash table (since the expiration tracker is responsible
142       // for removing the cache entries), so we avoid putting that entry in the
143       // table, which is a good things considering we are short on memory
144       // anyway, we probably don't want to retain things.
145       return false;
146     }
147     mHashEntries.Put(aValue->mKey, aValue);
148     return true;
149   }
150 
151  protected:
152   static const uint32_t MAX_GENERATION_MS = 10000;
153   /**
154    * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
155    * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
156    */
157   nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
158 };
159 
160 static GradientCache* gGradientCache = nullptr;
161 
GetGradientStops(const DrawTarget * aDT,nsTArray<GradientStop> & aStops,ExtendMode aExtend)162 GradientStops* gfxGradientCache::GetGradientStops(
163     const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
164   if (!gGradientCache) {
165     gGradientCache = new GradientCache();
166   }
167   GradientCacheData* cached =
168       gGradientCache->Lookup(aStops, aExtend, aDT->GetBackendType());
169   if (cached && cached->mStops) {
170     if (!cached->mStops->IsValid()) {
171       gGradientCache->NotifyExpired(cached);
172     } else {
173       return cached->mStops;
174     }
175   }
176 
177   return nullptr;
178 }
179 
GetOrCreateGradientStops(const DrawTarget * aDT,nsTArray<GradientStop> & aStops,ExtendMode aExtend)180 already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
181     const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
182   if (aDT->IsRecording()) {
183     return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
184                                     aExtend);
185   }
186 
187   RefPtr<GradientStops> gs = GetGradientStops(aDT, aStops, aExtend);
188   if (!gs) {
189     gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
190     if (!gs) {
191       return nullptr;
192     }
193     GradientCacheData* cached = new GradientCacheData(
194         gs, GradientCacheKey(aStops, aExtend, aDT->GetBackendType()));
195     if (!gGradientCache->RegisterEntry(cached)) {
196       delete cached;
197     }
198   }
199   return gs.forget();
200 }
201 
PurgeAllCaches()202 void gfxGradientCache::PurgeAllCaches() {
203   if (gGradientCache) {
204     gGradientCache->AgeAllGenerations();
205   }
206 }
207 
Shutdown()208 void gfxGradientCache::Shutdown() {
209   delete gGradientCache;
210   gGradientCache = nullptr;
211 }
212 
213 }  // namespace gfx
214 }  // namespace mozilla
215