michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/gfx/2D.h" michael@0: #include "nsTArray.h" michael@0: #include "pldhash.h" michael@0: #include "nsExpirationTracker.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "gfxGradientCache.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace gfx { michael@0: michael@0: using namespace mozilla; michael@0: michael@0: struct GradientCacheKey : public PLDHashEntryHdr { michael@0: typedef const GradientCacheKey& KeyType; michael@0: typedef const GradientCacheKey* KeyTypePointer; michael@0: enum { ALLOW_MEMMOVE = true }; michael@0: const nsTArray mStops; michael@0: ExtendMode mExtend; michael@0: BackendType mBackendType; michael@0: michael@0: GradientCacheKey(const nsTArray& aStops, ExtendMode aExtend, BackendType aBackendType) michael@0: : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) michael@0: { } michael@0: michael@0: GradientCacheKey(const GradientCacheKey* aOther) michael@0: : mStops(aOther->mStops), mExtend(aOther->mExtend), mBackendType(aOther->mBackendType) michael@0: { } michael@0: michael@0: union FloatUint32 michael@0: { michael@0: float f; michael@0: uint32_t u; michael@0: }; michael@0: michael@0: static PLDHashNumber michael@0: HashKey(const KeyTypePointer aKey) michael@0: { michael@0: PLDHashNumber hash = 0; michael@0: FloatUint32 convert; michael@0: hash = AddToHash(hash, int(aKey->mBackendType)); michael@0: hash = AddToHash(hash, int(aKey->mExtend)); michael@0: for (uint32_t i = 0; i < aKey->mStops.Length(); i++) { michael@0: hash = AddToHash(hash, aKey->mStops[i].color.ToABGR()); michael@0: // Use the float bits as hash, except for the cases of 0.0 and -0.0 which both map to 0 michael@0: convert.f = aKey->mStops[i].offset; michael@0: hash = AddToHash(hash, convert.f ? convert.u : 0); michael@0: } michael@0: return hash; michael@0: } michael@0: michael@0: bool KeyEquals(KeyTypePointer aKey) const michael@0: { michael@0: bool sameStops = true; michael@0: if (aKey->mStops.Length() != mStops.Length()) { michael@0: sameStops = false; michael@0: } else { michael@0: for (uint32_t i = 0; i < mStops.Length(); i++) { michael@0: if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() || michael@0: mStops[i].offset != aKey->mStops[i].offset) { michael@0: sameStops = false; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return sameStops && michael@0: (aKey->mBackendType == mBackendType) && michael@0: (aKey->mExtend == mExtend); michael@0: } michael@0: static KeyTypePointer KeyToPointer(KeyType aKey) michael@0: { michael@0: return &aKey; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * This class is what is cached. It need to be allocated in an object separated michael@0: * to the cache entry to be able to be tracked by the nsExpirationTracker. michael@0: * */ michael@0: struct GradientCacheData { michael@0: GradientCacheData(GradientStops* aStops, const GradientCacheKey& aKey) michael@0: : mStops(aStops), michael@0: mKey(aKey) michael@0: {} michael@0: michael@0: GradientCacheData(const GradientCacheData& aOther) michael@0: : mStops(aOther.mStops), michael@0: mKey(aOther.mKey) michael@0: { } michael@0: michael@0: nsExpirationState *GetExpirationState() { michael@0: return &mExpirationState; michael@0: } michael@0: michael@0: nsExpirationState mExpirationState; michael@0: const RefPtr mStops; michael@0: GradientCacheKey mKey; michael@0: }; michael@0: michael@0: /** michael@0: * This class implements a cache with no maximum size, that retains the michael@0: * gfxPatterns used to draw the gradients. michael@0: * michael@0: * The key is the nsStyleGradient that defines the gradient, and the size of the michael@0: * gradient. michael@0: * michael@0: * The value is the gfxPattern, and whether or not we perform an optimization michael@0: * based on the actual gradient property. michael@0: * michael@0: * An entry stays in the cache as long as it is used often. As long as a cache michael@0: * entry is in the cache, all the references it has are guaranteed to be valid: michael@0: * the nsStyleRect for the key, the gfxPattern for the value. michael@0: */ michael@0: class GradientCache MOZ_FINAL : public nsExpirationTracker michael@0: { michael@0: public: michael@0: GradientCache() michael@0: : nsExpirationTracker(MAX_GENERATION_MS) michael@0: { michael@0: srand(time(nullptr)); michael@0: mTimerPeriod = rand() % MAX_GENERATION_MS + 1; michael@0: Telemetry::Accumulate(Telemetry::GRADIENT_RETENTION_TIME, mTimerPeriod); michael@0: } michael@0: michael@0: virtual void NotifyExpired(GradientCacheData* aObject) michael@0: { michael@0: // This will free the gfxPattern. michael@0: RemoveObject(aObject); michael@0: mHashEntries.Remove(aObject->mKey); michael@0: } michael@0: michael@0: GradientCacheData* Lookup(const nsTArray& aStops, ExtendMode aExtend, BackendType aBackendType) michael@0: { michael@0: GradientCacheData* gradient = michael@0: mHashEntries.Get(GradientCacheKey(aStops, aExtend, aBackendType)); michael@0: michael@0: if (gradient) { michael@0: MarkUsed(gradient); michael@0: } michael@0: michael@0: return gradient; michael@0: } michael@0: michael@0: // Returns true if we successfully register the gradient in the cache, false michael@0: // otherwise. michael@0: bool RegisterEntry(GradientCacheData* aValue) michael@0: { michael@0: nsresult rv = AddObject(aValue); michael@0: if (NS_FAILED(rv)) { michael@0: // We are OOM, and we cannot track this object. We don't want stall michael@0: // entries in the hash table (since the expiration tracker is responsible michael@0: // for removing the cache entries), so we avoid putting that entry in the michael@0: // table, which is a good things considering we are short on memory michael@0: // anyway, we probably don't want to retain things. michael@0: return false; michael@0: } michael@0: mHashEntries.Put(aValue->mKey, aValue); michael@0: return true; michael@0: } michael@0: michael@0: protected: michael@0: uint32_t mTimerPeriod; michael@0: static const uint32_t MAX_GENERATION_MS = 10000; michael@0: /** michael@0: * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey. michael@0: * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 michael@0: */ michael@0: nsClassHashtable mHashEntries; michael@0: }; michael@0: michael@0: static GradientCache* gGradientCache = nullptr; michael@0: michael@0: GradientStops * michael@0: gfxGradientCache::GetGradientStops(DrawTarget *aDT, nsTArray& aStops, ExtendMode aExtend) michael@0: { michael@0: if (!gGradientCache) { michael@0: gGradientCache = new GradientCache(); michael@0: } michael@0: GradientCacheData* cached = gGradientCache->Lookup(aStops, aExtend, aDT->GetType()); michael@0: return cached ? cached->mStops : nullptr; michael@0: } michael@0: michael@0: GradientStops * michael@0: gfxGradientCache::GetOrCreateGradientStops(DrawTarget *aDT, nsTArray& aStops, ExtendMode aExtend) michael@0: { michael@0: RefPtr gs = GetGradientStops(aDT, aStops, aExtend); michael@0: if (!gs) { michael@0: gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend); michael@0: if (!gs) { michael@0: return nullptr; michael@0: } michael@0: GradientCacheData *cached = new GradientCacheData(gs, GradientCacheKey(aStops, aExtend, aDT->GetType())); michael@0: if (!gGradientCache->RegisterEntry(cached)) { michael@0: delete cached; michael@0: } michael@0: } michael@0: return gs; michael@0: } michael@0: michael@0: void michael@0: gfxGradientCache::Shutdown() michael@0: { michael@0: delete gGradientCache; michael@0: gGradientCache = nullptr; michael@0: } michael@0: michael@0: } michael@0: }