gfxGradientCache.cpp (10630B)
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 30 GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend, 31 BackendType aBackendType) 32 : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {} 33 34 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 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); 57 } 58 return hash; 59 } 60 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; 71 } 72 } 73 } 74 75 return sameStops && (aKey->mBackendType == mBackendType) && 76 (aKey->mExtend == mExtend); 77 } 78 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 { 86 GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey) 87 : mStops(aStops), mKey(std::move(aKey)) {} 88 89 GradientCacheData(GradientCacheData&& aOther) = default; 90 91 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: 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(); } 116 117 private: 118 GradientCacheMutex::AutoLock mAutoLock; 119 }; 120 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"_ns) {} 129 static bool EnsureInstance() { 130 LockedInstance lockedInstance(sInstanceMutex); 131 return EnsureInstanceLocked(lockedInstance); 132 } 133 134 static void DestroyInstance() { 135 LockedInstance lockedInstance(sInstanceMutex); 136 if (lockedInstance) { 137 *lockedInstance = nullptr; 138 } 139 } 140 141 static void AgeAllGenerations() { 142 LockedInstance lockedInstance(sInstanceMutex); 143 if (!lockedInstance) { 144 return; 145 } 146 lockedInstance->AgeAllGenerationsLocked(lockedInstance); 147 lockedInstance->NotifyHandlerEndLocked(lockedInstance); 148 } 149 150 template <typename CreateFunc> 151 static already_AddRefed<GradientStops> LookupOrInsert( 152 const GradientCacheKey& aKey, CreateFunc aCreateFunc) { 153 RefPtr<GradientStops> stops; 154 bool onMaxEntriesBreached = false; 155 { 156 LockedInstance lockedInstance(sInstanceMutex); 157 if (!EnsureInstanceLocked(lockedInstance)) { 158 return aCreateFunc(); 159 } 160 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); 166 } 167 168 lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance); 169 lockedInstance->NotifyHandlerEndLocked(lockedInstance); 170 } 171 172 stops = aCreateFunc(); 173 if (!stops) { 174 return nullptr; 175 } 176 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(); 186 } 187 lockedInstance->mHashEntries.InsertOrUpdate(aKey, std::move(data)); 188 if (lockedInstance->mHashEntries.Count() > MAX_ENTRIES && 189 !lockedInstance->mRemovingEntries) { 190 lockedInstance->mRemovingEntries = true; 191 onMaxEntriesBreached = true; 192 } 193 } 194 195 if (onMaxEntriesBreached) { 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 if (lockedInstance->mHashEntries.Count() < MAX_ENTRIES) { 204 lockedInstance->mRemovingEntries = false; 205 return; 206 } 207 while (true) { 208 uint32_t remainingEntries = lockedInstance->mHashEntries.Count(); 209 lockedInstance->AgeOneGenerationLocked(lockedInstance); 210 if (lockedInstance->mHashEntries.Count() >= remainingEntries) { 211 // Stop if there is no progress. 212 break; 213 } 214 } 215 lockedInstance->NotifyHandlerEndLocked(lockedInstance); 216 lockedInstance->mRemovingEntries = false; 217 })); 218 } 219 220 return stops.forget(); 221 } 222 223 GradientCacheMutex& GetMutex() final { return sInstanceMutex; } 224 225 void NotifyExpiredLocked(GradientCacheData* aObject, 226 const LockedInstance& aLockedInstance) final { 227 // Remove the gradient from the tracker. 228 RemoveObjectLocked(aObject, aLockedInstance); 229 230 // If entry exists move the data to mRemovedGradientData because we want to 231 // drop it outside of the lock. 232 Maybe<UniquePtr<GradientCacheData>> gradientData = 233 mHashEntries.Extract(aObject->mKey); 234 if (gradientData.isSome()) { 235 mRemovedGradientData.AppendElement(std::move(*gradientData)); 236 } 237 } 238 239 void NotifyHandlerEndLocked(const LockedInstance&) final { 240 NS_DispatchToMainThread( 241 NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops", 242 [stops = std::move(mRemovedGradientData)] {})); 243 } 244 245 private: 246 static const uint32_t MAX_GENERATION_MS = 10000; 247 248 // On Windows some of the Direct2D objects associated with the gradient stops 249 // can be quite large, so we limit the number of cache entries. 250 static const uint32_t MAX_ENTRIES = 4000; 251 static GradientCacheMutex sInstanceMutex; 252 253 [[nodiscard]] static bool EnsureInstanceLocked( 254 LockedInstance& aLockedInstance) { 255 if (!aLockedInstance) { 256 // GradientCache must be created on the main thread. 257 if (!NS_IsMainThread()) { 258 // This should only happen at shutdown, we fall back to not caching. 259 return false; 260 } 261 *aLockedInstance = MakeUnique<GradientCache>(); 262 } 263 return true; 264 } 265 266 /** 267 * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. 268 * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 269 */ 270 nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries; 271 nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData; 272 bool mRemovingEntries = false; 273 }; 274 275 MOZ_RUNINIT GradientCacheMutex GradientCache::sInstanceMutex("GradientCache"); 276 277 void gfxGradientCache::Init() { 278 MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(), 279 "First call must be on main thread."); 280 } 281 282 already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops( 283 const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) { 284 if (aDT->IsRecording()) { 285 return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), 286 aExtend); 287 } 288 289 return GradientCache::LookupOrInsert( 290 GradientCacheKey(aStops, aExtend, aDT->GetBackendType()), 291 [&]() -> already_AddRefed<GradientStops> { 292 return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), 293 aExtend); 294 }); 295 } 296 297 void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); } 298 299 void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); } 300 301 } // namespace gfx 302 } // namespace mozilla