/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gfxGradientCache.h" #include "MainThreadUtils.h" #include "mozilla/gfx/2D.h" #include "mozilla/DataMutex.h" #include "nsTArray.h" #include "PLDHashTable.h" #include "nsExpirationTracker.h" #include "nsClassHashtable.h" #include namespace mozilla { namespace gfx { using namespace mozilla; struct GradientCacheKey : public PLDHashEntryHdr { typedef const GradientCacheKey& KeyType; typedef const GradientCacheKey* KeyTypePointer; enum { ALLOW_MEMMOVE = true }; const CopyableTArray mStops; ExtendMode mExtend; BackendType mBackendType; GradientCacheKey(const nsTArray& aStops, ExtendMode aExtend, BackendType aBackendType) : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {} explicit GradientCacheKey(const GradientCacheKey* aOther) : mStops(aOther->mStops), mExtend(aOther->mExtend), mBackendType(aOther->mBackendType) {} GradientCacheKey(GradientCacheKey&& aOther) = default; union FloatUint32 { float f; uint32_t u; }; static PLDHashNumber HashKey(const KeyTypePointer aKey) { PLDHashNumber hash = 0; FloatUint32 convert; hash = AddToHash(hash, int(aKey->mBackendType)); hash = AddToHash(hash, int(aKey->mExtend)); for (uint32_t i = 0; i < aKey->mStops.Length(); i++) { hash = AddToHash(hash, aKey->mStops[i].color.ToABGR()); // Use the float bits as hash, except for the cases of 0.0 and -0.0 which // both map to 0 convert.f = aKey->mStops[i].offset; hash = AddToHash(hash, convert.f ? convert.u : 0); } return hash; } bool KeyEquals(KeyTypePointer aKey) const { bool sameStops = true; if (aKey->mStops.Length() != mStops.Length()) { sameStops = false; } else { for (uint32_t i = 0; i < mStops.Length(); i++) { if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() || mStops[i].offset != aKey->mStops[i].offset) { sameStops = false; break; } } } return sameStops && (aKey->mBackendType == mBackendType) && (aKey->mExtend == mExtend); } static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } }; /** * This class is what is cached. It need to be allocated in an object separated * to the cache entry to be able to be tracked by the nsExpirationTracker. * */ struct GradientCacheData { GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey) : mStops(aStops), mKey(std::move(aKey)) {} GradientCacheData(GradientCacheData&& aOther) = default; nsExpirationState* GetExpirationState() { return &mExpirationState; } nsExpirationState mExpirationState; const RefPtr mStops; GradientCacheKey mKey; }; /** * This class implements a cache, that retains the GradientStops used to draw * the gradients. * * An entry stays in the cache as long as it is used often and we don't exceed * the maximum, in which case the most recently used will be kept. */ class GradientCache; using GradientCacheMutex = StaticDataMutex>; class MOZ_RAII LockedInstance { public: explicit LockedInstance(GradientCacheMutex& aDataMutex) : mAutoLock(aDataMutex.Lock()) {} UniquePtr& operator->() const& { return mAutoLock.ref(); } UniquePtr& operator->() const&& = delete; UniquePtr& operator*() const& { return mAutoLock.ref(); } UniquePtr& operator*() const&& = delete; explicit operator bool() const { return !!mAutoLock.ref(); } private: GradientCacheMutex::AutoLock mAutoLock; }; class GradientCache final : public ExpirationTrackerImpl { public: GradientCache() : ExpirationTrackerImpl(MAX_GENERATION_MS, "GradientCache") {} static bool EnsureInstance() { LockedInstance lockedInstance(sInstanceMutex); return EnsureInstanceLocked(lockedInstance); } static void DestroyInstance() { LockedInstance lockedInstance(sInstanceMutex); if (lockedInstance) { *lockedInstance = nullptr; } } static void AgeAllGenerations() { LockedInstance lockedInstance(sInstanceMutex); if (!lockedInstance) { return; } lockedInstance->AgeAllGenerationsLocked(lockedInstance); lockedInstance->NotifyHandlerEndLocked(lockedInstance); } template static already_AddRefed LookupOrInsert( const GradientCacheKey& aKey, CreateFunc aCreateFunc) { uint32_t numberOfEntries; RefPtr stops; { LockedInstance lockedInstance(sInstanceMutex); if (!EnsureInstanceLocked(lockedInstance)) { return aCreateFunc(); } GradientCacheData* gradientData = lockedInstance->mHashEntries.Get(aKey); if (gradientData) { if (gradientData->mStops && gradientData->mStops->IsValid()) { lockedInstance->MarkUsedLocked(gradientData, lockedInstance); return do_AddRef(gradientData->mStops); } lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance); lockedInstance->NotifyHandlerEndLocked(lockedInstance); } stops = aCreateFunc(); if (!stops) { return nullptr; } auto data = MakeUnique(stops, GradientCacheKey(&aKey)); nsresult rv = lockedInstance->AddObjectLocked(data.get(), lockedInstance); if (NS_FAILED(rv)) { // We are OOM, and we cannot track this object. We don't want to store // entries in the hash table (since the expiration tracker is // responsible for removing the cache entries), so we avoid putting that // entry in the table, which is a good thing considering we are short on // memory anyway, we probably don't want to retain things. return stops.forget(); } lockedInstance->mHashEntries.InsertOrUpdate(aKey, std::move(data)); numberOfEntries = lockedInstance->mHashEntries.Count(); } if (numberOfEntries > MAX_ENTRIES) { // We have too many entries force the cache to age a generation. NS_DispatchToMainThread( NS_NewRunnableFunction("GradientCache::OnMaxEntriesBreached", [] { LockedInstance lockedInstance(sInstanceMutex); if (!lockedInstance) { return; } lockedInstance->AgeOneGenerationLocked(lockedInstance); lockedInstance->NotifyHandlerEndLocked(lockedInstance); })); } return stops.forget(); } GradientCacheMutex& GetMutex() final { return sInstanceMutex; } void NotifyExpiredLocked(GradientCacheData* aObject, const LockedInstance& aLockedInstance) final { // Remove the gradient from the tracker. RemoveObjectLocked(aObject, aLockedInstance); // If entry exists move the data to mRemovedGradientData because we want to // drop it outside of the lock. Maybe> gradientData = mHashEntries.Extract(aObject->mKey); if (gradientData.isSome()) { mRemovedGradientData.AppendElement(std::move(*gradientData)); } } void NotifyHandlerEndLocked(const LockedInstance&) final { NS_DispatchToMainThread( NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops", [stops = std::move(mRemovedGradientData)] {})); } private: static const uint32_t MAX_GENERATION_MS = 10000; // On Windows some of the Direct2D objects associated with the gradient stops // can be quite large, so we limit the number of cache entries. static const uint32_t MAX_ENTRIES = 4000; static GradientCacheMutex sInstanceMutex; [[nodiscard]] static bool EnsureInstanceLocked( LockedInstance& aLockedInstance) { if (!aLockedInstance) { // GradientCache must be created on the main thread. if (!NS_IsMainThread()) { // This should only happen at shutdown, we fall back to not caching. return false; } *aLockedInstance = MakeUnique(); } return true; } /** * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 */ nsClassHashtable mHashEntries; nsTArray> mRemovedGradientData; }; GradientCacheMutex GradientCache::sInstanceMutex("GradientCache"); void gfxGradientCache::Init() { MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(), "First call must be on main thread."); } already_AddRefed gfxGradientCache::GetOrCreateGradientStops( const DrawTarget* aDT, nsTArray& aStops, ExtendMode aExtend) { if (aDT->IsRecording()) { return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); } return GradientCache::LookupOrInsert( GradientCacheKey(aStops, aExtend, aDT->GetBackendType()), [&]() -> already_AddRefed { return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); }); } void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); } void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); } } // namespace gfx } // namespace mozilla