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/. */
6 #include "gfxGradientCache.h"
8 #include "MainThreadUtils.h"
9 #include "mozilla/gfx/2D.h"
10 #include "mozilla/DataMutex.h"
12 #include "PLDHashTable.h"
13 #include "nsExpirationTracker.h"
14 #include "nsClassHashtable.h"
20 using namespace mozilla
;
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
;
28 BackendType mBackendType
;
30 GradientCacheKey(const nsTArray
<GradientStop
>& aStops
, ExtendMode aExtend
,
31 BackendType aBackendType
)
32 : mStops(aStops
), mExtend(aExtend
), mBackendType(aBackendType
) {}
34 explicit GradientCacheKey(const GradientCacheKey
* aOther
)
35 : mStops(aOther
->mStops
),
36 mExtend(aOther
->mExtend
),
37 mBackendType(aOther
->mBackendType
) {}
39 GradientCacheKey(GradientCacheKey
&& aOther
) = default;
46 static PLDHashNumber
HashKey(const KeyTypePointer aKey
) {
47 PLDHashNumber hash
= 0;
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
55 convert
.f
= aKey
->mStops
[i
].offset
;
56 hash
= AddToHash(hash
, convert
.f
? convert
.u
: 0);
61 bool KeyEquals(KeyTypePointer aKey
) const {
62 bool sameStops
= true;
63 if (aKey
->mStops
.Length() != mStops
.Length()) {
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
) {
75 return sameStops
&& (aKey
->mBackendType
== mBackendType
) &&
76 (aKey
->mExtend
== mExtend
);
78 static KeyTypePointer
KeyToPointer(KeyType aKey
) { return &aKey
; }
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.
85 struct GradientCacheData
{
86 GradientCacheData(GradientStops
* aStops
, GradientCacheKey
&& aKey
)
87 : mStops(aStops
), mKey(std::move(aKey
)) {}
89 GradientCacheData(GradientCacheData
&& aOther
) = default;
91 nsExpirationState
* GetExpirationState() { return &mExpirationState
; }
93 nsExpirationState mExpirationState
;
94 const RefPtr
<GradientStops
> mStops
;
95 GradientCacheKey mKey
;
99 * This class implements a cache, that retains the GradientStops used to draw
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.
106 using GradientCacheMutex
= StaticDataMutex
<UniquePtr
<GradientCache
>>;
107 class MOZ_RAII LockedInstance
{
109 explicit LockedInstance(GradientCacheMutex
& aDataMutex
)
110 : mAutoLock(aDataMutex
.Lock()) {}
111 UniquePtr
<GradientCache
>& operator->() const& { return mAutoLock
.ref(); }
112 UniquePtr
<GradientCache
>& operator->() const&& = delete;
113 UniquePtr
<GradientCache
>& operator*() const& { return mAutoLock
.ref(); }
114 UniquePtr
<GradientCache
>& operator*() const&& = delete;
115 explicit operator bool() const { return !!mAutoLock
.ref(); }
118 GradientCacheMutex::AutoLock mAutoLock
;
121 class GradientCache final
122 : public ExpirationTrackerImpl
<GradientCacheData
, 4, GradientCacheMutex
,
126 : ExpirationTrackerImpl
<GradientCacheData
, 4, GradientCacheMutex
,
127 LockedInstance
>(MAX_GENERATION_MS
,
129 static bool EnsureInstance() {
130 LockedInstance
lockedInstance(sInstanceMutex
);
131 return EnsureInstanceLocked(lockedInstance
);
134 static void DestroyInstance() {
135 LockedInstance
lockedInstance(sInstanceMutex
);
136 if (lockedInstance
) {
137 *lockedInstance
= nullptr;
141 static void AgeAllGenerations() {
142 LockedInstance
lockedInstance(sInstanceMutex
);
143 if (!lockedInstance
) {
146 lockedInstance
->AgeAllGenerationsLocked(lockedInstance
);
147 lockedInstance
->NotifyHandlerEndLocked(lockedInstance
);
150 template <typename CreateFunc
>
151 static already_AddRefed
<GradientStops
> LookupOrInsert(
152 const GradientCacheKey
& aKey
, CreateFunc aCreateFunc
) {
153 uint32_t numberOfEntries
;
154 RefPtr
<GradientStops
> stops
;
156 LockedInstance
lockedInstance(sInstanceMutex
);
157 if (!EnsureInstanceLocked(lockedInstance
)) {
158 return aCreateFunc();
161 GradientCacheData
* gradientData
= lockedInstance
->mHashEntries
.Get(aKey
);
163 if (gradientData
->mStops
&& gradientData
->mStops
->IsValid()) {
164 lockedInstance
->MarkUsedLocked(gradientData
, lockedInstance
);
165 return do_AddRef(gradientData
->mStops
);
168 lockedInstance
->NotifyExpiredLocked(gradientData
, lockedInstance
);
169 lockedInstance
->NotifyHandlerEndLocked(lockedInstance
);
172 stops
= aCreateFunc();
177 auto data
= MakeUnique
<GradientCacheData
>(stops
, GradientCacheKey(&aKey
));
178 nsresult rv
= lockedInstance
->AddObjectLocked(data
.get(), lockedInstance
);
180 // We are OOM, and we cannot track this object. We don't want to store
181 // entries in the hash table (since the expiration tracker is
182 // responsible for removing the cache entries), so we avoid putting that
183 // entry in the table, which is a good thing considering we are short on
184 // memory anyway, we probably don't want to retain things.
185 return stops
.forget();
187 lockedInstance
->mHashEntries
.InsertOrUpdate(aKey
, std::move(data
));
188 numberOfEntries
= lockedInstance
->mHashEntries
.Count();
191 if (numberOfEntries
> MAX_ENTRIES
) {
192 // We have too many entries force the cache to age a generation.
193 NS_DispatchToMainThread(
194 NS_NewRunnableFunction("GradientCache::OnMaxEntriesBreached", [] {
195 LockedInstance
lockedInstance(sInstanceMutex
);
196 if (!lockedInstance
) {
199 lockedInstance
->AgeOneGenerationLocked(lockedInstance
);
200 lockedInstance
->NotifyHandlerEndLocked(lockedInstance
);
204 return stops
.forget();
207 GradientCacheMutex
& GetMutex() final
{ return sInstanceMutex
; }
209 void NotifyExpiredLocked(GradientCacheData
* aObject
,
210 const LockedInstance
& aLockedInstance
) final
{
211 // Remove the gradient from the tracker.
212 RemoveObjectLocked(aObject
, aLockedInstance
);
214 // If entry exists move the data to mRemovedGradientData because we want to
215 // drop it outside of the lock.
216 Maybe
<UniquePtr
<GradientCacheData
>> gradientData
=
217 mHashEntries
.Extract(aObject
->mKey
);
218 if (gradientData
.isSome()) {
219 mRemovedGradientData
.AppendElement(std::move(*gradientData
));
223 void NotifyHandlerEndLocked(const LockedInstance
&) final
{
224 NS_DispatchToMainThread(
225 NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops",
226 [stops
= std::move(mRemovedGradientData
)] {}));
230 static const uint32_t MAX_GENERATION_MS
= 10000;
232 // On Windows some of the Direct2D objects associated with the gradient stops
233 // can be quite large, so we limit the number of cache entries.
234 static const uint32_t MAX_ENTRIES
= 4000;
235 static GradientCacheMutex sInstanceMutex
;
237 [[nodiscard
]] static bool EnsureInstanceLocked(
238 LockedInstance
& aLockedInstance
) {
239 if (!aLockedInstance
) {
240 // GradientCache must be created on the main thread.
241 if (!NS_IsMainThread()) {
242 // This should only happen at shutdown, we fall back to not caching.
245 *aLockedInstance
= MakeUnique
<GradientCache
>();
251 * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
252 * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
254 nsClassHashtable
<GradientCacheKey
, GradientCacheData
> mHashEntries
;
255 nsTArray
<UniquePtr
<GradientCacheData
>> mRemovedGradientData
;
258 GradientCacheMutex
GradientCache::sInstanceMutex("GradientCache");
260 void gfxGradientCache::Init() {
261 MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(),
262 "First call must be on main thread.");
265 already_AddRefed
<GradientStops
> gfxGradientCache::GetOrCreateGradientStops(
266 const DrawTarget
* aDT
, nsTArray
<GradientStop
>& aStops
, ExtendMode aExtend
) {
267 if (aDT
->IsRecording()) {
268 return aDT
->CreateGradientStops(aStops
.Elements(), aStops
.Length(),
272 return GradientCache::LookupOrInsert(
273 GradientCacheKey(aStops
, aExtend
, aDT
->GetBackendType()),
274 [&]() -> already_AddRefed
<GradientStops
> {
275 return aDT
->CreateGradientStops(aStops
.Elements(), aStops
.Length(),
280 void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }
282 void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }
285 } // namespace mozilla