/* -*- 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 "mozilla/gfx/2D.h" #include "nsTArray.h" #include "PLDHashTable.h" #include "nsExpirationTracker.h" #include "nsClassHashtable.h" #include "gfxGradientCache.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 with no maximum size, that retains the * gfxPatterns used to draw the gradients. * * The key is the nsStyleGradient that defines the gradient, and the size of the * gradient. * * The value is the gfxPattern, and whether or not we perform an optimization * based on the actual gradient property. * * An entry stays in the cache as long as it is used often. As long as a cache * entry is in the cache, all the references it has are guaranteed to be valid: * the nsStyleRect for the key, the gfxPattern for the value. */ class GradientCache final : public nsExpirationTracker { public: GradientCache() : nsExpirationTracker(MAX_GENERATION_MS, "GradientCache") { srand(time(nullptr)); } virtual void NotifyExpired(GradientCacheData* aObject) override { // This will free the gfxPattern. RemoveObject(aObject); mHashEntries.Remove(aObject->mKey); } GradientCacheData* Lookup(const nsTArray& aStops, ExtendMode aExtend, BackendType aBackendType) { GradientCacheData* gradient = mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType)); if (gradient) { MarkUsed(gradient); } return gradient; } // Returns true if we successfully register the gradient in the cache, false // otherwise. bool RegisterEntry(GradientCacheData* aValue) { nsresult rv = AddObject(aValue); if (NS_FAILED(rv)) { // We are OOM, and we cannot track this object. We don't want stall // 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 things considering we are short on memory // anyway, we probably don't want to retain things. return false; } mHashEntries.Put(aValue->mKey, aValue); return true; } protected: static const uint32_t MAX_GENERATION_MS = 10000; /** * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 */ nsClassHashtable mHashEntries; }; static GradientCache* gGradientCache = nullptr; GradientStops* gfxGradientCache::GetGradientStops( const DrawTarget* aDT, nsTArray& aStops, ExtendMode aExtend) { if (!gGradientCache) { gGradientCache = new GradientCache(); } GradientCacheData* cached = gGradientCache->Lookup(aStops, aExtend, aDT->GetBackendType()); if (cached && cached->mStops) { if (!cached->mStops->IsValid()) { gGradientCache->NotifyExpired(cached); } else { return cached->mStops; } } return nullptr; } already_AddRefed gfxGradientCache::GetOrCreateGradientStops( const DrawTarget* aDT, nsTArray& aStops, ExtendMode aExtend) { if (aDT->IsRecording()) { return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); } RefPtr gs = GetGradientStops(aDT, aStops, aExtend); if (!gs) { gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); if (!gs) { return nullptr; } GradientCacheData* cached = new GradientCacheData( gs, GradientCacheKey(aStops, aExtend, aDT->GetBackendType())); if (!gGradientCache->RegisterEntry(cached)) { delete cached; } } return gs.forget(); } void gfxGradientCache::PurgeAllCaches() { if (gGradientCache) { gGradientCache->AgeAllGenerations(); } } void gfxGradientCache::Shutdown() { delete gGradientCache; gGradientCache = nullptr; } } // namespace gfx } // namespace mozilla