Bug 1370412 - Part 8b. Add ImageSurfaceCache::Prune to discard surfaces which are not needed in factor of 2 mode. r=tnikkel
authorAndrew Osmond <aosmond@mozilla.com>
Tue, 05 Sep 2017 07:58:46 -0400
changeset 430943 85cdb40c26211ea3288c46927641e14f0f5661d9
parent 430942 c856f513264340a2d6c2b70f71f345b5e4c8d6c0
child 430944 5cd1c5cd9b0154c25a5bb8971dfb5e7fc0583987
push id1567
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 12:36:05 +0000
treeherdermozilla-release@e512c14a0406 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs1370412
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1370412 - Part 8b. Add ImageSurfaceCache::Prune to discard surfaces which are not needed in factor of 2 mode. r=tnikkel
image/DecodedSurfaceProvider.cpp
image/SurfaceCache.cpp
image/SurfaceCache.h
--- a/image/DecodedSurfaceProvider.cpp
+++ b/image/DecodedSurfaceProvider.cpp
@@ -195,16 +195,22 @@ DecodedSurfaceProvider::FinishDecoding()
 {
   mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mImage);
   MOZ_ASSERT(mDecoder);
 
   // Send notifications.
   NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
 
+  // If we have a new and complete surface, we can try to prune similarly sized
+  // surfaces if the cache supports it.
+  if (mSurface && mSurface->IsFinished()) {
+    SurfaceCache::PruneImage(ImageKey(mImage));
+  }
+
   // Destroy our decoder; we don't need it anymore. (And if we don't destroy it,
   // our surface can never be optimized, because the decoder has a
   // RawAccessFrameRef to it.)
   mDecoder = nullptr;
 
   // We don't need a reference to our image anymore, either, and we don't want
   // one. We may be stored in the surface cache for a long time after decoding
   // finishes. If we don't drop our reference to the image, we'll end up
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -156,16 +156,19 @@ public:
     mProvider->SetLocked(aLocked);
   }
 
   bool IsLocked() const
   {
     return !IsPlaceholder() && mIsLocked && mProvider->IsLocked();
   }
 
+  void SetCannotSubstitute() { mProvider->Availability().SetCannotSubstitute(); }
+  bool CannotSubstitute() const { return mProvider->Availability().CannotSubstitute(); }
+
   bool IsPlaceholder() const { return mProvider->Availability().IsPlaceholder(); }
   bool IsDecoded() const { return !IsPlaceholder() && mProvider->IsFinished(); }
 
   ImageKey GetImageKey() const { return mProvider->GetImageKey(); }
   SurfaceKey GetSurfaceKey() const { return mProvider->GetSurfaceKey(); }
   nsExpirationState* GetExpirationState() { return &mExpirationState; }
 
   CostEntry GetCostEntry()
@@ -244,16 +247,17 @@ AreaOfIntSize(const IntSize& aSize) {
  */
 class ImageSurfaceCache
 {
   ~ImageSurfaceCache() { }
 public:
   ImageSurfaceCache()
     : mLocked(false)
     , mFactor2Mode(false)
+    , mFactor2Pruned(false)
   { }
 
   MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache)
 
   typedef
     nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
 
@@ -277,21 +281,27 @@ public:
   }
 
   already_AddRefed<CachedSurface> Lookup(const SurfaceKey& aSurfaceKey,
                                          bool aForAccess)
   {
     RefPtr<CachedSurface> surface;
     mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface));
 
