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 "gfxGradientCache.h"
7
8 #include "MainThreadUtils.h"
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/DataMutex.h"
11 #include "nsTArray.h"
12 #include "PLDHashTable.h"
13 #include "nsExpirationTracker.h"
14 #include "nsClassHashtable.h"
15 #include <time.h>
16
17 namespace mozilla {
18 namespace gfx {
19
20 using namespace mozilla;
21
22 struct GradientCacheKey : public PLDHashEntryHdr {
23 typedef const GradientCacheKey& KeyType;
24 typedef const GradientCacheKey* KeyTypePointer;
25 enum { ALLOW_MEMMOVE = true };
26 const CopyableTArray<GradientStop> mStops;
27 ExtendMode mExtend;
28 BackendType mBackendType;
29
GradientCacheKeymozilla::gfx::GradientCacheKey30 GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend,
31 BackendType aBackendType)
32 : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {}
33
GradientCacheKeymozilla::gfx::GradientCacheKey34 explicit GradientCacheKey(const GradientCacheKey* aOther)
35 : mStops(aOther->mStops),
36 mExtend(aOther->mExtend),
37 mBackendType(aOther->mBackendType) {}
38
39 GradientCacheKey(GradientCacheKey&& aOther) = default;
40
41 union FloatUint32 {
42 float f;
43 uint32_t u;
44 };
45
HashKeymozilla::gfx::GradientCacheKey46 static PLDHashNumber HashKey(const KeyTypePointer aKey) {
47 PLDHashNumber hash = 0;
48 FloatUint32 convert;
49 hash = AddToHash(hash, int(aKey->mBackendType));
50 hash = AddToHash(hash, int(aKey->mExtend));
51 for (uint32_t i = 0; i < aKey->mStops.Length(); i++) {
52 hash = AddToHash(hash, aKey->mStops[i].color.ToABGR());
53 // Use the float bits as hash, except for the cases of 0.0 and -0.0 which
54 // both map to 0
55 convert.f = aKey->mStops[i].offset;
56 hash = AddToHash(hash, convert.f ? convert.u : 0);
57 }
58 return hash;
59 }
60
KeyEqualsmozilla::gfx::GradientCacheKey61 bool KeyEquals(KeyTypePointer aKey) const {
62 bool sameStops = true;
63 if (aKey->mStops.Length() != mStops.Length()) {
64 sameStops = false;
65 } else {
66 for (uint32_t i = 0; i < mStops.Length(); i++) {
67 if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() ||
68 mStops[i].offset != aKey->mStops[i].offset) {
69 sameStops = false;
70 break;
71 }
72 }
73 }
74
75 return sameStops && (aKey->mBackendType == mBackendType) &&
76 (aKey->mExtend == mExtend);
77 }
KeyToPointermozilla::gfx::GradientCacheKey78 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
79 };
80
81 /**
82 * This class is what is cached. It need to be allocated in an object separated
83 * to the cache entry to be able to be tracked by the nsExpirationTracker.
84 * */
85 struct GradientCacheData {
GradientCacheDatamozilla::gfx::GradientCacheData86 GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey)
87 : mStops(aStops), mKey(std::move(aKey)) {}
88
89 GradientCacheData(GradientCacheData&& aOther) = default;
90
GetExpirationStatemozilla::gfx::GradientCacheData91 nsExpirationState* GetExpirationState() { return &mExpirationState; }
92
93 nsExpirationState mExpirationState;
94 const RefPtr<GradientStops> mStops;
95 GradientCacheKey mKey;
96 };
97
98 /**
99 * This class implements a cache, that retains the GradientStops used to draw
100 * the gradients.
101 *
102 * An entry stays in the cache as long as it is used often and we don't exceed
103 * the maximum, in which case the most recently used will be kept.
104 */
105 class GradientCache;
106 using GradientCacheMutex = StaticDataMutex<UniquePtr<GradientCache>>;
107 class MOZ_RAII LockedInstance {
108 public:
LockedInstance(GradientCacheMutex & aDataMutex)109 explicit LockedInstance(GradientCacheMutex& aDataMutex)
110 : mAutoLock(aDataMutex.Lock()) {}
operator ->() const111 UniquePtr<GradientCache>& operator->() const& { return mAutoLock.ref(); }
112 UniquePtr<GradientCache>& operator->() const&& = delete;
operator *() const113 UniquePtr<GradientCache>& operator*() const& { return mAutoLock.ref(); }
114 UniquePtr<GradientCache>& operator*() const&& = delete;
operator bool() const115 explicit operator bool() const { return !!mAutoLock.ref(); }
116
117 private:
118 GradientCacheMutex::AutoLock mAutoLock;
119 };
120
121 class GradientCache final
122 : public ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
123 LockedInstance> {
124 public:
GradientCache()125 GradientCache()
126 : ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
127 LockedInstance>(MAX_GENERATION_MS,
128 "GradientCache") {}
EnsureInstance()129 static bool EnsureInstance() {
130 LockedInstance lockedInstance(sInstanceMutex);
131 return EnsureInstanceLocked(lockedInstance);
132 }
133
DestroyInstance()134 static void DestroyInstance() {
135 LockedInstance lockedInstance(sInstanceMutex);
136 if (lockedInstance) {
137 *lockedInstance = nullptr;
138 }
139 }
140
AgeAllGenerations()141 static void AgeAllGenerations() {
142 LockedInstance lockedInstance(sInstanceMutex);
143 if (!lockedInstance) {
144 return;
145 }
146 lockedInstance->AgeAllGenerationsLocked(lockedInstance);
147 lockedInstance->NotifyHandlerEndLocked(lockedInstance);
148 }
149
Lookup(const nsTArray<GradientStop> & aStops,ExtendMode aExtend,BackendType aBackendType)150 static GradientCacheData* Lookup(const nsTArray<GradientStop>& aStops,
151 ExtendMode aExtend,
152 BackendType aBackendType) {
153 LockedInstance lockedInstance(sInstanceMutex);
154 if (!EnsureInstanceLocked(lockedInstance)) {
155 return nullptr;
156 }
157
158 GradientCacheData* gradientData = lockedInstance->mHashEntries.Get(
159 GradientCacheKey(aStops, aExtend, aBackendType));
160 if (!gradientData) {
161 return nullptr;
162 }
163
164 if (!gradientData->mStops || !gradientData->mStops->IsValid()) {
165 lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance);
166 return nullptr;
167 }
168
169 lockedInstance->MarkUsedLocked(gradientData, lockedInstance);
170 return gradientData;
171 }
172
RegisterEntry(UniquePtr<GradientCacheData> aValue)173 static void RegisterEntry(UniquePtr<GradientCacheData> aValue) {
174 uint32_t numberOfEntries;
175 {
176 LockedInstance lockedInstance(sInstanceMutex);
177 if (!EnsureInstanceLocked(lockedInstance)) {
178 return;
179 }
180
181 nsresult rv =
182 lockedInstance->AddObjectLocked(aValue.get(), lockedInstance);
183 if (NS_FAILED(rv)) {
184 // We are OOM, and we cannot track this object. We don't want to store
185 // entries in the hash table (since the expiration tracker is
186 // responsible for removing the cache entries), so we avoid putting that
187 // entry in the table, which is a good thing considering we are short on
188 // memory anyway, we probably don't want to retain things.
189 return;
190 }
191 lockedInstance->mHashEntries.InsertOrUpdate(aValue->mKey,
192 std::move(aValue));
193 numberOfEntries = lockedInstance->mHashEntries.Count();
194 }
195 if (numberOfEntries > MAX_ENTRIES) {
196 // We have too many entries force the cache to age a generation.
197 NS_DispatchToMainThread(
198 NS_NewRunnableFunction("GradientCache::OnMaxEntriesBreached", [] {
199 LockedInstance lockedInstance(sInstanceMutex);
200 if (!lockedInstance) {
201 return;
202 }
203 lockedInstance->AgeOneGenerationLocked(lockedInstance);
204 lockedInstance->NotifyHandlerEndLocked(lockedInstance);
205 }));
206 }
207 }
208
GetMutex()209 GradientCacheMutex& GetMutex() final { return sInstanceMutex; }
210
NotifyExpiredLocked(GradientCacheData * aObject,const LockedInstance & aLockedInstance)211 void NotifyExpiredLocked(GradientCacheData* aObject,
212 const LockedInstance& aLockedInstance) final {
213 // Remove the gradient from the tracker.
214 RemoveObjectLocked(aObject, aLockedInstance);
215
216 // If entry exists move the data to mRemovedGradientData because we want to
217 // drop it outside of the lock.
218 Maybe<UniquePtr<GradientCacheData>> gradientData =
219 mHashEntries.Extract(aObject->mKey);
220 if (gradientData.isSome()) {
221 mRemovedGradientData.AppendElement(std::move(*gradientData));
222 }
223 }
224
NotifyHandlerEndLocked(const LockedInstance &)225 void NotifyHandlerEndLocked(const LockedInstance&) final {
226 NS_DispatchToCurrentThread(
227 NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops",
228 [stops = std::move(mRemovedGradientData)] {}));
229 }
230
231 private:
232 static const uint32_t MAX_GENERATION_MS = 10000;
233
234 // On Windows some of the Direct2D objects associated with the gradient stops
235 // can be quite large, so we limit the number of cache entries.
236 static const uint32_t MAX_ENTRIES = 4000;
237 static GradientCacheMutex sInstanceMutex;
238
EnsureInstanceLocked(LockedInstance & aLockedInstance)239 [[nodiscard]] static bool EnsureInstanceLocked(
240 LockedInstance& aLockedInstance) {
241 if (!aLockedInstance) {
242 // GradientCache must be created on the main thread.
243 if (!NS_IsMainThread()) {
244 // This should only happen at shutdown, we fall back to not caching.
245 return false;
246 }
247 *aLockedInstance = MakeUnique<GradientCache>();
248 }
249 return true;
250 }
251
252 /**
253 * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
254 * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
255 */
256 nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
257 nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData;
258 };
259
260 GradientCacheMutex GradientCache::sInstanceMutex("GradientCache");
261
Init()262 void gfxGradientCache::Init() {
263 MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(),
264 "First call must be on main thread.");
265 }
266
GetOrCreateGradientStops(const DrawTarget * aDT,nsTArray<GradientStop> & aStops,ExtendMode aExtend)267 already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
268 const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
269 if (aDT->IsRecording()) {
270 return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
271 aExtend);
272 }
273
274 GradientCacheData* cached =
275 GradientCache::Lookup(aStops, aExtend, aDT->GetBackendType());
276 if (cached) {
277 return do_AddRef(cached->mStops);
278 }
279
280 RefPtr<GradientStops> gs =
281 aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
282 if (!gs) {
283 return nullptr;
284 }
285 GradientCache::RegisterEntry(MakeUnique<GradientCacheData>(
286 gs, GradientCacheKey(aStops, aExtend, aDT->GetBackendType())));
287 return gs.forget();
288 }
289
PurgeAllCaches()290 void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }
291
Shutdown()292 void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }
293
294 } // namespace gfx
295 } // namespace mozilla
296