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