1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/src/SurfaceCache.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,545 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/** 1.10 + * SurfaceCache is a service for caching temporary surfaces in imagelib. 1.11 + */ 1.12 + 1.13 +#include "SurfaceCache.h" 1.14 + 1.15 +#include <algorithm> 1.16 +#include "mozilla/Attributes.h" // for MOZ_THIS_IN_INITIALIZER_LIST 1.17 +#include "mozilla/DebugOnly.h" 1.18 +#include "mozilla/Preferences.h" 1.19 +#include "mozilla/RefPtr.h" 1.20 +#include "mozilla/StaticPtr.h" 1.21 +#include "nsIMemoryReporter.h" 1.22 +#include "gfx2DGlue.h" 1.23 +#include "gfxASurface.h" 1.24 +#include "gfxPattern.h" // Workaround for flaw in bug 921753 part 2. 1.25 +#include "gfxDrawable.h" 1.26 +#include "gfxPlatform.h" 1.27 +#include "nsAutoPtr.h" 1.28 +#include "nsExpirationTracker.h" 1.29 +#include "nsHashKeys.h" 1.30 +#include "nsRefPtrHashtable.h" 1.31 +#include "nsSize.h" 1.32 +#include "nsTArray.h" 1.33 +#include "prsystem.h" 1.34 +#include "SVGImageContext.h" 1.35 + 1.36 +using std::max; 1.37 +using std::min; 1.38 +using namespace mozilla::gfx; 1.39 + 1.40 +namespace mozilla { 1.41 +namespace image { 1.42 + 1.43 +class CachedSurface; 1.44 +class SurfaceCacheImpl; 1.45 + 1.46 +/////////////////////////////////////////////////////////////////////////////// 1.47 +// Static Data 1.48 +/////////////////////////////////////////////////////////////////////////////// 1.49 + 1.50 +// The single surface cache instance. 1.51 +static StaticRefPtr<SurfaceCacheImpl> sInstance; 1.52 + 1.53 + 1.54 +/////////////////////////////////////////////////////////////////////////////// 1.55 +// SurfaceCache Implementation 1.56 +/////////////////////////////////////////////////////////////////////////////// 1.57 + 1.58 +/* 1.59 + * Cost models the cost of storing a surface in the cache. Right now, this is 1.60 + * simply an estimate of the size of the surface in bytes, but in the future it 1.61 + * may be worth taking into account the cost of rematerializing the surface as 1.62 + * well. 1.63 + */ 1.64 +typedef size_t Cost; 1.65 + 1.66 +static Cost ComputeCost(const IntSize& aSize) 1.67 +{ 1.68 + return aSize.width * aSize.height * 4; // width * height * 4 bytes (32bpp) 1.69 +} 1.70 + 1.71 +/* 1.72 + * Since we want to be able to make eviction decisions based on cost, we need to 1.73 + * be able to look up the CachedSurface which has a certain cost as well as the 1.74 + * cost associated with a certain CachedSurface. To make this possible, in data 1.75 + * structures we actually store a CostEntry, which contains a weak pointer to 1.76 + * its associated surface. 1.77 + * 1.78 + * To make usage of the weak pointer safe, SurfaceCacheImpl always calls 1.79 + * StartTracking after a surface is stored in the cache and StopTracking before 1.80 + * it is removed. 1.81 + */ 1.82 +class CostEntry 1.83 +{ 1.84 +public: 1.85 + CostEntry(CachedSurface* aSurface, Cost aCost) 1.86 + : mSurface(aSurface) 1.87 + , mCost(aCost) 1.88 + { 1.89 + MOZ_ASSERT(aSurface, "Must have a surface"); 1.90 + } 1.91 + 1.92 + CachedSurface* GetSurface() const { return mSurface; } 1.93 + Cost GetCost() const { return mCost; } 1.94 + 1.95 + bool operator==(const CostEntry& aOther) const 1.96 + { 1.97 + return mSurface == aOther.mSurface && 1.98 + mCost == aOther.mCost; 1.99 + } 1.100 + 1.101 + bool operator<(const CostEntry& aOther) const 1.102 + { 1.103 + return mCost < aOther.mCost || 1.104 + (mCost == aOther.mCost && mSurface < aOther.mSurface); 1.105 + } 1.106 + 1.107 +private: 1.108 + CachedSurface* mSurface; 1.109 + Cost mCost; 1.110 +}; 1.111 + 1.112 +/* 1.113 + * A CachedSurface associates a surface with a key that uniquely identifies that 1.114 + * surface. 1.115 + */ 1.116 +class CachedSurface 1.117 +{ 1.118 +public: 1.119 + NS_INLINE_DECL_REFCOUNTING(CachedSurface) 1.120 + 1.121 + CachedSurface(DrawTarget* aTarget, 1.122 + const IntSize aTargetSize, 1.123 + const Cost aCost, 1.124 + const ImageKey aImageKey, 1.125 + const SurfaceKey& aSurfaceKey) 1.126 + : mTarget(aTarget) 1.127 + , mTargetSize(aTargetSize) 1.128 + , mCost(aCost) 1.129 + , mImageKey(aImageKey) 1.130 + , mSurfaceKey(aSurfaceKey) 1.131 + { 1.132 + MOZ_ASSERT(mTarget, "Must have a valid DrawTarget"); 1.133 + MOZ_ASSERT(mImageKey, "Must have a valid image key"); 1.134 + } 1.135 + 1.136 + already_AddRefed<gfxDrawable> Drawable() const 1.137 + { 1.138 + nsRefPtr<gfxDrawable> drawable = 1.139 + new gfxSurfaceDrawable(mTarget, ThebesIntSize(mTargetSize)); 1.140 + return drawable.forget(); 1.141 + } 1.142 + 1.143 + ImageKey GetImageKey() const { return mImageKey; } 1.144 + SurfaceKey GetSurfaceKey() const { return mSurfaceKey; } 1.145 + CostEntry GetCostEntry() { return image::CostEntry(this, mCost); } 1.146 + nsExpirationState* GetExpirationState() { return &mExpirationState; } 1.147 + 1.148 +private: 1.149 + nsExpirationState mExpirationState; 1.150 + nsRefPtr<DrawTarget> mTarget; 1.151 + const IntSize mTargetSize; 1.152 + const Cost mCost; 1.153 + const ImageKey mImageKey; 1.154 + const SurfaceKey mSurfaceKey; 1.155 +}; 1.156 + 1.157 +/* 1.158 + * An ImageSurfaceCache is a per-image surface cache. For correctness we must be 1.159 + * able to remove all surfaces associated with an image when the image is 1.160 + * destroyed or invalidated. Since this will happen frequently, it makes sense 1.161 + * to make it cheap by storing the surfaces for each image separately. 1.162 + */ 1.163 +class ImageSurfaceCache 1.164 +{ 1.165 +public: 1.166 + NS_INLINE_DECL_REFCOUNTING(ImageSurfaceCache) 1.167 + 1.168 + typedef nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable; 1.169 + 1.170 + bool IsEmpty() const { return mSurfaces.Count() == 0; } 1.171 + 1.172 + void Insert(const SurfaceKey& aKey, CachedSurface* aSurface) 1.173 + { 1.174 + MOZ_ASSERT(aSurface, "Should have a surface"); 1.175 + mSurfaces.Put(aKey, aSurface); 1.176 + } 1.177 + 1.178 + void Remove(CachedSurface* aSurface) 1.179 + { 1.180 + MOZ_ASSERT(aSurface, "Should have a surface"); 1.181 + MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()), 1.182 + "Should not be removing a surface we don't have"); 1.183 + 1.184 + mSurfaces.Remove(aSurface->GetSurfaceKey()); 1.185 + } 1.186 + 1.187 + already_AddRefed<CachedSurface> Lookup(const SurfaceKey& aSurfaceKey) 1.188 + { 1.189 + nsRefPtr<CachedSurface> surface; 1.190 + mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface)); 1.191 + return surface.forget(); 1.192 + } 1.193 + 1.194 + void ForEach(SurfaceTable::EnumReadFunction aFunction, void* aData) 1.195 + { 1.196 + mSurfaces.EnumerateRead(aFunction, aData); 1.197 + } 1.198 + 1.199 +private: 1.200 + SurfaceTable mSurfaces; 1.201 +}; 1.202 + 1.203 +/* 1.204 + * SurfaceCacheImpl is responsible for determining which surfaces will be cached 1.205 + * and managing the surface cache data structures. Rather than interact with 1.206 + * SurfaceCacheImpl directly, client code interacts with SurfaceCache, which 1.207 + * maintains high-level invariants and encapsulates the details of the surface 1.208 + * cache's implementation. 1.209 + */ 1.210 +class SurfaceCacheImpl : public nsIMemoryReporter 1.211 +{ 1.212 +public: 1.213 + NS_DECL_ISUPPORTS 1.214 + 1.215 + SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS, 1.216 + uint32_t aSurfaceCacheSize) 1.217 + : mExpirationTracker(MOZ_THIS_IN_INITIALIZER_LIST(), 1.218 + aSurfaceCacheExpirationTimeMS) 1.219 + , mMemoryPressureObserver(new MemoryPressureObserver) 1.220 + , mMaxCost(aSurfaceCacheSize) 1.221 + , mAvailableCost(aSurfaceCacheSize) 1.222 + { 1.223 + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1.224 + if (os) 1.225 + os->AddObserver(mMemoryPressureObserver, "memory-pressure", false); 1.226 + } 1.227 + 1.228 + virtual ~SurfaceCacheImpl() 1.229 + { 1.230 + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1.231 + if (os) 1.232 + os->RemoveObserver(mMemoryPressureObserver, "memory-pressure"); 1.233 + 1.234 + UnregisterWeakMemoryReporter(this); 1.235 + } 1.236 + 1.237 + void InitMemoryReporter() { 1.238 + RegisterWeakMemoryReporter(this); 1.239 + } 1.240 + 1.241 + void Insert(DrawTarget* aTarget, 1.242 + IntSize aTargetSize, 1.243 + const Cost aCost, 1.244 + const ImageKey aImageKey, 1.245 + const SurfaceKey& aSurfaceKey) 1.246 + { 1.247 + MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey).take(), 1.248 + "Inserting a duplicate drawable into the SurfaceCache"); 1.249 + 1.250 + // If this is bigger than the maximum cache size, refuse to cache it. 1.251 + if (!CanHold(aCost)) 1.252 + return; 1.253 + 1.254 + nsRefPtr<CachedSurface> surface = 1.255 + new CachedSurface(aTarget, aTargetSize, aCost, aImageKey, aSurfaceKey); 1.256 + 1.257 + // Remove elements in order of cost until we can fit this in the cache. 1.258 + while (aCost > mAvailableCost) { 1.259 + MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit"); 1.260 + Remove(mCosts.LastElement().GetSurface()); 1.261 + } 1.262 + 1.263 + // Locate the appropriate per-image cache. If there's not an existing cache 1.264 + // for this image, create it. 1.265 + nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey); 1.266 + if (!cache) { 1.267 + cache = new ImageSurfaceCache; 1.268 + mImageCaches.Put(aImageKey, cache); 1.269 + } 1.270 + 1.271 + // Insert. 1.272 + MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost"); 1.273 + cache->Insert(aSurfaceKey, surface); 1.274 + StartTracking(surface); 1.275 + } 1.276 + 1.277 + void Remove(CachedSurface* aSurface) 1.278 + { 1.279 + MOZ_ASSERT(aSurface, "Should have a surface"); 1.280 + const ImageKey imageKey = aSurface->GetImageKey(); 1.281 + 1.282 + nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey); 1.283 + MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache"); 1.284 + 1.285 + StopTracking(aSurface); 1.286 + cache->Remove(aSurface); 1.287 + 1.288 + // Remove the per-image cache if it's unneeded now. 1.289 + if (cache->IsEmpty()) { 1.290 + mImageCaches.Remove(imageKey); 1.291 + } 1.292 + } 1.293 + 1.294 + void StartTracking(CachedSurface* aSurface) 1.295 + { 1.296 + CostEntry costEntry = aSurface->GetCostEntry(); 1.297 + MOZ_ASSERT(costEntry.GetCost() <= mAvailableCost, 1.298 + "Cost too large and the caller didn't catch it"); 1.299 + 1.300 + mAvailableCost -= costEntry.GetCost(); 1.301 + mCosts.InsertElementSorted(costEntry); 1.302 + mExpirationTracker.AddObject(aSurface); 1.303 + } 1.304 + 1.305 + void StopTracking(CachedSurface* aSurface) 1.306 + { 1.307 + MOZ_ASSERT(aSurface, "Should have a surface"); 1.308 + CostEntry costEntry = aSurface->GetCostEntry(); 1.309 + 1.310 + mExpirationTracker.RemoveObject(aSurface); 1.311 + DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry); 1.312 + mAvailableCost += costEntry.GetCost(); 1.313 + 1.314 + MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface"); 1.315 + MOZ_ASSERT(mAvailableCost <= mMaxCost, "More available cost than we started with"); 1.316 + } 1.317 + 1.318 + already_AddRefed<gfxDrawable> Lookup(const ImageKey aImageKey, 1.319 + const SurfaceKey& aSurfaceKey) 1.320 + { 1.321 + nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey); 1.322 + if (!cache) 1.323 + return nullptr; // No cached surfaces for this image. 1.324 + 1.325 + nsRefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey); 1.326 + if (!surface) 1.327 + return nullptr; // Lookup in the per-image cache missed. 1.328 + 1.329 + mExpirationTracker.MarkUsed(surface); 1.330 + return surface->Drawable(); 1.331 + } 1.332 + 1.333 + bool CanHold(const Cost aCost) const 1.334 + { 1.335 + return aCost <= mMaxCost; 1.336 + } 1.337 + 1.338 + void Discard(const ImageKey aImageKey) 1.339 + { 1.340 + nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey); 1.341 + if (!cache) 1.342 + return; // No cached surfaces for this image, so nothing to do. 1.343 + 1.344 + // Discard all of the cached surfaces for this image. 1.345 + // XXX(seth): This is O(n^2) since for each item in the cache we are 1.346 + // removing an element from the costs array. Since n is expected to be 1.347 + // small, performance should be good, but if usage patterns change we should 1.348 + // change the data structure used for mCosts. 1.349 + cache->ForEach(DoStopTracking, this); 1.350 + 1.351 + // The per-image cache isn't needed anymore, so remove it as well. 1.352 + mImageCaches.Remove(aImageKey); 1.353 + } 1.354 + 1.355 + void DiscardAll() 1.356 + { 1.357 + // Remove in order of cost because mCosts is an array and the other data 1.358 + // structures are all hash tables. 1.359 + while (!mCosts.IsEmpty()) { 1.360 + Remove(mCosts.LastElement().GetSurface()); 1.361 + } 1.362 + } 1.363 + 1.364 + static PLDHashOperator DoStopTracking(const SurfaceKey&, 1.365 + CachedSurface* aSurface, 1.366 + void* aCache) 1.367 + { 1.368 + static_cast<SurfaceCacheImpl*>(aCache)->StopTracking(aSurface); 1.369 + return PL_DHASH_NEXT; 1.370 + } 1.371 + 1.372 + NS_IMETHOD 1.373 + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData) 1.374 + { 1.375 + return MOZ_COLLECT_REPORT( 1.376 + "imagelib-surface-cache", KIND_OTHER, UNITS_BYTES, 1.377 + SizeOfSurfacesEstimate(), 1.378 + "Memory used by the imagelib temporary surface cache."); 1.379 + } 1.380 + 1.381 + // XXX(seth): This is currently only an estimate and, since we don't know 1.382 + // which surfaces are in GPU memory and which aren't, it's reported as 1.383 + // KIND_OTHER and will also show up in heap-unclassified. Bug 923302 will 1.384 + // make this nicer. 1.385 + Cost SizeOfSurfacesEstimate() const 1.386 + { 1.387 + return mMaxCost - mAvailableCost; 1.388 + } 1.389 + 1.390 +private: 1.391 + already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey) 1.392 + { 1.393 + nsRefPtr<ImageSurfaceCache> imageCache; 1.394 + mImageCaches.Get(aImageKey, getter_AddRefs(imageCache)); 1.395 + return imageCache.forget(); 1.396 + } 1.397 + 1.398 + struct SurfaceTracker : public nsExpirationTracker<CachedSurface, 2> 1.399 + { 1.400 + SurfaceTracker(SurfaceCacheImpl* aCache, uint32_t aSurfaceCacheExpirationTimeMS) 1.401 + : nsExpirationTracker<CachedSurface, 2>(aSurfaceCacheExpirationTimeMS) 1.402 + , mCache(aCache) 1.403 + { } 1.404 + 1.405 + protected: 1.406 + virtual void NotifyExpired(CachedSurface* aSurface) MOZ_OVERRIDE 1.407 + { 1.408 + if (mCache) { 1.409 + mCache->Remove(aSurface); 1.410 + } 1.411 + } 1.412 + 1.413 + private: 1.414 + SurfaceCacheImpl* const mCache; // Weak pointer to owner. 1.415 + }; 1.416 + 1.417 + struct MemoryPressureObserver : public nsIObserver 1.418 + { 1.419 + NS_DECL_ISUPPORTS 1.420 + 1.421 + virtual ~MemoryPressureObserver() { } 1.422 + 1.423 + NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*) 1.424 + { 1.425 + if (sInstance && strcmp(aTopic, "memory-pressure") == 0) { 1.426 + sInstance->DiscardAll(); 1.427 + } 1.428 + return NS_OK; 1.429 + } 1.430 + }; 1.431 + 1.432 + 1.433 + nsTArray<CostEntry> mCosts; 1.434 + nsRefPtrHashtable<nsPtrHashKey<Image>, ImageSurfaceCache> mImageCaches; 1.435 + SurfaceTracker mExpirationTracker; 1.436 + nsRefPtr<MemoryPressureObserver> mMemoryPressureObserver; 1.437 + const Cost mMaxCost; 1.438 + Cost mAvailableCost; 1.439 +}; 1.440 + 1.441 +NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter) 1.442 +NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver) 1.443 + 1.444 +/////////////////////////////////////////////////////////////////////////////// 1.445 +// Public API 1.446 +/////////////////////////////////////////////////////////////////////////////// 1.447 + 1.448 +/* static */ void 1.449 +SurfaceCache::Initialize() 1.450 +{ 1.451 + // Initialize preferences. 1.452 + MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once"); 1.453 + 1.454 + // Length of time before an unused surface is removed from the cache, in milliseconds. 1.455 + // The default value gives an expiration time of 1 minute. 1.456 + uint32_t surfaceCacheExpirationTimeMS = 1.457 + Preferences::GetUint("image.mem.surfacecache.min_expiration_ms", 60 * 1000); 1.458 + 1.459 + // Maximum size of the surface cache, in kilobytes. 1.460 + // The default is 100MB. (But we may override this for e.g. B2G.) 1.461 + uint32_t surfaceCacheMaxSizeKB = 1.462 + Preferences::GetUint("image.mem.surfacecache.max_size_kb", 100 * 1024); 1.463 + 1.464 + // A knob determining the actual size of the surface cache. Currently the 1.465 + // cache is (size of main memory) / (surface cache size factor) KB 1.466 + // or (surface cache max size) KB, whichever is smaller. The formula 1.467 + // may change in the future, though. 1.468 + // The default value is 64, which yields a 64MB cache on a 4GB machine. 1.469 + // The smallest machines we are likely to run this code on have 256MB 1.470 + // of memory, which would yield a 4MB cache on the default setting. 1.471 + uint32_t surfaceCacheSizeFactor = 1.472 + Preferences::GetUint("image.mem.surfacecache.size_factor", 64); 1.473 + 1.474 + // Clamp to avoid division by zero below. 1.475 + surfaceCacheSizeFactor = max(surfaceCacheSizeFactor, 1u); 1.476 + 1.477 + // Compute the size of the surface cache. 1.478 + uint32_t proposedSize = PR_GetPhysicalMemorySize() / surfaceCacheSizeFactor; 1.479 + uint32_t surfaceCacheSizeBytes = min(proposedSize, surfaceCacheMaxSizeKB * 1024); 1.480 + 1.481 + // Create the surface cache singleton with the requested expiration time and 1.482 + // size. Note that the size is a limit that the cache may not grow beyond, but 1.483 + // we do not actually allocate any storage for surfaces at this time. 1.484 + sInstance = new SurfaceCacheImpl(surfaceCacheExpirationTimeMS, 1.485 + surfaceCacheSizeBytes); 1.486 + sInstance->InitMemoryReporter(); 1.487 +} 1.488 + 1.489 +/* static */ void 1.490 +SurfaceCache::Shutdown() 1.491 +{ 1.492 + MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?"); 1.493 + sInstance = nullptr; 1.494 +} 1.495 + 1.496 +/* static */ already_AddRefed<gfxDrawable> 1.497 +SurfaceCache::Lookup(const ImageKey aImageKey, 1.498 + const SurfaceKey& aSurfaceKey) 1.499 +{ 1.500 + MOZ_ASSERT(sInstance, "Should be initialized"); 1.501 + MOZ_ASSERT(NS_IsMainThread()); 1.502 + 1.503 + return sInstance->Lookup(aImageKey, aSurfaceKey); 1.504 +} 1.505 + 1.506 +/* static */ void 1.507 +SurfaceCache::Insert(DrawTarget* aTarget, 1.508 + const ImageKey aImageKey, 1.509 + const SurfaceKey& aSurfaceKey) 1.510 +{ 1.511 + MOZ_ASSERT(sInstance, "Should be initialized"); 1.512 + MOZ_ASSERT(NS_IsMainThread()); 1.513 + 1.514 + Cost cost = ComputeCost(aSurfaceKey.Size()); 1.515 + return sInstance->Insert(aTarget, aSurfaceKey.Size(), cost, aImageKey, 1.516 + aSurfaceKey); 1.517 +} 1.518 + 1.519 +/* static */ bool 1.520 +SurfaceCache::CanHold(const IntSize& aSize) 1.521 +{ 1.522 + MOZ_ASSERT(sInstance, "Should be initialized"); 1.523 + MOZ_ASSERT(NS_IsMainThread()); 1.524 + 1.525 + Cost cost = ComputeCost(aSize); 1.526 + return sInstance->CanHold(cost); 1.527 +} 1.528 + 1.529 +/* static */ void 1.530 +SurfaceCache::Discard(Image* aImageKey) 1.531 +{ 1.532 + MOZ_ASSERT(sInstance, "Should be initialized"); 1.533 + MOZ_ASSERT(NS_IsMainThread()); 1.534 + 1.535 + return sInstance->Discard(aImageKey); 1.536 +} 1.537 + 1.538 +/* static */ void 1.539 +SurfaceCache::DiscardAll() 1.540 +{ 1.541 + MOZ_ASSERT(sInstance, "Should be initialized"); 1.542 + MOZ_ASSERT(NS_IsMainThread()); 1.543 + 1.544 + return sInstance->DiscardAll(); 1.545 +} 1.546 + 1.547 +} // namespace image 1.548 +} // namespace mozilla