summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxGradientCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/thebes/gfxGradientCache.cpp')
-rw-r--r--gfx/thebes/gfxGradientCache.cpp239
1 files changed, 239 insertions, 0 deletions
diff --git a/gfx/thebes/gfxGradientCache.cpp b/gfx/thebes/gfxGradientCache.cpp
new file mode 100644
index 000000000..fa25bd78c
--- /dev/null
+++ b/gfx/thebes/gfxGradientCache.cpp
@@ -0,0 +1,239 @@
+/* -*- 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 "mozilla/Telemetry.h"
+#include "gfxGradientCache.h"
+#include <time.h>
+
+namespace mozilla {
+namespace gfx {
+
+using namespace mozilla;
+
+struct GradientCacheKey : public PLDHashEntryHdr {
+ typedef const GradientCacheKey& KeyType;
+ typedef const GradientCacheKey* KeyTypePointer;
+ enum { ALLOW_MEMMOVE = true };
+ const nsTArray<GradientStop> mStops;
+ ExtendMode mExtend;
+ BackendType mBackendType;
+
+ GradientCacheKey(const nsTArray<GradientStop>& 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)
+ { }
+
+ 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, const GradientCacheKey& aKey)
+ : mStops(aStops),
+ mKey(aKey)
+ {}
+
+ GradientCacheData(const GradientCacheData& aOther)
+ : mStops(aOther.mStops),
+ mKey(aOther.mKey)
+ { }
+
+ nsExpirationState *GetExpirationState() {
+ return &mExpirationState;
+ }
+
+ nsExpirationState mExpirationState;
+ const RefPtr<GradientStops> 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<GradientCacheData,4>
+{
+ public:
+ GradientCache()
+ : nsExpirationTracker<GradientCacheData,4>(MAX_GENERATION_MS,
+ "GradientCache")
+ {
+ srand(time(nullptr));
+ mTimerPeriod = rand() % MAX_GENERATION_MS + 1;
+ Telemetry::Accumulate(Telemetry::GRADIENT_RETENTION_TIME, mTimerPeriod);
+ }
+
+ virtual void NotifyExpired(GradientCacheData* aObject)
+ {
+ // This will free the gfxPattern.
+ RemoveObject(aObject);
+ mHashEntries.Remove(aObject->mKey);
+ }
+
+ GradientCacheData* Lookup(const nsTArray<GradientStop>& 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:
+ uint32_t mTimerPeriod;
+ 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<GradientCacheKey, GradientCacheData> mHashEntries;
+};
+
+static GradientCache* gGradientCache = nullptr;
+
+GradientStops *
+gfxGradientCache::GetGradientStops(const DrawTarget *aDT, nsTArray<GradientStop>& 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<GradientStops>
+gfxGradientCache::GetOrCreateGradientStops(const DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend)
+{
+ if (aDT->IsRecording()) {
+ return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
+ }
+
+ RefPtr<GradientStops> 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