Bug 1874684 - Part 28: Return DateDuration from DifferenceISODateTime. r=mgaudet
[gecko.git] / gfx / thebes / gfxGradientCache.cpp
blobc2a1682a7d9edb6ba1612b17ea1a1e02509e4959
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"
11 #include "nsTArray.h"
12 #include "PLDHashTable.h"
13 #include "nsExpirationTracker.h"
14 #include "nsClassHashtable.h"
15 #include <time.h>
17 namespace mozilla {
18 namespace gfx {
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;
27 ExtendMode mExtend;
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;
41 union FloatUint32 {
42 float f;
43 uint32_t u;
46 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);
58 return hash;
61 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;
75 return sameStops && (aKey->mBackendType == mBackendType) &&
76 (aKey->mExtend == mExtend);
78 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
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 {
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;
98 /**
99 * This class implements a cache, that retains the GradientStops used to draw
100 * the gradients.
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.
105 class GradientCache;
106 using GradientCacheMutex = StaticDataMutex<UniquePtr<GradientCache>>;
107 class MOZ_RAII LockedInstance {
108 public:
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(); }
117 private:
118 GradientCacheMutex::AutoLock mAutoLock;
121 class GradientCache final
122 : public ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
123 LockedInstance> {
124 public:
125 GradientCache()
126 : ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
127 LockedInstance>(MAX_GENERATION_MS,
128 "GradientCache") {}
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) {
144 return;
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);
162 if (gradientData) {
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();
173 if (!stops) {
174 return nullptr;
177 auto data = MakeUnique<GradientCacheData>(stops, GradientCacheKey(&aKey));
178 nsresult rv = lockedInstance->AddObjectLocked(data.get(), lockedInstance);
179 if (NS_FAILED(rv)) {
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) {
197 return;
199 lockedInstance->AgeOneGenerationLocked(lockedInstance);
200 lockedInstance->NotifyHandlerEndLocked(lockedInstance);
201 }));
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)] {}));
229 private:
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.
243 return false;
245 *aLockedInstance = MakeUnique<GradientCache>();
247 return true;
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(),
269 aExtend);
272 return GradientCache::LookupOrInsert(
273 GradientCacheKey(aStops, aExtend, aDT->GetBackendType()),
274 [&]() -> already_AddRefed<GradientStops> {
275 return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
276 aExtend);
280 void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }
282 void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }
284 } // namespace gfx
285 } // namespace mozilla