-    // If no exact match is found, and this is for use rather than internal
-    // accounting (i.e. insert and removal), we know this will trigger a
-    // decode. Make sure we switch now to factor of 2 mode if necessary.
-    if (!surface && aForAccess && !mFactor2Mode) {
-      MaybeSetFactor2Mode();
+    if (aForAccess) {
+      if (surface) {
+        // We don't want to allow factor of 2 mode pruning to release surfaces
+        // for which the callers will accept no substitute.
+        surface->SetCannotSubstitute();
+      } else if (!mFactor2Mode) {
+        // If no exact match is found, and this is for use rather than internal
+        // accounting (i.e. insert and removal), we know this will trigger a
+        // decode. Make sure we switch now to factor of 2 mode if necessary.
+        MaybeSetFactor2Mode();
+      }
     }
 
     return surface.forget();
   }
 
   /**
    * @returns A tuple containing the best matching CachedSurface if available,
    *          a MatchType describing how the CachedSurface was selected, and
@@ -460,16 +470,66 @@ public:
         nativeSize.IsEmpty()) {
       return;
     }
 
     // We have a valid size, we can change modes.
     mFactor2Mode = true;
   }
 
+  template<typename Function>
+  void Prune(Function&& aRemoveCallback)
+  {
+    if (!mFactor2Mode || mFactor2Pruned) {
+      return;
+    }
+
+    // Attempt to discard any surfaces which are not factor of 2 and the best
+    // factor of 2 match exists.
+    bool hasNotFactorSize = false;
+    for (auto iter = mSurfaces.Iter(); !iter.Done(); iter.Next()) {
+      NotNull<CachedSurface*> current = WrapNotNull(iter.UserData());
+      const SurfaceKey& currentKey = current->GetSurfaceKey();
+      const IntSize& currentSize = currentKey.Size();
+
+      // First we check if someone requested this size and would not accept
+      // an alternatively sized surface.
+      if (current->CannotSubstitute()) {
+        continue;
+      }
+
+      // Next we find the best factor of 2 size for this surface. If this
+      // surface is a factor of 2 size, then we want to keep it.
+      IntSize bestSize = SuggestedSize(currentSize);
+      if (bestSize == currentSize) {
+        continue;
+      }
+
+      // Check the cache for a surface with the same parameters except for the
+      // size which uses the closest factor of 2 size.
+      SurfaceKey compactKey = currentKey.CloneWithSize(bestSize);
+      RefPtr<CachedSurface> compactMatch;
+      mSurfaces.Get(compactKey, getter_AddRefs(compactMatch));
+      if (compactMatch && compactMatch->IsDecoded()) {
+        aRemoveCallback(current);
+        iter.Remove();
+      } else {
+        hasNotFactorSize = true;
+      }
+    }
+
+    // We have no surfaces that are not factor of 2 sized, so we can stop
+    // pruning henceforth, because we avoid the insertion of new surfaces that
+    // don't match our sizing set (unless the caller won't accept a
+    // substitution.)
+    if (!hasNotFactorSize) {
+      mFactor2Pruned = true;
+    }
+  }
+
   IntSize SuggestedSize(const IntSize& aSize) const
   {
     // When not in factor of 2 mode, we can always decode at the given size.
     if (!mFactor2Mode) {
       return aSize;
     }
 
     MOZ_ASSERT(!IsEmpty());
@@ -548,16 +608,20 @@ public:
 
 private:
   SurfaceTable      mSurfaces;
 
   bool              mLocked;
 
   // True in "factor of 2" mode.
   bool              mFactor2Mode;
+
+  // True if all non-factor of 2 surfaces have been removed from the cache. Note
+  // that this excludes unsubstitutable sizes.
+  bool              mFactor2Pruned;
 };
 
 /**
  * SurfaceCacheImpl is responsible for determining which surfaces will be cached
  * and managing the surface cache data structures. Rather than interact with
  * SurfaceCacheImpl directly, client code interacts with SurfaceCache, which
  * maintains high-level invariants and encapsulates the details of the surface
  * cache's implementation.
@@ -945,16 +1009,28 @@ public:
     // This implicitly unlocks the image if it was locked.
     mImageCaches.Remove(aImageKey);
 
     // Since we did not actually remove any of the surfaces from the cache
     // itself, only stopped tracking them, we should free it outside the lock.
     return cache.forget();
   }
 
+  void PruneImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
+  {
+    RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
+    if (!cache) {
+      return;  // No cached surfaces for this image, so nothing to do.
+    }
+
+    cache->Prune([this, &aAutoLock](NotNull<CachedSurface*> aSurface) -> void {
+      StopTracking(aSurface, aAutoLock);
+    });
+  }
+
   void DiscardAll(const StaticMutexAutoLock& aAutoLock)
   {
     // Remove in order of cost because mCosts is an array and the other data
     // structures are all hash tables. Note that locked surfaces are not
     // removed, since they aren't present in mCosts.
     while (!mCosts.IsEmpty()) {
       Remove(mCosts.LastElement().Surface(), aAutoLock);
     }
@@ -1395,16 +1471,25 @@ SurfaceCache::RemoveImage(const ImageKey
     StaticMutexAutoLock lock(sInstanceMutex);
     if (sInstance) {
       discard = sInstance->RemoveImage(aImageKey, lock);
     }
   }
 }
 
 /* static */ void
+SurfaceCache::PruneImage(const ImageKey aImageKey)
+{
+  StaticMutexAutoLock lock(sInstanceMutex);
+  if (sInstance) {
+    sInstance->PruneImage(aImageKey, lock);
+  }
+}
+
+/* static */ void
 SurfaceCache::DiscardAll()
 {
   nsTArray<RefPtr<CachedSurface>> discard;
   {
     StaticMutexAutoLock lock(sInstanceMutex);
     if (sInstance) {
       sInstance->DiscardAll(lock);
       sInstance->TakeDiscard(discard, lock);
--- a/image/SurfaceCache.h
+++ b/image/SurfaceCache.h
@@ -400,16 +400,26 @@ struct SurfaceCache
    * entries in the surface cache is destroyed. If another image were allocated
    * at the same address it could result in subtle, difficult-to-reproduce bugs.
    *
    * @param aImageKey  The image which should be removed from the cache.
    */
   static void RemoveImage(const ImageKey aImageKey);
 
   /**
+   * Attempts to remove cache entries (including placeholders) associated with
+   * the given image from the cache, assuming there is an equivalent entry that
+   * it is able substitute that entry with. Note that this only applies if the
+   * image is in factor of 2 mode. If it is not, this operation does nothing.
+   *
+   * @param aImageKey  The image whose cache which should be pruned.
+   */
+  static void PruneImage(const ImageKey aImageKey);
+
+  /**
    * Evicts all evictable entries from the cache.
    *
    * All entries are evictable except for entries associated with locked images.
    * Non-evictable entries can only be removed by RemoveImage().
    */
   static void DiscardAll();
 
   /**