tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gfxGradientCache.cpp (10630B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "gfxGradientCache.h"
      7 
      8 #include "MainThreadUtils.h"
      9 #include "mozilla/gfx/2D.h"
     10 #include "mozilla/DataMutex.h"
     11 #include "nsTArray.h"
     12 #include "PLDHashTable.h"
     13 #include "nsExpirationTracker.h"
     14 #include "nsClassHashtable.h"
     15 #include <time.h>
     16 
     17 namespace mozilla {
     18 namespace gfx {
     19 
     20 using namespace mozilla;
     21 
     22 struct GradientCacheKey : public PLDHashEntryHdr {
     23  typedef const GradientCacheKey& KeyType;
     24  typedef const GradientCacheKey* KeyTypePointer;
     25  enum { ALLOW_MEMMOVE = true };
     26  const CopyableTArray<GradientStop> mStops;
     27  ExtendMode mExtend;
     28  BackendType mBackendType;
     29 
     30  GradientCacheKey(const nsTArray<GradientStop>& aStops, ExtendMode aExtend,
     31                   BackendType aBackendType)
     32      : mStops(aStops), mExtend(aExtend), mBackendType(aBackendType) {}
     33 
     34  explicit GradientCacheKey(const GradientCacheKey* aOther)
     35      : mStops(aOther->mStops),
     36        mExtend(aOther->mExtend),
     37        mBackendType(aOther->mBackendType) {}
     38 
     39  GradientCacheKey(GradientCacheKey&& aOther) = default;
     40 
     41  union FloatUint32 {
     42    float f;
     43    uint32_t u;
     44  };
     45 
     46  static PLDHashNumber HashKey(const KeyTypePointer aKey) {
     47    PLDHashNumber hash = 0;
     48    FloatUint32 convert;
     49    hash = AddToHash(hash, int(aKey->mBackendType));
     50    hash = AddToHash(hash, int(aKey->mExtend));
     51    for (uint32_t i = 0; i < aKey->mStops.Length(); i++) {
     52      hash = AddToHash(hash, aKey->mStops[i].color.ToABGR());
     53      // Use the float bits as hash, except for the cases of 0.0 and -0.0 which
     54      // both map to 0
     55      convert.f = aKey->mStops[i].offset;
     56      hash = AddToHash(hash, convert.f ? convert.u : 0);
     57    }
     58    return hash;
     59  }
     60 
     61  bool KeyEquals(KeyTypePointer aKey) const {
     62    bool sameStops = true;
     63    if (aKey->mStops.Length() != mStops.Length()) {
     64      sameStops = false;
     65    } else {
     66      for (uint32_t i = 0; i < mStops.Length(); i++) {
     67        if (mStops[i].color.ToABGR() != aKey->mStops[i].color.ToABGR() ||
     68            mStops[i].offset != aKey->mStops[i].offset) {
     69          sameStops = false;
     70          break;
     71        }
     72      }
     73    }
     74 
     75    return sameStops && (aKey->mBackendType == mBackendType) &&
     76           (aKey->mExtend == mExtend);
     77  }
     78  static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
     79 };
     80 
     81 /**
     82 * This class is what is cached. It need to be allocated in an object separated
     83 * to the cache entry to be able to be tracked by the nsExpirationTracker.
     84 * */
     85 struct GradientCacheData {
     86  GradientCacheData(GradientStops* aStops, GradientCacheKey&& aKey)
     87      : mStops(aStops), mKey(std::move(aKey)) {}
     88 
     89  GradientCacheData(GradientCacheData&& aOther) = default;
     90 
     91  nsExpirationState* GetExpirationState() { return &mExpirationState; }
     92 
     93  nsExpirationState mExpirationState;
     94  const RefPtr<GradientStops> mStops;
     95  GradientCacheKey mKey;
     96 };
     97 
     98 /**
     99 * This class implements a cache, that retains the GradientStops used to draw
    100 * the gradients.
    101 *
    102 * An entry stays in the cache as long as it is used often and we don't exceed
    103 * the maximum, in which case the most recently used will be kept.
    104 */
    105 class GradientCache;
    106 using GradientCacheMutex = StaticDataMutex<UniquePtr<GradientCache>>;
    107 class MOZ_RAII LockedInstance {
    108 public:
    109  explicit LockedInstance(GradientCacheMutex& aDataMutex)
    110      : mAutoLock(aDataMutex.Lock()) {}
    111  UniquePtr<GradientCache>& operator->() const& { return mAutoLock.ref(); }
    112  UniquePtr<GradientCache>& operator->() const&& = delete;
    113  UniquePtr<GradientCache>& operator*() const& { return mAutoLock.ref(); }
    114  UniquePtr<GradientCache>& operator*() const&& = delete;
    115  explicit operator bool() const { return !!mAutoLock.ref(); }
    116 
    117 private:
    118  GradientCacheMutex::AutoLock mAutoLock;
    119 };
    120 
    121 class GradientCache final
    122    : public ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
    123                                   LockedInstance> {
    124 public:
    125  GradientCache()
    126      : ExpirationTrackerImpl<GradientCacheData, 4, GradientCacheMutex,
    127                              LockedInstance>(MAX_GENERATION_MS,
    128                                              "GradientCache"_ns) {}
    129  static bool EnsureInstance() {
    130    LockedInstance lockedInstance(sInstanceMutex);
    131    return EnsureInstanceLocked(lockedInstance);
    132  }
    133 
    134  static void DestroyInstance() {
    135    LockedInstance lockedInstance(sInstanceMutex);
    136    if (lockedInstance) {
    137      *lockedInstance = nullptr;
    138    }
    139  }
    140 
    141  static void AgeAllGenerations() {
    142    LockedInstance lockedInstance(sInstanceMutex);
    143    if (!lockedInstance) {
    144      return;
    145    }
    146    lockedInstance->AgeAllGenerationsLocked(lockedInstance);
    147    lockedInstance->NotifyHandlerEndLocked(lockedInstance);
    148  }
    149 
    150  template <typename CreateFunc>
    151  static already_AddRefed<GradientStops> LookupOrInsert(
    152      const GradientCacheKey& aKey, CreateFunc aCreateFunc) {
    153    RefPtr<GradientStops> stops;
    154    bool onMaxEntriesBreached = false;
    155    {
    156      LockedInstance lockedInstance(sInstanceMutex);
    157      if (!EnsureInstanceLocked(lockedInstance)) {
    158        return aCreateFunc();
    159      }
    160 
    161      GradientCacheData* gradientData = lockedInstance->mHashEntries.Get(aKey);
    162      if (gradientData) {
    163        if (gradientData->mStops && gradientData->mStops->IsValid()) {
    164          lockedInstance->MarkUsedLocked(gradientData, lockedInstance);
    165          return do_AddRef(gradientData->mStops);
    166        }
    167 
    168        lockedInstance->NotifyExpiredLocked(gradientData, lockedInstance);
    169        lockedInstance->NotifyHandlerEndLocked(lockedInstance);
    170      }
    171 
    172      stops = aCreateFunc();
    173      if (!stops) {
    174        return nullptr;
    175      }
    176 
    177      auto data = MakeUnique<GradientCacheData>(stops, GradientCacheKey(&aKey));
    178      nsresult rv = lockedInstance->AddObjectLocked(data.get(), lockedInstance);
    179      if (NS_FAILED(rv)) {
    180        // We are OOM, and we cannot track this object. We don't want to store
    181        // entries in the hash table (since the expiration tracker is
    182        // responsible for removing the cache entries), so we avoid putting that
    183        // entry in the table, which is a good thing considering we are short on
    184        // memory anyway, we probably don't want to retain things.
    185        return stops.forget();
    186      }
    187      lockedInstance->mHashEntries.InsertOrUpdate(aKey, std::move(data));
    188      if (lockedInstance->mHashEntries.Count() > MAX_ENTRIES &&
    189          !lockedInstance->mRemovingEntries) {
    190        lockedInstance->mRemovingEntries = true;
    191        onMaxEntriesBreached = true;
    192      }
    193    }
    194 
    195    if (onMaxEntriesBreached) {
    196      // We have too many entries force the cache to age a generation.
    197      NS_DispatchToMainThread(
    198          NS_NewRunnableFunction("GradientCache::OnMaxEntriesBreached", [] {
    199            LockedInstance lockedInstance(sInstanceMutex);
    200            if (!lockedInstance) {
    201              return;
    202            }
    203            if (lockedInstance->mHashEntries.Count() < MAX_ENTRIES) {
    204              lockedInstance->mRemovingEntries = false;
    205              return;
    206            }
    207            while (true) {
    208              uint32_t remainingEntries = lockedInstance->mHashEntries.Count();
    209              lockedInstance->AgeOneGenerationLocked(lockedInstance);
    210              if (lockedInstance->mHashEntries.Count() >= remainingEntries) {
    211                // Stop if there is no progress.
    212                break;
    213              }
    214            }
    215            lockedInstance->NotifyHandlerEndLocked(lockedInstance);
    216            lockedInstance->mRemovingEntries = false;
    217          }));
    218    }
    219 
    220    return stops.forget();
    221  }
    222 
    223  GradientCacheMutex& GetMutex() final { return sInstanceMutex; }
    224 
    225  void NotifyExpiredLocked(GradientCacheData* aObject,
    226                           const LockedInstance& aLockedInstance) final {
    227    // Remove the gradient from the tracker.
    228    RemoveObjectLocked(aObject, aLockedInstance);
    229 
    230    // If entry exists move the data to mRemovedGradientData because we want to
    231    // drop it outside of the lock.
    232    Maybe<UniquePtr<GradientCacheData>> gradientData =
    233        mHashEntries.Extract(aObject->mKey);
    234    if (gradientData.isSome()) {
    235      mRemovedGradientData.AppendElement(std::move(*gradientData));
    236    }
    237  }
    238 
    239  void NotifyHandlerEndLocked(const LockedInstance&) final {
    240    NS_DispatchToMainThread(
    241        NS_NewRunnableFunction("GradientCache::DestroyRemovedGradientStops",
    242                               [stops = std::move(mRemovedGradientData)] {}));
    243  }
    244 
    245 private:
    246  static const uint32_t MAX_GENERATION_MS = 10000;
    247 
    248  // On Windows some of the Direct2D objects associated with the gradient stops
    249  // can be quite large, so we limit the number of cache entries.
    250  static const uint32_t MAX_ENTRIES = 4000;
    251  static GradientCacheMutex sInstanceMutex;
    252 
    253  [[nodiscard]] static bool EnsureInstanceLocked(
    254      LockedInstance& aLockedInstance) {
    255    if (!aLockedInstance) {
    256      // GradientCache must be created on the main thread.
    257      if (!NS_IsMainThread()) {
    258        // This should only happen at shutdown, we fall back to not caching.
    259        return false;
    260      }
    261      *aLockedInstance = MakeUnique<GradientCache>();
    262    }
    263    return true;
    264  }
    265 
    266  /**
    267   * FIXME use nsTHashtable to avoid duplicating the GradientCacheKey.
    268   * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
    269   */
    270  nsClassHashtable<GradientCacheKey, GradientCacheData> mHashEntries;
    271  nsTArray<UniquePtr<GradientCacheData>> mRemovedGradientData;
    272  bool mRemovingEntries = false;
    273 };
    274 
    275 MOZ_RUNINIT GradientCacheMutex GradientCache::sInstanceMutex("GradientCache");
    276 
    277 void gfxGradientCache::Init() {
    278  MOZ_RELEASE_ASSERT(GradientCache::EnsureInstance(),
    279                     "First call must be on main thread.");
    280 }
    281 
    282 already_AddRefed<GradientStops> gfxGradientCache::GetOrCreateGradientStops(
    283    const DrawTarget* aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend) {
    284  if (aDT->IsRecording()) {
    285    return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
    286                                    aExtend);
    287  }
    288 
    289  return GradientCache::LookupOrInsert(
    290      GradientCacheKey(aStops, aExtend, aDT->GetBackendType()),
    291      [&]() -> already_AddRefed<GradientStops> {
    292        return aDT->CreateGradientStops(aStops.Elements(), aStops.Length(),
    293                                        aExtend);
    294      });
    295 }
    296 
    297 void gfxGradientCache::PurgeAllCaches() { GradientCache::AgeAllGenerations(); }
    298 
    299 void gfxGradientCache::Shutdown() { GradientCache::DestroyInstance(); }
    300 
    301 }  // namespace gfx
    302 }  // namespace mozilla