image/src/SurfaceCache.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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/. */
     6 /**
     7  * SurfaceCache is a service for caching temporary surfaces in imagelib.
     8  */
    10 #include "SurfaceCache.h"
    12 #include <algorithm>
    13 #include "mozilla/Attributes.h"  // for MOZ_THIS_IN_INITIALIZER_LIST
    14 #include "mozilla/DebugOnly.h"
    15 #include "mozilla/Preferences.h"
    16 #include "mozilla/RefPtr.h"
    17 #include "mozilla/StaticPtr.h"
    18 #include "nsIMemoryReporter.h"
    19 #include "gfx2DGlue.h"
    20 #include "gfxASurface.h"
    21 #include "gfxPattern.h"  // Workaround for flaw in bug 921753 part 2.
    22 #include "gfxDrawable.h"
    23 #include "gfxPlatform.h"
    24 #include "nsAutoPtr.h"
    25 #include "nsExpirationTracker.h"
    26 #include "nsHashKeys.h"
    27 #include "nsRefPtrHashtable.h"
    28 #include "nsSize.h"
    29 #include "nsTArray.h"
    30 #include "prsystem.h"
    31 #include "SVGImageContext.h"
    33 using std::max;
    34 using std::min;
    35 using namespace mozilla::gfx;
    37 namespace mozilla {
    38 namespace image {
    40 class CachedSurface;
    41 class SurfaceCacheImpl;
    43 ///////////////////////////////////////////////////////////////////////////////
    44 // Static Data
    45 ///////////////////////////////////////////////////////////////////////////////
    47 // The single surface cache instance.
    48 static StaticRefPtr<SurfaceCacheImpl> sInstance;
    51 ///////////////////////////////////////////////////////////////////////////////
    52 // SurfaceCache Implementation
    53 ///////////////////////////////////////////////////////////////////////////////
    55 /*
    56  * Cost models the cost of storing a surface in the cache. Right now, this is
    57  * simply an estimate of the size of the surface in bytes, but in the future it
    58  * may be worth taking into account the cost of rematerializing the surface as
    59  * well.
    60  */
    61 typedef size_t Cost;
    63 static Cost ComputeCost(const IntSize& aSize)
    64 {
    65   return aSize.width * aSize.height * 4;  // width * height * 4 bytes (32bpp)
    66 }
    68 /*
    69  * Since we want to be able to make eviction decisions based on cost, we need to
    70  * be able to look up the CachedSurface which has a certain cost as well as the
    71  * cost associated with a certain CachedSurface. To make this possible, in data
    72  * structures we actually store a CostEntry, which contains a weak pointer to
    73  * its associated surface.
    74  *
    75  * To make usage of the weak pointer safe, SurfaceCacheImpl always calls
    76  * StartTracking after a surface is stored in the cache and StopTracking before
    77  * it is removed.
    78  */
    79 class CostEntry
    80 {
    81 public:
    82   CostEntry(CachedSurface* aSurface, Cost aCost)
    83     : mSurface(aSurface)
    84     , mCost(aCost)
    85   {
    86     MOZ_ASSERT(aSurface, "Must have a surface");
    87   }
    89   CachedSurface* GetSurface() const { return mSurface; }
    90   Cost GetCost() const { return mCost; }
    92   bool operator==(const CostEntry& aOther) const
    93   {
    94     return mSurface == aOther.mSurface &&
    95            mCost == aOther.mCost;
    96   }
    98   bool operator<(const CostEntry& aOther) const
    99   {
   100     return mCost < aOther.mCost ||
   101            (mCost == aOther.mCost && mSurface < aOther.mSurface);
   102   }
   104 private:
   105   CachedSurface* mSurface;
   106   Cost           mCost;
   107 };
   109 /*
   110  * A CachedSurface associates a surface with a key that uniquely identifies that
   111  * surface.
   112  */
   113 class CachedSurface
   114 {
   115 public:
   116   NS_INLINE_DECL_REFCOUNTING(CachedSurface)
   118   CachedSurface(DrawTarget*       aTarget,
   119                 const IntSize     aTargetSize,
   120                 const Cost        aCost,
   121                 const ImageKey    aImageKey,
   122                 const SurfaceKey& aSurfaceKey)
   123     : mTarget(aTarget)
   124     , mTargetSize(aTargetSize)
   125     , mCost(aCost)
   126     , mImageKey(aImageKey)
   127     , mSurfaceKey(aSurfaceKey)
   128   {
   129     MOZ_ASSERT(mTarget, "Must have a valid DrawTarget");
   130     MOZ_ASSERT(mImageKey, "Must have a valid image key");
   131   }
   133   already_AddRefed<gfxDrawable> Drawable() const
   134   {
   135     nsRefPtr<gfxDrawable> drawable =
   136       new gfxSurfaceDrawable(mTarget, ThebesIntSize(mTargetSize));
   137     return drawable.forget();
   138   }
   140   ImageKey GetImageKey() const { return mImageKey; }
   141   SurfaceKey GetSurfaceKey() const { return mSurfaceKey; }
   142   CostEntry GetCostEntry() { return image::CostEntry(this, mCost); }
   143   nsExpirationState* GetExpirationState() { return &mExpirationState; }
   145 private:
   146   nsExpirationState       mExpirationState;
   147   nsRefPtr<DrawTarget>    mTarget;
   148   const IntSize           mTargetSize;
   149   const Cost              mCost;
   150   const ImageKey          mImageKey;
   151   const SurfaceKey        mSurfaceKey;
   152 };
   154 /*
   155  * An ImageSurfaceCache is a per-image surface cache. For correctness we must be
   156  * able to remove all surfaces associated with an image when the image is
   157  * destroyed or invalidated. Since this will happen frequently, it makes sense
   158  * to make it cheap by storing the surfaces for each image separately.
   159  */
   160 class ImageSurfaceCache
   161 {
   162 public:
   163   NS_INLINE_DECL_REFCOUNTING(ImageSurfaceCache)
   165   typedef nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
   167   bool IsEmpty() const { return mSurfaces.Count() == 0; }
   169   void Insert(const SurfaceKey& aKey, CachedSurface* aSurface)
   170   {
   171     MOZ_ASSERT(aSurface, "Should have a surface");
   172     mSurfaces.Put(aKey, aSurface);
   173   }
   175   void Remove(CachedSurface* aSurface)
   176   {
   177     MOZ_ASSERT(aSurface, "Should have a surface");
   178     MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()),
   179         "Should not be removing a surface we don't have");
   181     mSurfaces.Remove(aSurface->GetSurfaceKey());
   182   }
   184   already_AddRefed<CachedSurface> Lookup(const SurfaceKey& aSurfaceKey)
   185   {
   186     nsRefPtr<CachedSurface> surface;
   187     mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface));
   188     return surface.forget();
   189   }
   191   void ForEach(SurfaceTable::EnumReadFunction aFunction, void* aData)
   192   {
   193     mSurfaces.EnumerateRead(aFunction, aData);
   194   }
   196 private:
   197   SurfaceTable mSurfaces;
   198 };
   200 /*
   201  * SurfaceCacheImpl is responsible for determining which surfaces will be cached
   202  * and managing the surface cache data structures. Rather than interact with
   203  * SurfaceCacheImpl directly, client code interacts with SurfaceCache, which
   204  * maintains high-level invariants and encapsulates the details of the surface
   205  * cache's implementation.
   206  */
   207 class SurfaceCacheImpl : public nsIMemoryReporter
   208 {
   209 public:
   210   NS_DECL_ISUPPORTS
   212   SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS,
   213                    uint32_t aSurfaceCacheSize)
   214     : mExpirationTracker(MOZ_THIS_IN_INITIALIZER_LIST(),
   215                          aSurfaceCacheExpirationTimeMS)
   216     , mMemoryPressureObserver(new MemoryPressureObserver)
   217     , mMaxCost(aSurfaceCacheSize)
   218     , mAvailableCost(aSurfaceCacheSize)
   219   {
   220     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   221     if (os)
   222       os->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
   223   }
   225   virtual ~SurfaceCacheImpl()
   226   {
   227     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   228     if (os)
   229       os->RemoveObserver(mMemoryPressureObserver, "memory-pressure");
   231     UnregisterWeakMemoryReporter(this);
   232   }
   234   void InitMemoryReporter() {
   235     RegisterWeakMemoryReporter(this);
   236   }
   238   void Insert(DrawTarget*       aTarget,
   239               IntSize           aTargetSize,
   240               const Cost        aCost,
   241               const ImageKey    aImageKey,
   242               const SurfaceKey& aSurfaceKey)
   243   {
   244     MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey).take(),
   245                "Inserting a duplicate drawable into the SurfaceCache");
   247     // If this is bigger than the maximum cache size, refuse to cache it.
   248     if (!CanHold(aCost))
   249       return;
   251     nsRefPtr<CachedSurface> surface =
   252       new CachedSurface(aTarget, aTargetSize, aCost, aImageKey, aSurfaceKey);
   254     // Remove elements in order of cost until we can fit this in the cache.
   255     while (aCost > mAvailableCost) {
   256       MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit");
   257       Remove(mCosts.LastElement().GetSurface());
   258     }
   260     // Locate the appropriate per-image cache. If there's not an existing cache
   261     // for this image, create it.
   262     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
   263     if (!cache) {
   264       cache = new ImageSurfaceCache;
   265       mImageCaches.Put(aImageKey, cache);
   266     }
   268     // Insert.
   269     MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost");
   270     cache->Insert(aSurfaceKey, surface);
   271     StartTracking(surface);
   272   }
   274   void Remove(CachedSurface* aSurface)
   275   {
   276     MOZ_ASSERT(aSurface, "Should have a surface");
   277     const ImageKey imageKey = aSurface->GetImageKey();
   279     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
   280     MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
   282     StopTracking(aSurface);
   283     cache->Remove(aSurface);
   285     // Remove the per-image cache if it's unneeded now.
   286     if (cache->IsEmpty()) {
   287       mImageCaches.Remove(imageKey);
   288     }
   289   }
   291   void StartTracking(CachedSurface* aSurface)
   292   {
   293     CostEntry costEntry = aSurface->GetCostEntry();
   294     MOZ_ASSERT(costEntry.GetCost() <= mAvailableCost,
   295                "Cost too large and the caller didn't catch it");
   297     mAvailableCost -= costEntry.GetCost();
   298     mCosts.InsertElementSorted(costEntry);
   299     mExpirationTracker.AddObject(aSurface);
   300   }
   302   void StopTracking(CachedSurface* aSurface)
   303   {
   304     MOZ_ASSERT(aSurface, "Should have a surface");
   305     CostEntry costEntry = aSurface->GetCostEntry();
   307     mExpirationTracker.RemoveObject(aSurface);
   308     DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
   309     mAvailableCost += costEntry.GetCost();
   311     MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
   312     MOZ_ASSERT(mAvailableCost <= mMaxCost, "More available cost than we started with");
   313   }
   315   already_AddRefed<gfxDrawable> Lookup(const ImageKey    aImageKey,
   316                                        const SurfaceKey& aSurfaceKey)
   317   {
   318     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
   319     if (!cache)
   320       return nullptr;  // No cached surfaces for this image.
   322     nsRefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
   323     if (!surface)
   324       return nullptr;  // Lookup in the per-image cache missed.
   326     mExpirationTracker.MarkUsed(surface);
   327     return surface->Drawable();
   328   }
   330   bool CanHold(const Cost aCost) const
   331   {
   332     return aCost <= mMaxCost;
   333   }
   335   void Discard(const ImageKey aImageKey)
   336   {
   337     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
   338     if (!cache)
   339       return;  // No cached surfaces for this image, so nothing to do.
   341     // Discard all of the cached surfaces for this image.
   342     // XXX(seth): This is O(n^2) since for each item in the cache we are
   343     // removing an element from the costs array. Since n is expected to be
   344     // small, performance should be good, but if usage patterns change we should
   345     // change the data structure used for mCosts.
   346     cache->ForEach(DoStopTracking, this);
   348     // The per-image cache isn't needed anymore, so remove it as well.
   349     mImageCaches.Remove(aImageKey);
   350   }
   352   void DiscardAll()
   353   {
   354     // Remove in order of cost because mCosts is an array and the other data
   355     // structures are all hash tables.
   356     while (!mCosts.IsEmpty()) {
   357       Remove(mCosts.LastElement().GetSurface());
   358     }
   359   }
   361   static PLDHashOperator DoStopTracking(const SurfaceKey&,
   362                                         CachedSurface*    aSurface,
   363                                         void*             aCache)
   364   {
   365     static_cast<SurfaceCacheImpl*>(aCache)->StopTracking(aSurface);
   366     return PL_DHASH_NEXT;
   367   }
   369   NS_IMETHOD
   370   CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData)
   371   {
   372     return MOZ_COLLECT_REPORT(
   373       "imagelib-surface-cache", KIND_OTHER, UNITS_BYTES,
   374       SizeOfSurfacesEstimate(),
   375       "Memory used by the imagelib temporary surface cache.");
   376   }
   378   // XXX(seth): This is currently only an estimate and, since we don't know
   379   // which surfaces are in GPU memory and which aren't, it's reported as
   380   // KIND_OTHER and will also show up in heap-unclassified. Bug 923302 will
   381   // make this nicer.
   382   Cost SizeOfSurfacesEstimate() const
   383   {
   384     return mMaxCost - mAvailableCost;
   385   }
   387 private:
   388   already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
   389   {
   390     nsRefPtr<ImageSurfaceCache> imageCache;
   391     mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
   392     return imageCache.forget();
   393   }
   395   struct SurfaceTracker : public nsExpirationTracker<CachedSurface, 2>
   396   {
   397     SurfaceTracker(SurfaceCacheImpl* aCache, uint32_t aSurfaceCacheExpirationTimeMS)
   398       : nsExpirationTracker<CachedSurface, 2>(aSurfaceCacheExpirationTimeMS)
   399       , mCache(aCache)
   400     { }
   402   protected:
   403     virtual void NotifyExpired(CachedSurface* aSurface) MOZ_OVERRIDE
   404     {
   405       if (mCache) {
   406         mCache->Remove(aSurface);
   407       }
   408     }
   410   private:
   411     SurfaceCacheImpl* const mCache;  // Weak pointer to owner.
   412   };
   414   struct MemoryPressureObserver : public nsIObserver
   415   {
   416     NS_DECL_ISUPPORTS
   418     virtual ~MemoryPressureObserver() { }
   420     NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*)
   421     {
   422       if (sInstance && strcmp(aTopic, "memory-pressure") == 0) {
   423         sInstance->DiscardAll();
   424       }
   425       return NS_OK;
   426     }
   427   };
   430   nsTArray<CostEntry>                                       mCosts;
   431   nsRefPtrHashtable<nsPtrHashKey<Image>, ImageSurfaceCache> mImageCaches;
   432   SurfaceTracker                                            mExpirationTracker;
   433   nsRefPtr<MemoryPressureObserver>                          mMemoryPressureObserver;
   434   const Cost                                                mMaxCost;
   435   Cost                                                      mAvailableCost;
   436 };
   438 NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
   439 NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
   441 ///////////////////////////////////////////////////////////////////////////////
   442 // Public API
   443 ///////////////////////////////////////////////////////////////////////////////
   445 /* static */ void
   446 SurfaceCache::Initialize()
   447 {
   448   // Initialize preferences.
   449   MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once");
   451   // Length of time before an unused surface is removed from the cache, in milliseconds.
   452   // The default value gives an expiration time of 1 minute.
   453   uint32_t surfaceCacheExpirationTimeMS =
   454     Preferences::GetUint("image.mem.surfacecache.min_expiration_ms", 60 * 1000);
   456   // Maximum size of the surface cache, in kilobytes.
   457   // The default is 100MB. (But we may override this for e.g. B2G.)
   458   uint32_t surfaceCacheMaxSizeKB =
   459     Preferences::GetUint("image.mem.surfacecache.max_size_kb", 100 * 1024);
   461   // A knob determining the actual size of the surface cache. Currently the
   462   // cache is (size of main memory) / (surface cache size factor) KB
   463   // or (surface cache max size) KB, whichever is smaller. The formula
   464   // may change in the future, though.
   465   // The default value is 64, which yields a 64MB cache on a 4GB machine.
   466   // The smallest machines we are likely to run this code on have 256MB
   467   // of memory, which would yield a 4MB cache on the default setting.
   468   uint32_t surfaceCacheSizeFactor =
   469     Preferences::GetUint("image.mem.surfacecache.size_factor", 64);
   471   // Clamp to avoid division by zero below.
   472   surfaceCacheSizeFactor = max(surfaceCacheSizeFactor, 1u);
   474   // Compute the size of the surface cache.
   475   uint32_t proposedSize = PR_GetPhysicalMemorySize() / surfaceCacheSizeFactor;
   476   uint32_t surfaceCacheSizeBytes = min(proposedSize, surfaceCacheMaxSizeKB * 1024);
   478   // Create the surface cache singleton with the requested expiration time and
   479   // size. Note that the size is a limit that the cache may not grow beyond, but
   480   // we do not actually allocate any storage for surfaces at this time.
   481   sInstance = new SurfaceCacheImpl(surfaceCacheExpirationTimeMS,
   482                                    surfaceCacheSizeBytes);
   483   sInstance->InitMemoryReporter();
   484 }
   486 /* static */ void
   487 SurfaceCache::Shutdown()
   488 {
   489   MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
   490   sInstance = nullptr;
   491 }
   493 /* static */ already_AddRefed<gfxDrawable>
   494 SurfaceCache::Lookup(const ImageKey    aImageKey,
   495                      const SurfaceKey& aSurfaceKey)
   496 {
   497   MOZ_ASSERT(sInstance, "Should be initialized");
   498   MOZ_ASSERT(NS_IsMainThread());
   500   return sInstance->Lookup(aImageKey, aSurfaceKey);
   501 }
   503 /* static */ void
   504 SurfaceCache::Insert(DrawTarget*       aTarget,
   505                      const ImageKey    aImageKey,
   506                      const SurfaceKey& aSurfaceKey)
   507 {
   508   MOZ_ASSERT(sInstance, "Should be initialized");
   509   MOZ_ASSERT(NS_IsMainThread());
   511   Cost cost = ComputeCost(aSurfaceKey.Size());
   512   return sInstance->Insert(aTarget, aSurfaceKey.Size(), cost, aImageKey,
   513                            aSurfaceKey);
   514 }
   516 /* static */ bool
   517 SurfaceCache::CanHold(const IntSize& aSize)
   518 {
   519   MOZ_ASSERT(sInstance, "Should be initialized");
   520   MOZ_ASSERT(NS_IsMainThread());
   522   Cost cost = ComputeCost(aSize);
   523   return sInstance->CanHold(cost);
   524 }
   526 /* static */ void
   527 SurfaceCache::Discard(Image* aImageKey)
   528 {
   529   MOZ_ASSERT(sInstance, "Should be initialized");
   530   MOZ_ASSERT(NS_IsMainThread());
   532   return sInstance->Discard(aImageKey);
   533 }
   535 /* static */ void
   536 SurfaceCache::DiscardAll()
   537 {
   538   MOZ_ASSERT(sInstance, "Should be initialized");
   539   MOZ_ASSERT(NS_IsMainThread());
   541   return sInstance->DiscardAll();
   542 }
   544 } // namespace image
   545 } // namespace mozilla

mercurial