summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxGradientCache.cpp
blob: c2a1682a7d9edb6ba1612b17ea1a1e02509e4959 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/* -*- 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 <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 CopyableTArray<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) {}

  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<GradientStops> 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<UniquePtr<GradientCache>>;
class MOZ_RAII LockedInstance {
 public:
  explicit LockedInstance(GradientCacheMutex& aDataMutex)
      : mAutoLock(aDataMutex.Lock()) {}
  UniquePtr<GradientCache>& operator->() const& { return mAutoLock.ref(); }
  UniquePtr<GradientCache>& operator->() const&& = delete;
  UniquePtr<GradientCache>& operator*() const& { return mAutoLock.ref(); }
  UniquePtr<GradientCache>& operator*() const&& = delete;
  explicit operator bool() const { return !!mAutoLock.ref(); }

 private:
  GradientCacheMutex::AutoLock mAutoLock;
};

class GradientCache final
    : public ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
                                   LockedInstance> {
 public:
  GradientCache()
      : ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
                              LockedInstance>(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 <typename CreateFunc>
  static already_AddRefed<GradientStops> LookupOrInsert(
      const GradientCacheKey& aKey, CreateFunc aCreateFunc) {
    uint32_t numberOfEntries;
    RefPtr<GradientStops> 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<GradientCacheData>(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<UniquePtr<GradientCacheData>> 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<GradientCache>();
    }
    return true;
  }

  /**
   * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
   * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
   */
  nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
  nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData;
};

GradientCacheMutex GradientCache::sInstanceMutex("GradientCache");

void gfxGradientCache::Init() {
  MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(),
                     "First call must be on main thread.");
}

already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
    const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
  if (aDT->IsRecording()) {
    return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
                                    aExtend);
  }

  return GradientCache::LookupOrInsert(
      GradientCacheKey(aStops, aExtend, aDT->GetBackendType()),
      [&]() -> already_AddRefed<GradientStops> {
        return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
                                        aExtend);
      });
}

void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }

void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }

}  // namespace gfx
}  // namespace mozilla