LCOV - code coverage report
Current view: top level - image - SurfaceCache.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 263 452 58.2 %
Date: 2017-07-14 16:53:18 Functions: 66 91 72.5 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : /**
       7             :  * SurfaceCache is a service for caching temporary surfaces in imagelib.
       8             :  */
       9             : 
      10             : #include "SurfaceCache.h"
      11             : 
      12             : #include <algorithm>
      13             : #include "mozilla/Assertions.h"
      14             : #include "mozilla/Attributes.h"
      15             : #include "mozilla/DebugOnly.h"
      16             : #include "mozilla/Likely.h"
      17             : #include "mozilla/Move.h"
      18             : #include "mozilla/Pair.h"
      19             : #include "mozilla/RefPtr.h"
      20             : #include "mozilla/StaticMutex.h"
      21             : #include "mozilla/StaticPtr.h"
      22             : #include "mozilla/Tuple.h"
      23             : #include "nsIMemoryReporter.h"
      24             : #include "gfx2DGlue.h"
      25             : #include "gfxPlatform.h"
      26             : #include "gfxPrefs.h"
      27             : #include "imgFrame.h"
      28             : #include "Image.h"
      29             : #include "ISurfaceProvider.h"
      30             : #include "LookupResult.h"
      31             : #include "nsExpirationTracker.h"
      32             : #include "nsHashKeys.h"
      33             : #include "nsRefPtrHashtable.h"
      34             : #include "nsSize.h"
      35             : #include "nsTArray.h"
      36             : #include "prsystem.h"
      37             : #include "ShutdownTracker.h"
      38             : 
      39             : using std::max;
      40             : using std::min;
      41             : 
      42             : namespace mozilla {
      43             : 
      44             : using namespace gfx;
      45             : 
      46             : namespace image {
      47             : 
      48             : class CachedSurface;
      49             : class SurfaceCacheImpl;
      50             : 
      51             : ///////////////////////////////////////////////////////////////////////////////
      52             : // Static Data
      53             : ///////////////////////////////////////////////////////////////////////////////
      54             : 
      55             : // The single surface cache instance.
      56           3 : static StaticRefPtr<SurfaceCacheImpl> sInstance;
      57             : 
      58             : // The mutex protecting the surface cache.
      59           3 : static StaticMutex sInstanceMutex;
      60             : 
      61             : ///////////////////////////////////////////////////////////////////////////////
      62             : // SurfaceCache Implementation
      63             : ///////////////////////////////////////////////////////////////////////////////
      64             : 
      65             : /**
      66             :  * Cost models the cost of storing a surface in the cache. Right now, this is
      67             :  * simply an estimate of the size of the surface in bytes, but in the future it
      68             :  * may be worth taking into account the cost of rematerializing the surface as
      69             :  * well.
      70             :  */
      71             : typedef size_t Cost;
      72             : 
      73             : static Cost
      74          18 : ComputeCost(const IntSize& aSize, uint32_t aBytesPerPixel)
      75             : {
      76          18 :   MOZ_ASSERT(aBytesPerPixel == 1 || aBytesPerPixel == 4);
      77          18 :   return aSize.width * aSize.height * aBytesPerPixel;
      78             : }
      79             : 
      80             : /**
      81             :  * Since we want to be able to make eviction decisions based on cost, we need to
      82             :  * be able to look up the CachedSurface which has a certain cost as well as the
      83             :  * cost associated with a certain CachedSurface. To make this possible, in data
      84             :  * structures we actually store a CostEntry, which contains a weak pointer to
      85             :  * its associated surface.
      86             :  *
      87             :  * To make usage of the weak pointer safe, SurfaceCacheImpl always calls
      88             :  * StartTracking after a surface is stored in the cache and StopTracking before
      89             :  * it is removed.
      90             :  */
      91             : class CostEntry
      92             : {
      93             : public:
      94          60 :   CostEntry(NotNull<CachedSurface*> aSurface, Cost aCost)
      95          60 :     : mSurface(aSurface)
      96          60 :     , mCost(aCost)
      97          60 :   { }
      98             : 
      99           0 :   NotNull<CachedSurface*> Surface() const { return mSurface; }
     100         138 :   Cost GetCost() const { return mCost; }
     101             : 
     102          37 :   bool operator==(const CostEntry& aOther) const
     103             :   {
     104          65 :     return mSurface == aOther.mSurface &&
     105          65 :            mCost == aOther.mCost;
     106             :   }
     107             : 
     108          24 :   bool operator<(const CostEntry& aOther) const
     109             :   {
     110          48 :     return mCost < aOther.mCost ||
     111          61 :            (mCost == aOther.mCost && mSurface < aOther.mSurface);
     112             :   }
     113             : 
     114             : private:
     115             :   NotNull<CachedSurface*> mSurface;
     116             :   Cost                    mCost;
     117             : };
     118             : 
     119             : /**
     120             :  * A CachedSurface associates a surface with a key that uniquely identifies that
     121             :  * surface.
     122             :  */
     123             : class CachedSurface
     124             : {
     125          14 :   ~CachedSurface() { }
     126             : public:
     127             :   MOZ_DECLARE_REFCOUNTED_TYPENAME(CachedSurface)
     128        1214 :   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CachedSurface)
     129             : 
     130          46 :   explicit CachedSurface(NotNull<ISurfaceProvider*> aProvider)
     131          46 :     : mProvider(aProvider)
     132          46 :     , mIsLocked(false)
     133          46 :   { }
     134             : 
     135         113 :   DrawableSurface GetDrawableSurface() const
     136             :   {
     137         113 :     if (MOZ_UNLIKELY(IsPlaceholder())) {
     138           0 :       MOZ_ASSERT_UNREACHABLE("Called GetDrawableSurface() on a placeholder");
     139             :       return DrawableSurface();
     140             :     }
     141             : 
     142         113 :     return mProvider->Surface();
     143             :   }
     144             : 
     145          32 :   void SetLocked(bool aLocked)
     146             :   {
     147          32 :     if (IsPlaceholder()) {
     148           0 :       return;  // Can't lock a placeholder.
     149             :     }
     150             : 
     151             :     // Update both our state and our provider's state. Some surface providers
     152             :     // are permanently locked; maintaining our own locking state enables us to
     153             :     // respect SetLocked() even when it's meaningless from the provider's
     154             :     // perspective.
     155          32 :     mIsLocked = aLocked;
     156          32 :     mProvider->SetLocked(aLocked);
     157             :   }
     158             : 
     159         237 :   bool IsLocked() const
     160             :   {
     161         237 :     return !IsPlaceholder() && mIsLocked && mProvider->IsLocked();
     162             :   }
     163             : 
     164         730 :   bool IsPlaceholder() const { return mProvider->Availability().IsPlaceholder(); }
     165          24 :   bool IsDecoded() const { return !IsPlaceholder() && mProvider->IsFinished(); }
     166             : 
     167          14 :   ImageKey GetImageKey() const { return mProvider->GetImageKey(); }
     168         187 :   SurfaceKey GetSurfaceKey() const { return mProvider->GetSurfaceKey(); }
     169          56 :   nsExpirationState* GetExpirationState() { return &mExpirationState; }
     170             : 
     171          60 :   CostEntry GetCostEntry()
     172             :   {
     173          60 :     return image::CostEntry(WrapNotNull(this), mProvider->LogicalSizeInBytes());
     174             :   }
     175             : 
     176             :   // A helper type used by SurfaceCacheImpl::CollectSizeOfSurfaces.
     177             :   struct MOZ_STACK_CLASS SurfaceMemoryReport
     178             :   {
     179           0 :     SurfaceMemoryReport(nsTArray<SurfaceMemoryCounter>& aCounters,
     180             :                         MallocSizeOf                    aMallocSizeOf)
     181           0 :       : mCounters(aCounters)
     182           0 :       , mMallocSizeOf(aMallocSizeOf)
     183           0 :     { }
     184             : 
     185           0 :     void Add(NotNull<CachedSurface*> aCachedSurface)
     186             :     {
     187           0 :       SurfaceMemoryCounter counter(aCachedSurface->GetSurfaceKey(),
     188           0 :                                    aCachedSurface->IsLocked());
     189             : 
     190           0 :       if (aCachedSurface->IsPlaceholder()) {
     191           0 :         return;
     192             :       }
     193             : 
     194             :       // Record the memory used by the ISurfaceProvider. This may not have a
     195             :       // straightforward relationship to the size of the surface that
     196             :       // DrawableRef() returns if the surface is generated dynamically. (i.e.,
     197             :       // for surfaces with PlaybackType::eAnimated.)
     198           0 :       size_t heap = 0;
     199           0 :       size_t nonHeap = 0;
     200           0 :       size_t handles = 0;
     201           0 :       aCachedSurface->mProvider
     202           0 :         ->AddSizeOfExcludingThis(mMallocSizeOf, heap, nonHeap, handles);
     203           0 :       counter.Values().SetDecodedHeap(heap);
     204           0 :       counter.Values().SetDecodedNonHeap(nonHeap);
     205           0 :       counter.Values().SetSharedHandles(handles);
     206             : 
     207           0 :       mCounters.AppendElement(counter);
     208             :     }
     209             : 
     210             :   private:
     211             :     nsTArray<SurfaceMemoryCounter>& mCounters;
     212             :     MallocSizeOf                    mMallocSizeOf;
     213             :   };
     214             : 
     215             : private:
     216             :   nsExpirationState                 mExpirationState;
     217             :   NotNull<RefPtr<ISurfaceProvider>> mProvider;
     218             :   bool                              mIsLocked;
     219             : };
     220             : 
     221             : static int64_t
     222           0 : AreaOfIntSize(const IntSize& aSize) {
     223           0 :   return static_cast<int64_t>(aSize.width) * static_cast<int64_t>(aSize.height);
     224             : }
     225             : 
     226             : /**
     227             :  * An ImageSurfaceCache is a per-image surface cache. For correctness we must be
     228             :  * able to remove all surfaces associated with an image when the image is
     229             :  * destroyed or invalidated. Since this will happen frequently, it makes sense
     230             :  * to make it cheap by storing the surfaces for each image separately.
     231             :  *
     232             :  * ImageSurfaceCache also keeps track of whether its associated image is locked
     233             :  * or unlocked.
     234             :  */
     235             : class ImageSurfaceCache
     236             : {
     237           0 :   ~ImageSurfaceCache() { }
     238             : public:
     239          40 :   ImageSurfaceCache() : mLocked(false) { }
     240             : 
     241             :   MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
     242        1588 :   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache)
     243             : 
     244             :   typedef
     245             :     nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
     246             : 
     247          14 :   bool IsEmpty() const { return mSurfaces.Count() == 0; }
     248             : 
     249          46 :   void Insert(NotNull<CachedSurface*> aSurface)
     250             :   {
     251          46 :     MOZ_ASSERT(!mLocked || aSurface->IsPlaceholder() || aSurface->IsLocked(),
     252             :                "Inserting an unlocked surface for a locked image");
     253          46 :     mSurfaces.Put(aSurface->GetSurfaceKey(), aSurface);
     254          46 :   }
     255             : 
     256          14 :   void Remove(NotNull<CachedSurface*> aSurface)
     257             :   {
     258          14 :     MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()),
     259             :         "Should not be removing a surface we don't have");
     260             : 
     261          14 :     mSurfaces.Remove(aSurface->GetSurfaceKey());
     262          14 :   }
     263             : 
     264         183 :   already_AddRefed<CachedSurface> Lookup(const SurfaceKey& aSurfaceKey)
     265             :   {
     266         366 :     RefPtr<CachedSurface> surface;
     267         183 :     mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface));
     268         366 :     return surface.forget();
     269             :   }
     270             : 
     271             :   Pair<already_AddRefed<CachedSurface>, MatchType>
     272          24 :   LookupBestMatch(const SurfaceKey& aIdealKey)
     273             :   {
     274             :     // Try for an exact match first.
     275          48 :     RefPtr<CachedSurface> exactMatch;
     276          24 :     mSurfaces.Get(aIdealKey, getter_AddRefs(exactMatch));
     277          24 :     if (exactMatch && exactMatch->IsDecoded()) {
     278          24 :       return MakePair(exactMatch.forget(), MatchType::EXACT);
     279             :     }
     280             : 
     281             :     // There's no perfect match, so find the best match we can.
     282           0 :     RefPtr<CachedSurface> bestMatch;
     283           0 :     for (auto iter = ConstIter(); !iter.Done(); iter.Next()) {
     284           0 :       NotNull<CachedSurface*> current = WrapNotNull(iter.UserData());
     285           0 :       const SurfaceKey& currentKey = current->GetSurfaceKey();
     286             : 
     287             :       // We never match a placeholder.
     288           0 :       if (current->IsPlaceholder()) {
     289           0 :         continue;
     290             :       }
     291             :       // Matching the playback type and SVG context is required.
     292           0 :       if (currentKey.Playback() != aIdealKey.Playback() ||
     293           0 :           currentKey.SVGContext() != aIdealKey.SVGContext()) {
     294           0 :         continue;
     295             :       }
     296             :       // Matching the flags is required.
     297           0 :       if (currentKey.Flags() != aIdealKey.Flags()) {
     298           0 :         continue;
     299             :       }
     300             :       // Anything is better than nothing! (Within the constraints we just
     301             :       // checked, of course.)
     302           0 :       if (!bestMatch) {
     303           0 :         bestMatch = current;
     304           0 :         continue;
     305             :       }
     306             : 
     307           0 :       MOZ_ASSERT(bestMatch, "Should have a current best match");
     308             : 
     309             :       // Always prefer completely decoded surfaces.
     310           0 :       bool bestMatchIsDecoded = bestMatch->IsDecoded();
     311           0 :       if (bestMatchIsDecoded && !current->IsDecoded()) {
     312           0 :         continue;
     313             :       }
     314           0 :       if (!bestMatchIsDecoded && current->IsDecoded()) {
     315           0 :         bestMatch = current;
     316           0 :         continue;
     317             :       }
     318             : 
     319           0 :       SurfaceKey bestMatchKey = bestMatch->GetSurfaceKey();
     320             : 
     321             :       // Compare sizes. We use an area-based heuristic here instead of computing a
     322             :       // truly optimal answer, since it seems very unlikely to make a difference
     323             :       // for realistic sizes.
     324           0 :       int64_t idealArea = AreaOfIntSize(aIdealKey.Size());
     325           0 :       int64_t currentArea = AreaOfIntSize(currentKey.Size());
     326           0 :       int64_t bestMatchArea = AreaOfIntSize(bestMatchKey.Size());
     327             : 
     328             :       // If the best match is smaller than the ideal size, prefer bigger sizes.
     329           0 :       if (bestMatchArea < idealArea) {
     330           0 :         if (currentArea > bestMatchArea) {
     331           0 :           bestMatch = current;
     332             :         }
     333           0 :         continue;
     334             :       }
     335             :       // Other, prefer sizes closer to the ideal size, but still not smaller.
     336           0 :       if (idealArea <= currentArea && currentArea < bestMatchArea) {
     337           0 :         bestMatch = current;
     338           0 :         continue;
     339             :       }
     340             :       // This surface isn't an improvement over the current best match.
     341             :     }
     342             : 
     343             :     MatchType matchType;
     344           0 :     if (bestMatch) {
     345           0 :       if (!exactMatch) {
     346             :         // No exact match, but we found a substitute.
     347           0 :         matchType = MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND;
     348           0 :       } else if (exactMatch != bestMatch) {
     349             :         // The exact match is still decoding, but we found a substitute.
     350           0 :         matchType = MatchType::SUBSTITUTE_BECAUSE_PENDING;
     351             :       } else {
     352             :         // The exact match is still decoding, but it's the best we've got.
     353           0 :         matchType = MatchType::EXACT;
     354             :       }
     355             :     } else {
     356           0 :       if (exactMatch) {
     357             :         // We found an "exact match"; it must have been a placeholder.
     358           0 :         MOZ_ASSERT(exactMatch->IsPlaceholder());
     359           0 :         matchType = MatchType::PENDING;
     360             :       } else {
     361             :         // We couldn't find an exact match *or* a substitute.
     362           0 :         matchType = MatchType::NOT_FOUND;
     363             :       }
     364             :     }
     365             : 
     366           0 :     return MakePair(bestMatch.forget(), matchType);
     367             :   }
     368             : 
     369          32 :   SurfaceTable::Iterator ConstIter() const
     370             :   {
     371          32 :     return mSurfaces.ConstIter();
     372             :   }
     373             : 
     374          40 :   void SetLocked(bool aLocked) { mLocked = aLocked; }
     375         205 :   bool IsLocked() const { return mLocked; }
     376             : 
     377             : private:
     378             :   SurfaceTable mSurfaces;
     379             :   bool         mLocked;
     380             : };
     381             : 
     382             : /**
     383             :  * SurfaceCacheImpl is responsible for determining which surfaces will be cached
     384             :  * and managing the surface cache data structures. Rather than interact with
     385             :  * SurfaceCacheImpl directly, client code interacts with SurfaceCache, which
     386             :  * maintains high-level invariants and encapsulates the details of the surface
     387             :  * cache's implementation.
     388             :  */
     389             : class SurfaceCacheImpl final : public nsIMemoryReporter
     390             : {
     391             : public:
     392             :   NS_DECL_ISUPPORTS
     393             : 
     394           3 :   SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS,
     395             :                    uint32_t aSurfaceCacheDiscardFactor,
     396             :                    uint32_t aSurfaceCacheSize)
     397           3 :     : mExpirationTracker(aSurfaceCacheExpirationTimeMS)
     398           3 :     , mMemoryPressureObserver(new MemoryPressureObserver)
     399             :     , mDiscardFactor(aSurfaceCacheDiscardFactor)
     400             :     , mMaxCost(aSurfaceCacheSize)
     401             :     , mAvailableCost(aSurfaceCacheSize)
     402             :     , mLockedCost(0)
     403           6 :     , mOverflowCount(0)
     404             :   {
     405           6 :     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     406           3 :     if (os) {
     407           3 :       os->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
     408             :     }
     409           3 :   }
     410             : 
     411             : private:
     412           0 :   virtual ~SurfaceCacheImpl()
     413           0 :   {
     414           0 :     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     415           0 :     if (os) {
     416           0 :       os->RemoveObserver(mMemoryPressureObserver, "memory-pressure");
     417             :     }
     418             : 
     419           0 :     UnregisterWeakMemoryReporter(this);
     420           0 :   }
     421             : 
     422             : public:
     423           3 :   void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
     424             : 
     425          46 :   InsertOutcome Insert(NotNull<ISurfaceProvider*> aProvider,
     426             :                        bool                       aSetAvailable,
     427             :                        const StaticMutexAutoLock& aAutoLock)
     428             :   {
     429             :     // If this is a duplicate surface, refuse to replace the original.
     430             :     // XXX(seth): Calling Lookup() and then RemoveEntry() does the lookup
     431             :     // twice. We'll make this more efficient in bug 1185137.
     432          46 :     LookupResult result = Lookup(aProvider->GetImageKey(),
     433          46 :                                  aProvider->GetSurfaceKey(),
     434             :                                  aAutoLock,
     435         138 :                                  /* aMarkUsed = */ false);
     436          46 :     if (MOZ_UNLIKELY(result)) {
     437           0 :       return InsertOutcome::FAILURE_ALREADY_PRESENT;
     438             :     }
     439             : 
     440          46 :     if (result.Type() == MatchType::PENDING) {
     441          14 :       RemoveEntry(aProvider->GetImageKey(), aProvider->GetSurfaceKey(), aAutoLock);
     442             :     }
     443             : 
     444          46 :     MOZ_ASSERT(result.Type() == MatchType::NOT_FOUND ||
     445             :                result.Type() == MatchType::PENDING,
     446             :                "A LookupResult with no surface should be NOT_FOUND or PENDING");
     447             : 
     448             :     // If this is bigger than we can hold after discarding everything we can,
     449             :     // refuse to cache it.
     450          46 :     Cost cost = aProvider->LogicalSizeInBytes();
     451          46 :     if (MOZ_UNLIKELY(!CanHoldAfterDiscarding(cost))) {
     452           0 :       mOverflowCount++;
     453           0 :       return InsertOutcome::FAILURE;
     454             :     }
     455             : 
     456             :     // Remove elements in order of cost until we can fit this in the cache. Note
     457             :     // that locked surfaces aren't in mCosts, so we never remove them here.
     458           0 :     while (cost > mAvailableCost) {
     459           0 :       MOZ_ASSERT(!mCosts.IsEmpty(),
     460             :                  "Removed everything and it still won't fit");
     461           0 :       Remove(mCosts.LastElement().Surface(), aAutoLock);
     462             :     }
     463             : 
     464             :     // Locate the appropriate per-image cache. If there's not an existing cache
     465             :     // for this image, create it.
     466          92 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aProvider->GetImageKey());
     467          46 :     if (!cache) {
     468           0 :       cache = new ImageSurfaceCache;
     469           0 :       mImageCaches.Put(aProvider->GetImageKey(), cache);
     470             :     }
     471             : 
     472             :     // If we were asked to mark the cache entry available, do so.
     473          46 :     if (aSetAvailable) {
     474          14 :       aProvider->Availability().SetAvailable();
     475             :     }
     476             : 
     477             :     NotNull<RefPtr<CachedSurface>> surface =
     478         138 :       WrapNotNull(new CachedSurface(aProvider));
     479             : 
     480             :     // We require that locking succeed if the image is locked and we're not
     481             :     // inserting a placeholder; the caller may need to know this to handle
     482             :     // errors correctly.
     483          46 :     if (cache->IsLocked() && !surface->IsPlaceholder()) {
     484          32 :       surface->SetLocked(true);
     485          32 :       if (!surface->IsLocked()) {
     486           0 :         return InsertOutcome::FAILURE;
     487             :       }
     488             :     }
     489             : 
     490             :     // Insert.
     491          46 :     MOZ_ASSERT(cost <= mAvailableCost, "Inserting despite too large a cost");
     492          46 :     cache->Insert(surface);
     493          46 :     StartTracking(surface, aAutoLock);
     494             : 
     495          46 :     return InsertOutcome::SUCCESS;
     496             :   }
     497             : 
     498          14 :   void Remove(NotNull<CachedSurface*> aSurface,
     499             :               const StaticMutexAutoLock& aAutoLock)
     500             :   {
     501          14 :     ImageKey imageKey = aSurface->GetImageKey();
     502             : 
     503          28 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
     504          14 :     MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
     505             : 
     506             :     // If the surface was not a placeholder, tell its image that we discarded it.
     507          14 :     if (!aSurface->IsPlaceholder()) {
     508           0 :       static_cast<Image*>(imageKey)->OnSurfaceDiscarded(aSurface->GetSurfaceKey());
     509             :     }
     510             : 
     511          14 :     StopTracking(aSurface, aAutoLock);
     512          14 :     cache->Remove(aSurface);
     513             : 
     514             :     // Remove the per-image cache if it's unneeded now. (Keep it if the image is
     515             :     // locked, since the per-image cache is where we store that state.)
     516          14 :     if (cache->IsEmpty() && !cache->IsLocked()) {
     517           0 :       mImageCaches.Remove(imageKey);
     518             :     }
     519          14 :   }
     520             : 
     521          46 :   void StartTracking(NotNull<CachedSurface*> aSurface,
     522             :                      const StaticMutexAutoLock& aAutoLock)
     523             :   {
     524          46 :     CostEntry costEntry = aSurface->GetCostEntry();
     525          46 :     MOZ_ASSERT(costEntry.GetCost() <= mAvailableCost,
     526             :                "Cost too large and the caller didn't catch it");
     527             : 
     528          46 :     mAvailableCost -= costEntry.GetCost();
     529             : 
     530          46 :     if (aSurface->IsLocked()) {
     531          32 :       mLockedCost += costEntry.GetCost();
     532          32 :       MOZ_ASSERT(mLockedCost <= mMaxCost, "Locked more than we can hold?");
     533             :     } else {
     534          14 :       mCosts.InsertElementSorted(costEntry);
     535             :       // This may fail during XPCOM shutdown, so we need to ensure the object is
     536             :       // tracked before calling RemoveObject in StopTracking.
     537          14 :       mExpirationTracker.AddObjectLocked(aSurface, aAutoLock);
     538             :     }
     539          46 :   }
     540             : 
     541          14 :   void StopTracking(NotNull<CachedSurface*> aSurface,
     542             :                     const StaticMutexAutoLock& aAutoLock)
     543             :   {
     544          14 :     CostEntry costEntry = aSurface->GetCostEntry();
     545             : 
     546          14 :     if (aSurface->IsLocked()) {
     547           0 :       MOZ_ASSERT(mLockedCost >= costEntry.GetCost(), "Costs don't balance");
     548           0 :       mLockedCost -= costEntry.GetCost();
     549             :       // XXX(seth): It'd be nice to use an O(log n) lookup here. This is O(n).
     550           0 :       MOZ_ASSERT(!mCosts.Contains(costEntry),
     551             :                  "Shouldn't have a cost entry for a locked surface");
     552             :     } else {
     553          14 :       if (MOZ_LIKELY(aSurface->GetExpirationState()->IsTracked())) {
     554          14 :         mExpirationTracker.RemoveObjectLocked(aSurface, aAutoLock);
     555             :       } else {
     556             :         // Our call to AddObject must have failed in StartTracking; most likely
     557             :         // we're in XPCOM shutdown right now.
     558           0 :         NS_ASSERTION(ShutdownTracker::ShutdownHasStarted(),
     559             :                      "Not expiration-tracking an unlocked surface!");
     560             :       }
     561             : 
     562          28 :       DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
     563          14 :       MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
     564             :     }
     565             : 
     566          14 :     mAvailableCost += costEntry.GetCost();
     567          14 :     MOZ_ASSERT(mAvailableCost <= mMaxCost,
     568             :                "More available cost than we started with");
     569          14 :   }
     570             : 
     571         169 :   LookupResult Lookup(const ImageKey    aImageKey,
     572             :                       const SurfaceKey& aSurfaceKey,
     573             :                       const StaticMutexAutoLock& aAutoLock,
     574             :                       bool aMarkUsed = true)
     575             :   {
     576         338 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     577         169 :     if (!cache) {
     578             :       // No cached surfaces for this image.
     579           0 :       return LookupResult(MatchType::NOT_FOUND);
     580             :     }
     581             : 
     582         338 :     RefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
     583         169 :     if (!surface) {
     584             :       // Lookup in the per-image cache missed.
     585          64 :       return LookupResult(MatchType::NOT_FOUND);
     586             :     }
     587             : 
     588         105 :     if (surface->IsPlaceholder()) {
     589          16 :       return LookupResult(MatchType::PENDING);
     590             :     }
     591             : 
     592         178 :     DrawableSurface drawableSurface = surface->GetDrawableSurface();
     593          89 :     if (!drawableSurface) {
     594             :       // The surface was released by the operating system. Remove the cache
     595             :       // entry as well.
     596           0 :       Remove(WrapNotNull(surface), aAutoLock);
     597           0 :       return LookupResult(MatchType::NOT_FOUND);
     598             :     }
     599             : 
     600          89 :     if (aMarkUsed) {
     601          89 :       MarkUsed(WrapNotNull(surface), WrapNotNull(cache), aAutoLock);
     602             :     }
     603             : 
     604          89 :     MOZ_ASSERT(surface->GetSurfaceKey() == aSurfaceKey,
     605             :                "Lookup() not returning an exact match?");
     606          89 :     return LookupResult(Move(drawableSurface), MatchType::EXACT);
     607             :   }
     608             : 
     609          24 :   LookupResult LookupBestMatch(const ImageKey         aImageKey,
     610             :                                const SurfaceKey&      aSurfaceKey,
     611             :                                const StaticMutexAutoLock& aAutoLock)
     612             :   {
     613          48 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     614          24 :     if (!cache) {
     615             :       // No cached surfaces for this image.
     616           0 :       return LookupResult(MatchType::NOT_FOUND);
     617             :     }
     618             : 
     619             :     // Repeatedly look up the best match, trying again if the resulting surface
     620             :     // has been freed by the operating system, until we can either lock a
     621             :     // surface for drawing or there are no matching surfaces left.
     622             :     // XXX(seth): This is O(N^2), but N is expected to be very small. If we
     623             :     // encounter a performance problem here we can revisit this.
     624             : 
     625          48 :     RefPtr<CachedSurface> surface;
     626          48 :     DrawableSurface drawableSurface;
     627          24 :     MatchType matchType = MatchType::NOT_FOUND;
     628             :     while (true) {
     629          24 :       Tie(surface, matchType) = cache->LookupBestMatch(aSurfaceKey);
     630             : 
     631          24 :       if (!surface) {
     632           0 :         return LookupResult(matchType);  // Lookup in the per-image cache missed.
     633             :       }
     634             : 
     635          24 :       drawableSurface = surface->GetDrawableSurface();
     636          24 :       if (drawableSurface) {
     637          24 :         break;
     638             :       }
     639             : 
     640             :       // The surface was released by the operating system. Remove the cache
     641             :       // entry as well.
     642           0 :       Remove(WrapNotNull(surface), aAutoLock);
     643             :     }
     644             : 
     645          24 :     MOZ_ASSERT_IF(matchType == MatchType::EXACT,
     646             :                   surface->GetSurfaceKey() == aSurfaceKey);
     647          24 :     MOZ_ASSERT_IF(matchType == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND ||
     648             :                   matchType == MatchType::SUBSTITUTE_BECAUSE_PENDING,
     649             :       surface->GetSurfaceKey().SVGContext() == aSurfaceKey.SVGContext() &&
     650             :       surface->GetSurfaceKey().Playback() == aSurfaceKey.Playback() &&
     651             :       surface->GetSurfaceKey().Flags() == aSurfaceKey.Flags());
     652             : 
     653          24 :     if (matchType == MatchType::EXACT) {
     654          24 :       MarkUsed(WrapNotNull(surface), WrapNotNull(cache), aAutoLock);
     655             :     }
     656             : 
     657          24 :     return LookupResult(Move(drawableSurface), matchType);
     658             :   }
     659             : 
     660          56 :   bool CanHold(const Cost aCost) const
     661             :   {
     662          56 :     return aCost <= mMaxCost;
     663             :   }
     664             : 
     665          31 :   size_t MaximumCapacity() const
     666             :   {
     667          31 :     return size_t(mMaxCost);
     668             :   }
     669             : 
     670          14 :   void SurfaceAvailable(NotNull<ISurfaceProvider*> aProvider,
     671             :                         const StaticMutexAutoLock& aAutoLock)
     672             :   {
     673          14 :     if (!aProvider->Availability().IsPlaceholder()) {
     674           0 :       MOZ_ASSERT_UNREACHABLE("Calling SurfaceAvailable on non-placeholder");
     675             :       return;
     676             :     }
     677             : 
     678             :     // Reinsert the provider, requesting that Insert() mark it available. This
     679             :     // may or may not succeed, depending on whether some other decoder has
     680             :     // beaten us to the punch and inserted a non-placeholder version of this
     681             :     // surface first, but it's fine either way.
     682             :     // XXX(seth): This could be implemented more efficiently; we should be able
     683             :     // to just update our data structures without reinserting.
     684          14 :     Insert(aProvider, /* aSetAvailable = */ true, aAutoLock);
     685          14 :   }
     686             : 
     687          40 :   void LockImage(const ImageKey aImageKey)
     688             :   {
     689          80 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     690          40 :     if (!cache) {
     691          40 :       cache = new ImageSurfaceCache;
     692          40 :       mImageCaches.Put(aImageKey, cache);
     693             :     }
     694             : 
     695          40 :     cache->SetLocked(true);
     696             : 
     697             :     // We don't relock this image's existing surfaces right away; instead, the
     698             :     // image should arrange for Lookup() to touch them if they are still useful.
     699          40 :   }
     700             : 
     701           0 :   void UnlockImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
     702             :   {
     703           0 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     704           0 :     if (!cache || !cache->IsLocked()) {
     705           0 :       return;  // Already unlocked.
     706             :     }
     707             : 
     708           0 :     cache->SetLocked(false);
     709           0 :     DoUnlockSurfaces(WrapNotNull(cache), /* aStaticOnly = */ false, aAutoLock);
     710             :   }
     711             : 
     712          32 :   void UnlockEntries(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
     713             :   {
     714          64 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     715          32 :     if (!cache || !cache->IsLocked()) {
     716           0 :       return;  // Already unlocked.
     717             :     }
     718             : 
     719             :     // (Note that we *don't* unlock the per-image cache here; that's the
     720             :     // difference between this and UnlockImage.)
     721          64 :     DoUnlockSurfaces(WrapNotNull(cache),
     722          64 :       /* aStaticOnly = */ !gfxPrefs::ImageMemAnimatedDiscardable(), aAutoLock);
     723             :   }
     724             : 
     725           1 :   void RemoveImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
     726             :   {
     727           1 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     728           1 :     if (!cache) {
     729           1 :       return;  // No cached surfaces for this image, so nothing to do.
     730             :     }
     731             : 
     732             :     // Discard all of the cached surfaces for this image.
     733             :     // XXX(seth): This is O(n^2) since for each item in the cache we are
     734             :     // removing an element from the costs array. Since n is expected to be
     735             :     // small, performance should be good, but if usage patterns change we should
     736             :     // change the data structure used for mCosts.
     737           0 :     for (auto iter = cache->ConstIter(); !iter.Done(); iter.Next()) {
     738           0 :       StopTracking(WrapNotNull(iter.UserData()), aAutoLock);
     739             :     }
     740             : 
     741             :     // The per-image cache isn't needed anymore, so remove it as well.
     742             :     // This implicitly unlocks the image if it was locked.
     743           0 :     mImageCaches.Remove(aImageKey);
     744             :   }
     745             : 
     746           0 :   void DiscardAll(const StaticMutexAutoLock& aAutoLock)
     747             :   {
     748             :     // Remove in order of cost because mCosts is an array and the other data
     749             :     // structures are all hash tables. Note that locked surfaces are not
     750             :     // removed, since they aren't present in mCosts.
     751           0 :     while (!mCosts.IsEmpty()) {
     752           0 :       Remove(mCosts.LastElement().Surface(), aAutoLock);
     753             :     }
     754           0 :   }
     755             : 
     756           0 :   void DiscardForMemoryPressure(const StaticMutexAutoLock& aAutoLock)
     757             :   {
     758             :     // Compute our discardable cost. Since locked surfaces aren't discardable,
     759             :     // we exclude them.
     760           0 :     const Cost discardableCost = (mMaxCost - mAvailableCost) - mLockedCost;
     761           0 :     MOZ_ASSERT(discardableCost <= mMaxCost, "Discardable cost doesn't add up");
     762             : 
     763             :     // Our target is to raise our available cost by (1 / mDiscardFactor) of our
     764             :     // discardable cost - in other words, we want to end up with about
     765             :     // (discardableCost / mDiscardFactor) fewer bytes stored in the surface
     766             :     // cache after we're done.
     767           0 :     const Cost targetCost = mAvailableCost + (discardableCost / mDiscardFactor);
     768             : 
     769           0 :     if (targetCost > mMaxCost - mLockedCost) {
     770           0 :       MOZ_ASSERT_UNREACHABLE("Target cost is more than we can discard");
     771             :       DiscardAll(aAutoLock);
     772             :       return;
     773             :     }
     774             : 
     775             :     // Discard surfaces until we've reduced our cost to our target cost.
     776           0 :     while (mAvailableCost < targetCost) {
     777           0 :       MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and still not done");
     778           0 :       Remove(mCosts.LastElement().Surface(), aAutoLock);
     779             :     }
     780             :   }
     781             : 
     782         113 :   void LockSurface(NotNull<CachedSurface*> aSurface,
     783             :                    const StaticMutexAutoLock& aAutoLock)
     784             :   {
     785         113 :     if (aSurface->IsPlaceholder() || aSurface->IsLocked()) {
     786         113 :       return;
     787             :     }
     788             : 
     789           0 :     StopTracking(aSurface, aAutoLock);
     790             : 
     791             :     // Lock the surface. This can fail.
     792           0 :     aSurface->SetLocked(true);
     793           0 :     StartTracking(aSurface, aAutoLock);
     794             :   }
     795             : 
     796             :   NS_IMETHOD
     797           0 :   CollectReports(nsIHandleReportCallback* aHandleReport,
     798             :                  nsISupports*             aData,
     799             :                  bool                     aAnonymize) override
     800             :   {
     801           0 :     StaticMutexAutoLock lock(sInstanceMutex);
     802             : 
     803             :     // We have explicit memory reporting for the surface cache which is more
     804             :     // accurate than the cost metrics we report here, but these metrics are
     805             :     // still useful to report, since they control the cache's behavior.
     806           0 :     MOZ_COLLECT_REPORT(
     807             :       "imagelib-surface-cache-estimated-total",
     808             :       KIND_OTHER, UNITS_BYTES, (mMaxCost - mAvailableCost),
     809           0 : "Estimated total memory used by the imagelib surface cache.");
     810             : 
     811           0 :     MOZ_COLLECT_REPORT(
     812             :       "imagelib-surface-cache-estimated-locked",
     813             :       KIND_OTHER, UNITS_BYTES, mLockedCost,
     814           0 : "Estimated memory used by locked surfaces in the imagelib surface cache.");
     815             : 
     816           0 :     MOZ_COLLECT_REPORT(
     817             :       "imagelib-surface-cache-overflow-count",
     818             :       KIND_OTHER, UNITS_COUNT, mOverflowCount,
     819             : "Count of how many times the surface cache has hit its capacity and been "
     820           0 : "unable to insert a new surface.");
     821             : 
     822           0 :     return NS_OK;
     823             :   }
     824             : 
     825           0 :   void CollectSizeOfSurfaces(const ImageKey                  aImageKey,
     826             :                              nsTArray<SurfaceMemoryCounter>& aCounters,
     827             :                              MallocSizeOf                    aMallocSizeOf)
     828             :   {
     829           0 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     830           0 :     if (!cache) {
     831           0 :       return;  // No surfaces for this image.
     832             :     }
     833             : 
     834             :     // Report all surfaces in the per-image cache.
     835           0 :     CachedSurface::SurfaceMemoryReport report(aCounters, aMallocSizeOf);
     836           0 :     for (auto iter = cache->ConstIter(); !iter.Done(); iter.Next()) {
     837           0 :       report.Add(WrapNotNull(iter.UserData()));
     838             :     }
     839             :   }
     840             : 
     841             : private:
     842         340 :   already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
     843             :   {
     844         680 :     RefPtr<ImageSurfaceCache> imageCache;
     845         340 :     mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
     846         680 :     return imageCache.forget();
     847             :   }
     848             : 
     849             :   // This is similar to CanHold() except that it takes into account the costs of
     850             :   // locked surfaces. It's used internally in Insert(), but it's not exposed
     851             :   // publicly because we permit multithreaded access to the surface cache, which
     852             :   // means that the result would be meaningless: another thread could insert a
     853             :   // surface or lock an image at any time.
     854          46 :   bool CanHoldAfterDiscarding(const Cost aCost) const
     855             :   {
     856          46 :     return aCost <= mMaxCost - mLockedCost;
     857             :   }
     858             : 
     859         113 :   void MarkUsed(NotNull<CachedSurface*> aSurface,
     860             :                 NotNull<ImageSurfaceCache*> aCache,
     861             :                 const StaticMutexAutoLock& aAutoLock)
     862             :   {
     863         113 :     if (aCache->IsLocked()) {
     864         113 :       LockSurface(aSurface, aAutoLock);
     865             :     } else {
     866           0 :       mExpirationTracker.MarkUsedLocked(aSurface, aAutoLock);
     867             :     }
     868         113 :   }
     869             : 
     870          32 :   void DoUnlockSurfaces(NotNull<ImageSurfaceCache*> aCache, bool aStaticOnly,
     871             :                         const StaticMutexAutoLock& aAutoLock)
     872             :   {
     873             :     // Unlock all the surfaces the per-image cache is holding.
     874          32 :     for (auto iter = aCache->ConstIter(); !iter.Done(); iter.Next()) {
     875           0 :       NotNull<CachedSurface*> surface = WrapNotNull(iter.UserData());
     876           0 :       if (surface->IsPlaceholder() || !surface->IsLocked()) {
     877           0 :         continue;
     878             :       }
     879           0 :       if (aStaticOnly && surface->GetSurfaceKey().Playback() != PlaybackType::eStatic) {
     880           0 :         continue;
     881             :       }
     882           0 :       StopTracking(surface, aAutoLock);
     883           0 :       surface->SetLocked(false);
     884           0 :       StartTracking(surface, aAutoLock);
     885             :     }
     886          32 :   }
     887             : 
     888          14 :   void RemoveEntry(const ImageKey    aImageKey,
     889             :                    const SurfaceKey& aSurfaceKey,
     890             :                    const StaticMutexAutoLock& aAutoLock)
     891             :   {
     892          28 :     RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     893          14 :     if (!cache) {
     894           0 :       return;  // No cached surfaces for this image.
     895             :     }
     896             : 
     897          28 :     RefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
     898          14 :     if (!surface) {
     899           0 :       return;  // Lookup in the per-image cache missed.
     900             :     }
     901             : 
     902          14 :     Remove(WrapNotNull(surface), aAutoLock);
     903             :   }
     904             : 
     905           0 :   struct SurfaceTracker : public ExpirationTrackerImpl<CachedSurface, 2,
     906             :                                                        StaticMutex,
     907             :                                                        StaticMutexAutoLock>
     908             :   {
     909           3 :     explicit SurfaceTracker(uint32_t aSurfaceCacheExpirationTimeMS)
     910           3 :       : ExpirationTrackerImpl<CachedSurface, 2,
     911             :                               StaticMutex, StaticMutexAutoLock>(
     912           3 :           aSurfaceCacheExpirationTimeMS, "SurfaceTracker")
     913           3 :     { }
     914             : 
     915             :   protected:
     916           0 :     void NotifyExpiredLocked(CachedSurface* aSurface,
     917             :                              const StaticMutexAutoLock& aAutoLock) override
     918             :     {
     919           0 :       sInstance->Remove(WrapNotNull(aSurface), aAutoLock);
     920           0 :     }
     921             : 
     922           0 :     StaticMutex& GetMutex() override
     923             :     {
     924           0 :       return sInstanceMutex;
     925             :     }
     926             :   };
     927             : 
     928           3 :   struct MemoryPressureObserver : public nsIObserver
     929             :   {
     930             :     NS_DECL_ISUPPORTS
     931             : 
     932           0 :     NS_IMETHOD Observe(nsISupports*,
     933             :                        const char* aTopic,
     934             :                        const char16_t*) override
     935             :     {
     936           0 :       StaticMutexAutoLock lock(sInstanceMutex);
     937           0 :       if (sInstance && strcmp(aTopic, "memory-pressure") == 0) {
     938           0 :         sInstance->DiscardForMemoryPressure(lock);
     939             :       }
     940           0 :       return NS_OK;
     941             :     }
     942             : 
     943             :   private:
     944           0 :     virtual ~MemoryPressureObserver() { }
     945             :   };
     946             : 
     947             :   nsTArray<CostEntry>                     mCosts;
     948             :   nsRefPtrHashtable<nsPtrHashKey<Image>,
     949             :     ImageSurfaceCache> mImageCaches;
     950             :   SurfaceTracker                          mExpirationTracker;
     951             :   RefPtr<MemoryPressureObserver>        mMemoryPressureObserver;
     952             :   const uint32_t                          mDiscardFactor;
     953             :   const Cost                              mMaxCost;
     954             :   Cost                                    mAvailableCost;
     955             :   Cost                                    mLockedCost;
     956             :   size_t                                  mOverflowCount;
     957             : };
     958             : 
     959          12 : NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
     960           6 : NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
     961             : 
     962             : ///////////////////////////////////////////////////////////////////////////////
     963             : // Public API
     964             : ///////////////////////////////////////////////////////////////////////////////
     965             : 
     966             : /* static */ void
     967           3 : SurfaceCache::Initialize()
     968             : {
     969             :   // Initialize preferences.
     970           3 :   MOZ_ASSERT(NS_IsMainThread());
     971           3 :   MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once");
     972             : 
     973             :   // See gfxPrefs for the default values of these preferences.
     974             : 
     975             :   // Length of time before an unused surface is removed from the cache, in
     976             :   // milliseconds.
     977             :   uint32_t surfaceCacheExpirationTimeMS =
     978           3 :     gfxPrefs::ImageMemSurfaceCacheMinExpirationMS();
     979             : 
     980             :   // What fraction of the memory used by the surface cache we should discard
     981             :   // when we get a memory pressure notification. This value is interpreted as
     982             :   // 1/N, so 1 means to discard everything, 2 means to discard about half of the
     983             :   // memory we're using, and so forth. We clamp it to avoid division by zero.
     984             :   uint32_t surfaceCacheDiscardFactor =
     985           3 :     max(gfxPrefs::ImageMemSurfaceCacheDiscardFactor(), 1u);
     986             : 
     987             :   // Maximum size of the surface cache, in kilobytes.
     988           3 :   uint64_t surfaceCacheMaxSizeKB = gfxPrefs::ImageMemSurfaceCacheMaxSizeKB();
     989             : 
     990             :   // A knob determining the actual size of the surface cache. Currently the
     991             :   // cache is (size of main memory) / (surface cache size factor) KB
     992             :   // or (surface cache max size) KB, whichever is smaller. The formula
     993             :   // may change in the future, though.
     994             :   // For example, a value of 4 would yield a 256MB cache on a 1GB machine.
     995             :   // The smallest machines we are likely to run this code on have 256MB
     996             :   // of memory, which would yield a 64MB cache on this setting.
     997             :   // We clamp this value to avoid division by zero.
     998             :   uint32_t surfaceCacheSizeFactor =
     999           3 :     max(gfxPrefs::ImageMemSurfaceCacheSizeFactor(), 1u);
    1000             : 
    1001             :   // Compute the size of the surface cache.
    1002           3 :   uint64_t memorySize = PR_GetPhysicalMemorySize();
    1003           3 :   if (memorySize == 0) {
    1004           0 :     MOZ_ASSERT_UNREACHABLE("PR_GetPhysicalMemorySize not implemented here");
    1005             :     memorySize = 256 * 1024 * 1024;  // Fall back to 256MB.
    1006             :   }
    1007           3 :   uint64_t proposedSize = memorySize / surfaceCacheSizeFactor;
    1008           3 :   uint64_t surfaceCacheSizeBytes = min(proposedSize,
    1009           6 :                                        surfaceCacheMaxSizeKB * 1024);
    1010             :   uint32_t finalSurfaceCacheSizeBytes =
    1011           3 :     min(surfaceCacheSizeBytes, uint64_t(UINT32_MAX));
    1012             : 
    1013             :   // Create the surface cache singleton with the requested settings.  Note that
    1014             :   // the size is a limit that the cache may not grow beyond, but we do not
    1015             :   // actually allocate any storage for surfaces at this time.
    1016             :   sInstance = new SurfaceCacheImpl(surfaceCacheExpirationTimeMS,
    1017             :                                    surfaceCacheDiscardFactor,
    1018           3 :                                    finalSurfaceCacheSizeBytes);
    1019           3 :   sInstance->InitMemoryReporter();
    1020           3 : }
    1021             : 
    1022             : /* static */ void
    1023           0 : SurfaceCache::Shutdown()
    1024             : {
    1025           0 :   StaticMutexAutoLock lock(sInstanceMutex);
    1026           0 :   MOZ_ASSERT(NS_IsMainThread());
    1027           0 :   MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
    1028           0 :   sInstance = nullptr;
    1029           0 : }
    1030             : 
    1031             : /* static */ LookupResult
    1032         123 : SurfaceCache::Lookup(const ImageKey         aImageKey,
    1033             :                      const SurfaceKey&      aSurfaceKey)
    1034             : {
    1035         246 :   StaticMutexAutoLock lock(sInstanceMutex);
    1036         123 :   if (!sInstance) {
    1037           0 :     return LookupResult(MatchType::NOT_FOUND);
    1038             :   }
    1039             : 
    1040         123 :   return sInstance->Lookup(aImageKey, aSurfaceKey, lock);
    1041             : }
    1042             : 
    1043             : /* static */ LookupResult
    1044          24 : SurfaceCache::LookupBestMatch(const ImageKey         aImageKey,
    1045             :                               const SurfaceKey&      aSurfaceKey)
    1046             : {
    1047          48 :   StaticMutexAutoLock lock(sInstanceMutex);
    1048          24 :   if (!sInstance) {
    1049           0 :     return LookupResult(MatchType::NOT_FOUND);
    1050             :   }
    1051             : 
    1052          24 :   return sInstance->LookupBestMatch(aImageKey, aSurfaceKey, lock);
    1053             : }
    1054             : 
    1055             : /* static */ InsertOutcome
    1056          32 : SurfaceCache::Insert(NotNull<ISurfaceProvider*> aProvider)
    1057             : {
    1058          64 :   StaticMutexAutoLock lock(sInstanceMutex);
    1059          32 :   if (!sInstance) {
    1060           0 :     return InsertOutcome::FAILURE;
    1061             :   }
    1062             : 
    1063          32 :   return sInstance->Insert(aProvider, /* aSetAvailable = */ false, lock);
    1064             : }
    1065             : 
    1066             : /* static */ bool
    1067          18 : SurfaceCache::CanHold(const IntSize& aSize, uint32_t aBytesPerPixel /* = 4 */)
    1068             : {
    1069          36 :   StaticMutexAutoLock lock(sInstanceMutex);
    1070          18 :   if (!sInstance) {
    1071           0 :     return false;
    1072             :   }
    1073             : 
    1074          18 :   Cost cost = ComputeCost(aSize, aBytesPerPixel);
    1075          18 :   return sInstance->CanHold(cost);
    1076             : }
    1077             : 
    1078             : /* static */ bool
    1079          38 : SurfaceCache::CanHold(size_t aSize)
    1080             : {
    1081          76 :   StaticMutexAutoLock lock(sInstanceMutex);
    1082          38 :   if (!sInstance) {
    1083           0 :     return false;
    1084             :   }
    1085             : 
    1086          38 :   return sInstance->CanHold(aSize);
    1087             : }
    1088             : 
    1089             : /* static */ void
    1090          14 : SurfaceCache::SurfaceAvailable(NotNull<ISurfaceProvider*> aProvider)
    1091             : {
    1092          28 :   StaticMutexAutoLock lock(sInstanceMutex);
    1093          14 :   if (!sInstance) {
    1094           0 :     return;
    1095             :   }
    1096             : 
    1097          14 :   sInstance->SurfaceAvailable(aProvider, lock);
    1098             : }
    1099             : 
    1100             : /* static */ void
    1101          40 : SurfaceCache::LockImage(const ImageKey aImageKey)
    1102             : {
    1103          40 :   StaticMutexAutoLock lock(sInstanceMutex);
    1104          40 :   if (sInstance) {
    1105          40 :     return sInstance->LockImage(aImageKey);
    1106             :   }
    1107             : }
    1108             : 
    1109             : /* static */ void
    1110           0 : SurfaceCache::UnlockImage(const ImageKey aImageKey)
    1111             : {
    1112           0 :   StaticMutexAutoLock lock(sInstanceMutex);
    1113           0 :   if (sInstance) {
    1114           0 :     return sInstance->UnlockImage(aImageKey, lock);
    1115             :   }
    1116             : }
    1117             : 
    1118             : /* static */ void
    1119          32 : SurfaceCache::UnlockEntries(const ImageKey aImageKey)
    1120             : {
    1121          32 :   StaticMutexAutoLock lock(sInstanceMutex);
    1122          32 :   if (sInstance) {
    1123          32 :     return sInstance->UnlockEntries(aImageKey, lock);
    1124             :   }
    1125             : }
    1126             : 
    1127             : /* static */ void
    1128           1 : SurfaceCache::RemoveImage(const ImageKey aImageKey)
    1129             : {
    1130           2 :   StaticMutexAutoLock lock(sInstanceMutex);
    1131           1 :   if (sInstance) {
    1132           1 :     sInstance->RemoveImage(aImageKey, lock);
    1133             :   }
    1134           1 : }
    1135             : 
    1136             : /* static */ void
    1137           0 : SurfaceCache::DiscardAll()
    1138             : {
    1139           0 :   StaticMutexAutoLock lock(sInstanceMutex);
    1140           0 :   if (sInstance) {
    1141           0 :     sInstance->DiscardAll(lock);
    1142             :   }
    1143           0 : }
    1144             : 
    1145             : /* static */ void
    1146           0 : SurfaceCache::CollectSizeOfSurfaces(const ImageKey                  aImageKey,
    1147             :                                     nsTArray<SurfaceMemoryCounter>& aCounters,
    1148             :                                     MallocSizeOf                    aMallocSizeOf)
    1149             : {
    1150           0 :   StaticMutexAutoLock lock(sInstanceMutex);
    1151           0 :   if (!sInstance) {
    1152           0 :     return;
    1153             :   }
    1154             : 
    1155           0 :   return sInstance->CollectSizeOfSurfaces(aImageKey, aCounters, aMallocSizeOf);
    1156             : }
    1157             : 
    1158             : /* static */ size_t
    1159          31 : SurfaceCache::MaximumCapacity()
    1160             : {
    1161          62 :   StaticMutexAutoLock lock(sInstanceMutex);
    1162          31 :   if (!sInstance) {
    1163           0 :     return 0;
    1164             :   }
    1165             : 
    1166          31 :   return sInstance->MaximumCapacity();
    1167             : }
    1168             : 
    1169             : } // namespace image
    1170             : } // namespace mozilla

Generated by: LCOV version 1.13