1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165 |
- /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /**
- * SurfaceCache is a service for caching temporary surfaces in imagelib.
- */
- #include "SurfaceCache.h"
- #include <algorithm>
- #include "mozilla/Assertions.h"
- #include "mozilla/Attributes.h"
- #include "mozilla/DebugOnly.h"
- #include "mozilla/Likely.h"
- #include "mozilla/Move.h"
- #include "mozilla/Pair.h"
- #include "mozilla/RefPtr.h"
- #include "mozilla/StaticMutex.h"
- #include "mozilla/StaticPtr.h"
- #include "mozilla/Tuple.h"
- #include "nsIMemoryReporter.h"
- #include "gfx2DGlue.h"
- #include "gfxPlatform.h"
- #include "gfxPrefs.h"
- #include "imgFrame.h"
- #include "Image.h"
- #include "ISurfaceProvider.h"
- #include "LookupResult.h"
- #include "nsExpirationTracker.h"
- #include "nsHashKeys.h"
- #include "nsRefPtrHashtable.h"
- #include "nsSize.h"
- #include "nsTArray.h"
- #include "prsystem.h"
- #include "ShutdownTracker.h"
- using std::max;
- using std::min;
- namespace mozilla {
- using namespace gfx;
- namespace image {
- class CachedSurface;
- class SurfaceCacheImpl;
- ///////////////////////////////////////////////////////////////////////////////
- // Static Data
- ///////////////////////////////////////////////////////////////////////////////
- // The single surface cache instance.
- static StaticRefPtr<SurfaceCacheImpl> sInstance;
- // The mutex protecting the surface cache.
- static StaticMutex sInstanceMutex;
- ///////////////////////////////////////////////////////////////////////////////
- // SurfaceCache Implementation
- ///////////////////////////////////////////////////////////////////////////////
- /**
- * Cost models the cost of storing a surface in the cache. Right now, this is
- * simply an estimate of the size of the surface in bytes, but in the future it
- * may be worth taking into account the cost of rematerializing the surface as
- * well.
- */
- typedef size_t Cost;
- static Cost
- ComputeCost(const IntSize& aSize, uint32_t aBytesPerPixel)
- {
- MOZ_ASSERT(aBytesPerPixel == 1 || aBytesPerPixel == 4);
- return aSize.width * aSize.height * aBytesPerPixel;
- }
- /**
- * Since we want to be able to make eviction decisions based on cost, we need to
- * be able to look up the CachedSurface which has a certain cost as well as the
- * cost associated with a certain CachedSurface. To make this possible, in data
- * structures we actually store a CostEntry, which contains a weak pointer to
- * its associated surface.
- *
- * To make usage of the weak pointer safe, SurfaceCacheImpl always calls
- * StartTracking after a surface is stored in the cache and StopTracking before
- * it is removed.
- */
- class CostEntry
- {
- public:
- CostEntry(NotNull<CachedSurface*> aSurface, Cost aCost)
- : mSurface(aSurface)
- , mCost(aCost)
- { }
- NotNull<CachedSurface*> Surface() const { return mSurface; }
- Cost GetCost() const { return mCost; }
- bool operator==(const CostEntry& aOther) const
- {
- return mSurface == aOther.mSurface &&
- mCost == aOther.mCost;
- }
- bool operator<(const CostEntry& aOther) const
- {
- return mCost < aOther.mCost ||
- (mCost == aOther.mCost && mSurface < aOther.mSurface);
- }
- private:
- NotNull<CachedSurface*> mSurface;
- Cost mCost;
- };
- /**
- * A CachedSurface associates a surface with a key that uniquely identifies that
- * surface.
- */
- class CachedSurface
- {
- ~CachedSurface() { }
- public:
- MOZ_DECLARE_REFCOUNTED_TYPENAME(CachedSurface)
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CachedSurface)
- explicit CachedSurface(NotNull<ISurfaceProvider*> aProvider)
- : mProvider(aProvider)
- , mIsLocked(false)
- { }
- DrawableSurface GetDrawableSurface() const
- {
- if (MOZ_UNLIKELY(IsPlaceholder())) {
- MOZ_ASSERT_UNREACHABLE("Called GetDrawableSurface() on a placeholder");
- return DrawableSurface();
- }
- return mProvider->Surface();
- }
- void SetLocked(bool aLocked)
- {
- if (IsPlaceholder()) {
- return; // Can't lock a placeholder.
- }
- // Update both our state and our provider's state. Some surface providers
- // are permanently locked; maintaining our own locking state enables us to
- // respect SetLocked() even when it's meaningless from the provider's
- // perspective.
- mIsLocked = aLocked;
- mProvider->SetLocked(aLocked);
- }
- bool IsLocked() const
- {
- return !IsPlaceholder() && mIsLocked && mProvider->IsLocked();
- }
- 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()
- {
- return image::CostEntry(WrapNotNull(this), mProvider->LogicalSizeInBytes());
- }
- // A helper type used by SurfaceCacheImpl::CollectSizeOfSurfaces.
- struct MOZ_STACK_CLASS SurfaceMemoryReport
- {
- SurfaceMemoryReport(nsTArray<SurfaceMemoryCounter>& aCounters,
- MallocSizeOf aMallocSizeOf)
- : mCounters(aCounters)
- , mMallocSizeOf(aMallocSizeOf)
- { }
- void Add(NotNull<CachedSurface*> aCachedSurface)
- {
- SurfaceMemoryCounter counter(aCachedSurface->GetSurfaceKey(),
- aCachedSurface->IsLocked());
- if (aCachedSurface->IsPlaceholder()) {
- return;
- }
- // Record the memory used by the ISurfaceProvider. This may not have a
- // straightforward relationship to the size of the surface that
- // DrawableRef() returns if the surface is generated dynamically. (i.e.,
- // for surfaces with PlaybackType::eAnimated.)
- size_t heap = 0;
- size_t nonHeap = 0;
- aCachedSurface->mProvider
- ->AddSizeOfExcludingThis(mMallocSizeOf, heap, nonHeap);
- counter.Values().SetDecodedHeap(heap);
- counter.Values().SetDecodedNonHeap(nonHeap);
- mCounters.AppendElement(counter);
- }
- private:
- nsTArray<SurfaceMemoryCounter>& mCounters;
- MallocSizeOf mMallocSizeOf;
- };
- private:
- nsExpirationState mExpirationState;
- NotNull<RefPtr<ISurfaceProvider>> mProvider;
- bool mIsLocked;
- };
- static int64_t
- AreaOfIntSize(const IntSize& aSize) {
- return static_cast<int64_t>(aSize.width) * static_cast<int64_t>(aSize.height);
- }
- /**
- * An ImageSurfaceCache is a per-image surface cache. For correctness we must be
- * able to remove all surfaces associated with an image when the image is
- * destroyed or invalidated. Since this will happen frequently, it makes sense
- * to make it cheap by storing the surfaces for each image separately.
- *
- * ImageSurfaceCache also keeps track of whether its associated image is locked
- * or unlocked.
- */
- class ImageSurfaceCache
- {
- ~ImageSurfaceCache() { }
- public:
- ImageSurfaceCache() : mLocked(false) { }
- MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageSurfaceCache)
- typedef
- nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
- bool IsEmpty() const { return mSurfaces.Count() == 0; }
- void Insert(NotNull<CachedSurface*> aSurface)
- {
- MOZ_ASSERT(!mLocked || aSurface->IsPlaceholder() || aSurface->IsLocked(),
- "Inserting an unlocked surface for a locked image");
- mSurfaces.Put(aSurface->GetSurfaceKey(), aSurface);
- }
- void Remove(NotNull<CachedSurface*> aSurface)
- {
- MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()),
- "Should not be removing a surface we don't have");
- mSurfaces.Remove(aSurface->GetSurfaceKey());
- }
- already_AddRefed<CachedSurface> Lookup(const SurfaceKey& aSurfaceKey)
- {
- RefPtr<CachedSurface> surface;
- mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface));
- return surface.forget();
- }
- Pair<already_AddRefed<CachedSurface>, MatchType>
- LookupBestMatch(const SurfaceKey& aIdealKey)
- {
- // Try for an exact match first.
- RefPtr<CachedSurface> exactMatch;
- mSurfaces.Get(aIdealKey, getter_AddRefs(exactMatch));
- if (exactMatch && exactMatch->IsDecoded()) {
- return MakePair(exactMatch.forget(), MatchType::EXACT);
- }
- // There's no perfect match, so find the best match we can.
- RefPtr<CachedSurface> bestMatch;
- for (auto iter = ConstIter(); !iter.Done(); iter.Next()) {
- NotNull<CachedSurface*> current = WrapNotNull(iter.UserData());
- const SurfaceKey& currentKey = current->GetSurfaceKey();
- // We never match a placeholder.
- if (current->IsPlaceholder()) {
- continue;
- }
- // Matching the playback type and SVG context is required.
- if (currentKey.Playback() != aIdealKey.Playback() ||
- currentKey.SVGContext() != aIdealKey.SVGContext()) {
- continue;
- }
- // Matching the flags is required.
- if (currentKey.Flags() != aIdealKey.Flags()) {
- continue;
- }
- // Anything is better than nothing! (Within the constraints we just
- // checked, of course.)
- if (!bestMatch) {
- bestMatch = current;
- continue;
- }
- MOZ_ASSERT(bestMatch, "Should have a current best match");
- // Always prefer completely decoded surfaces.
- bool bestMatchIsDecoded = bestMatch->IsDecoded();
- if (bestMatchIsDecoded && !current->IsDecoded()) {
- continue;
- }
- if (!bestMatchIsDecoded && current->IsDecoded()) {
- bestMatch = current;
- continue;
- }
- SurfaceKey bestMatchKey = bestMatch->GetSurfaceKey();
- // Compare sizes. We use an area-based heuristic here instead of computing a
- // truly optimal answer, since it seems very unlikely to make a difference
- // for realistic sizes.
- int64_t idealArea = AreaOfIntSize(aIdealKey.Size());
- int64_t currentArea = AreaOfIntSize(currentKey.Size());
- int64_t bestMatchArea = AreaOfIntSize(bestMatchKey.Size());
- // If the best match is smaller than the ideal size, prefer bigger sizes.
- if (bestMatchArea < idealArea) {
- if (currentArea > bestMatchArea) {
- bestMatch = current;
- }
- continue;
- }
- // Other, prefer sizes closer to the ideal size, but still not smaller.
- if (idealArea <= currentArea && currentArea < bestMatchArea) {
- bestMatch = current;
- continue;
- }
- // This surface isn't an improvement over the current best match.
- }
- MatchType matchType;
- if (bestMatch) {
- if (!exactMatch) {
- // No exact match, but we found a substitute.
- matchType = MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND;
- } else if (exactMatch != bestMatch) {
- // The exact match is still decoding, but we found a substitute.
- matchType = MatchType::SUBSTITUTE_BECAUSE_PENDING;
- } else {
- // The exact match is still decoding, but it's the best we've got.
- matchType = MatchType::EXACT;
- }
- } else {
- if (exactMatch) {
- // We found an "exact match"; it must have been a placeholder.
- MOZ_ASSERT(exactMatch->IsPlaceholder());
- matchType = MatchType::PENDING;
- } else {
- // We couldn't find an exact match *or* a substitute.
- matchType = MatchType::NOT_FOUND;
- }
- }
- return MakePair(bestMatch.forget(), matchType);
- }
- SurfaceTable::Iterator ConstIter() const
- {
- return mSurfaces.ConstIter();
- }
- void SetLocked(bool aLocked) { mLocked = aLocked; }
- bool IsLocked() const { return mLocked; }
- private:
- SurfaceTable mSurfaces;
- bool mLocked;
- };
- /**
- * 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.
- */
- class SurfaceCacheImpl final : public nsIMemoryReporter
- {
- public:
- NS_DECL_ISUPPORTS
- SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS,
- uint32_t aSurfaceCacheDiscardFactor,
- uint32_t aSurfaceCacheSize)
- : mExpirationTracker(aSurfaceCacheExpirationTimeMS)
- , mMemoryPressureObserver(new MemoryPressureObserver)
- , mDiscardFactor(aSurfaceCacheDiscardFactor)
- , mMaxCost(aSurfaceCacheSize)
- , mAvailableCost(aSurfaceCacheSize)
- , mLockedCost(0)
- , mOverflowCount(0)
- {
- nsCOMPtr<nsIObserverService> os = services::GetObserverService();
- if (os) {
- os->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
- }
- }
- private:
- virtual ~SurfaceCacheImpl()
- {
- nsCOMPtr<nsIObserverService> os = services::GetObserverService();
- if (os) {
- os->RemoveObserver(mMemoryPressureObserver, "memory-pressure");
- }
- UnregisterWeakMemoryReporter(this);
- }
- public:
- void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
- InsertOutcome Insert(NotNull<ISurfaceProvider*> aProvider,
- bool aSetAvailable,
- const StaticMutexAutoLock& aAutoLock)
- {
- // If this is a duplicate surface, refuse to replace the original.
- // XXX(seth): Calling Lookup() and then RemoveEntry() does the lookup
- // twice. We'll make this more efficient in bug 1185137.
- LookupResult result = Lookup(aProvider->GetImageKey(),
- aProvider->GetSurfaceKey(),
- aAutoLock,
- /* aMarkUsed = */ false);
- if (MOZ_UNLIKELY(result)) {
- return InsertOutcome::FAILURE_ALREADY_PRESENT;
- }
- if (result.Type() == MatchType::PENDING) {
- RemoveEntry(aProvider->GetImageKey(), aProvider->GetSurfaceKey(), aAutoLock);
- }
- MOZ_ASSERT(result.Type() == MatchType::NOT_FOUND ||
- result.Type() == MatchType::PENDING,
- "A LookupResult with no surface should be NOT_FOUND or PENDING");
- // If this is bigger than we can hold after discarding everything we can,
- // refuse to cache it.
- Cost cost = aProvider->LogicalSizeInBytes();
- if (MOZ_UNLIKELY(!CanHoldAfterDiscarding(cost))) {
- mOverflowCount++;
- return InsertOutcome::FAILURE;
- }
- // Remove elements in order of cost until we can fit this in the cache. Note
- // that locked surfaces aren't in mCosts, so we never remove them here.
- while (cost > mAvailableCost) {
- MOZ_ASSERT(!mCosts.IsEmpty(),
- "Removed everything and it still won't fit");
- Remove(mCosts.LastElement().Surface(), aAutoLock);
- }
- // Locate the appropriate per-image cache. If there's not an existing cache
- // for this image, create it.
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aProvider->GetImageKey());
- if (!cache) {
- cache = new ImageSurfaceCache;
- mImageCaches.Put(aProvider->GetImageKey(), cache);
- }
- // If we were asked to mark the cache entry available, do so.
- if (aSetAvailable) {
- aProvider->Availability().SetAvailable();
- }
- NotNull<RefPtr<CachedSurface>> surface =
- WrapNotNull(new CachedSurface(aProvider));
- // We require that locking succeed if the image is locked and we're not
- // inserting a placeholder; the caller may need to know this to handle
- // errors correctly.
- if (cache->IsLocked() && !surface->IsPlaceholder()) {
- surface->SetLocked(true);
- if (!surface->IsLocked()) {
- return InsertOutcome::FAILURE;
- }
- }
- // Insert.
- MOZ_ASSERT(cost <= mAvailableCost, "Inserting despite too large a cost");
- cache->Insert(surface);
- StartTracking(surface, aAutoLock);
- return InsertOutcome::SUCCESS;
- }
- void Remove(NotNull<CachedSurface*> aSurface,
- const StaticMutexAutoLock& aAutoLock)
- {
- ImageKey imageKey = aSurface->GetImageKey();
- RefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
- MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
- // If the surface was not a placeholder, tell its image that we discarded it.
- if (!aSurface->IsPlaceholder()) {
- static_cast<Image*>(imageKey)->OnSurfaceDiscarded();
- }
- StopTracking(aSurface, aAutoLock);
- cache->Remove(aSurface);
- // Remove the per-image cache if it's unneeded now. (Keep it if the image is
- // locked, since the per-image cache is where we store that state.)
- if (cache->IsEmpty() && !cache->IsLocked()) {
- mImageCaches.Remove(imageKey);
- }
- }
- void StartTracking(NotNull<CachedSurface*> aSurface,
- const StaticMutexAutoLock& aAutoLock)
- {
- CostEntry costEntry = aSurface->GetCostEntry();
- MOZ_ASSERT(costEntry.GetCost() <= mAvailableCost,
- "Cost too large and the caller didn't catch it");
- mAvailableCost -= costEntry.GetCost();
- if (aSurface->IsLocked()) {
- mLockedCost += costEntry.GetCost();
- MOZ_ASSERT(mLockedCost <= mMaxCost, "Locked more than we can hold?");
- } else {
- mCosts.InsertElementSorted(costEntry);
- // This may fail during XPCOM shutdown, so we need to ensure the object is
- // tracked before calling RemoveObject in StopTracking.
- mExpirationTracker.AddObjectLocked(aSurface, aAutoLock);
- }
- }
- void StopTracking(NotNull<CachedSurface*> aSurface,
- const StaticMutexAutoLock& aAutoLock)
- {
- CostEntry costEntry = aSurface->GetCostEntry();
- if (aSurface->IsLocked()) {
- MOZ_ASSERT(mLockedCost >= costEntry.GetCost(), "Costs don't balance");
- mLockedCost -= costEntry.GetCost();
- // XXX(seth): It'd be nice to use an O(log n) lookup here. This is O(n).
- MOZ_ASSERT(!mCosts.Contains(costEntry),
- "Shouldn't have a cost entry for a locked surface");
- } else {
- if (MOZ_LIKELY(aSurface->GetExpirationState()->IsTracked())) {
- mExpirationTracker.RemoveObjectLocked(aSurface, aAutoLock);
- } else {
- // Our call to AddObject must have failed in StartTracking; most likely
- // we're in XPCOM shutdown right now.
- NS_ASSERTION(ShutdownTracker::ShutdownHasStarted(),
- "Not expiration-tracking an unlocked surface!");
- }
- DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
- MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
- }
- mAvailableCost += costEntry.GetCost();
- MOZ_ASSERT(mAvailableCost <= mMaxCost,
- "More available cost than we started with");
- }
- LookupResult Lookup(const ImageKey aImageKey,
- const SurfaceKey& aSurfaceKey,
- const StaticMutexAutoLock& aAutoLock,
- bool aMarkUsed = true)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache) {
- // No cached surfaces for this image.
- return LookupResult(MatchType::NOT_FOUND);
- }
- RefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
- if (!surface) {
- // Lookup in the per-image cache missed.
- return LookupResult(MatchType::NOT_FOUND);
- }
- if (surface->IsPlaceholder()) {
- return LookupResult(MatchType::PENDING);
- }
- DrawableSurface drawableSurface = surface->GetDrawableSurface();
- if (!drawableSurface) {
- // The surface was released by the operating system. Remove the cache
- // entry as well.
- Remove(WrapNotNull(surface), aAutoLock);
- return LookupResult(MatchType::NOT_FOUND);
- }
- if (aMarkUsed) {
- MarkUsed(WrapNotNull(surface), WrapNotNull(cache), aAutoLock);
- }
- MOZ_ASSERT(surface->GetSurfaceKey() == aSurfaceKey,
- "Lookup() not returning an exact match?");
- return LookupResult(Move(drawableSurface), MatchType::EXACT);
- }
- LookupResult LookupBestMatch(const ImageKey aImageKey,
- const SurfaceKey& aSurfaceKey,
- const StaticMutexAutoLock& aAutoLock)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache) {
- // No cached surfaces for this image.
- return LookupResult(MatchType::NOT_FOUND);
- }
- // Repeatedly look up the best match, trying again if the resulting surface
- // has been freed by the operating system, until we can either lock a
- // surface for drawing or there are no matching surfaces left.
- // XXX(seth): This is O(N^2), but N is expected to be very small. If we
- // encounter a performance problem here we can revisit this.
- RefPtr<CachedSurface> surface;
- DrawableSurface drawableSurface;
- MatchType matchType = MatchType::NOT_FOUND;
- while (true) {
- Tie(surface, matchType) = cache->LookupBestMatch(aSurfaceKey);
- if (!surface) {
- return LookupResult(matchType); // Lookup in the per-image cache missed.
- }
- drawableSurface = surface->GetDrawableSurface();
- if (drawableSurface) {
- break;
- }
- // The surface was released by the operating system. Remove the cache
- // entry as well.
- Remove(WrapNotNull(surface), aAutoLock);
- }
- MOZ_ASSERT_IF(matchType == MatchType::EXACT,
- surface->GetSurfaceKey() == aSurfaceKey);
- MOZ_ASSERT_IF(matchType == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND ||
- matchType == MatchType::SUBSTITUTE_BECAUSE_PENDING,
- surface->GetSurfaceKey().SVGContext() == aSurfaceKey.SVGContext() &&
- surface->GetSurfaceKey().Playback() == aSurfaceKey.Playback() &&
- surface->GetSurfaceKey().Flags() == aSurfaceKey.Flags());
- if (matchType == MatchType::EXACT) {
- MarkUsed(WrapNotNull(surface), WrapNotNull(cache), aAutoLock);
- }
- return LookupResult(Move(drawableSurface), matchType);
- }
- bool CanHold(const Cost aCost) const
- {
- return aCost <= mMaxCost;
- }
- size_t MaximumCapacity() const
- {
- return size_t(mMaxCost);
- }
- void SurfaceAvailable(NotNull<ISurfaceProvider*> aProvider,
- const StaticMutexAutoLock& aAutoLock)
- {
- if (!aProvider->Availability().IsPlaceholder()) {
- MOZ_ASSERT_UNREACHABLE("Calling SurfaceAvailable on non-placeholder");
- return;
- }
- // Reinsert the provider, requesting that Insert() mark it available. This
- // may or may not succeed, depending on whether some other decoder has
- // beaten us to the punch and inserted a non-placeholder version of this
- // surface first, but it's fine either way.
- // XXX(seth): This could be implemented more efficiently; we should be able
- // to just update our data structures without reinserting.
- Insert(aProvider, /* aSetAvailable = */ true, aAutoLock);
- }
- void LockImage(const ImageKey aImageKey)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache) {
- cache = new ImageSurfaceCache;
- mImageCaches.Put(aImageKey, cache);
- }
- cache->SetLocked(true);
- // We don't relock this image's existing surfaces right away; instead, the
- // image should arrange for Lookup() to touch them if they are still useful.
- }
- void UnlockImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache || !cache->IsLocked()) {
- return; // Already unlocked.
- }
- cache->SetLocked(false);
- DoUnlockSurfaces(WrapNotNull(cache), aAutoLock);
- }
- void UnlockEntries(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache || !cache->IsLocked()) {
- return; // Already unlocked.
- }
- // (Note that we *don't* unlock the per-image cache here; that's the
- // difference between this and UnlockImage.)
- DoUnlockSurfaces(WrapNotNull(cache), aAutoLock);
- }
- void RemoveImage(const ImageKey aImageKey, const StaticMutexAutoLock& aAutoLock)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache) {
- return; // No cached surfaces for this image, so nothing to do.
- }
- // Discard all of the cached surfaces for this image.
- // XXX(seth): This is O(n^2) since for each item in the cache we are
- // removing an element from the costs array. Since n is expected to be
- // small, performance should be good, but if usage patterns change we should
- // change the data structure used for mCosts.
- for (auto iter = cache->ConstIter(); !iter.Done(); iter.Next()) {
- StopTracking(WrapNotNull(iter.UserData()), aAutoLock);
- }
- // The per-image cache isn't needed anymore, so remove it as well.
- // This implicitly unlocks the image if it was locked.
- mImageCaches.Remove(aImageKey);
- }
- 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);
- }
- }
- void DiscardForMemoryPressure(const StaticMutexAutoLock& aAutoLock)
- {
- // Compute our discardable cost. Since locked surfaces aren't discardable,
- // we exclude them.
- const Cost discardableCost = (mMaxCost - mAvailableCost) - mLockedCost;
- MOZ_ASSERT(discardableCost <= mMaxCost, "Discardable cost doesn't add up");
- // Our target is to raise our available cost by (1 / mDiscardFactor) of our
- // discardable cost - in other words, we want to end up with about
- // (discardableCost / mDiscardFactor) fewer bytes stored in the surface
- // cache after we're done.
- const Cost targetCost = mAvailableCost + (discardableCost / mDiscardFactor);
- if (targetCost > mMaxCost - mLockedCost) {
- MOZ_ASSERT_UNREACHABLE("Target cost is more than we can discard");
- DiscardAll(aAutoLock);
- return;
- }
- // Discard surfaces until we've reduced our cost to our target cost.
- while (mAvailableCost < targetCost) {
- MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and still not done");
- Remove(mCosts.LastElement().Surface(), aAutoLock);
- }
- }
- void LockSurface(NotNull<CachedSurface*> aSurface,
- const StaticMutexAutoLock& aAutoLock)
- {
- if (aSurface->IsPlaceholder() || aSurface->IsLocked()) {
- return;
- }
- StopTracking(aSurface, aAutoLock);
- // Lock the surface. This can fail.
- aSurface->SetLocked(true);
- StartTracking(aSurface, aAutoLock);
- }
- NS_IMETHOD
- CollectReports(nsIHandleReportCallback* aHandleReport,
- nsISupports* aData,
- bool aAnonymize) override
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- // We have explicit memory reporting for the surface cache which is more
- // accurate than the cost metrics we report here, but these metrics are
- // still useful to report, since they control the cache's behavior.
- MOZ_COLLECT_REPORT(
- "imagelib-surface-cache-estimated-total",
- KIND_OTHER, UNITS_BYTES, (mMaxCost - mAvailableCost),
- "Estimated total memory used by the imagelib surface cache.");
- MOZ_COLLECT_REPORT(
- "imagelib-surface-cache-estimated-locked",
- KIND_OTHER, UNITS_BYTES, mLockedCost,
- "Estimated memory used by locked surfaces in the imagelib surface cache.");
- MOZ_COLLECT_REPORT(
- "imagelib-surface-cache-overflow-count",
- KIND_OTHER, UNITS_COUNT, mOverflowCount,
- "Count of how many times the surface cache has hit its capacity and been "
- "unable to insert a new surface.");
- return NS_OK;
- }
- void CollectSizeOfSurfaces(const ImageKey aImageKey,
- nsTArray<SurfaceMemoryCounter>& aCounters,
- MallocSizeOf aMallocSizeOf)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache) {
- return; // No surfaces for this image.
- }
- // Report all surfaces in the per-image cache.
- CachedSurface::SurfaceMemoryReport report(aCounters, aMallocSizeOf);
- for (auto iter = cache->ConstIter(); !iter.Done(); iter.Next()) {
- report.Add(WrapNotNull(iter.UserData()));
- }
- }
- private:
- already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
- {
- RefPtr<ImageSurfaceCache> imageCache;
- mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
- return imageCache.forget();
- }
- // This is similar to CanHold() except that it takes into account the costs of
- // locked surfaces. It's used internally in Insert(), but it's not exposed
- // publicly because we permit multithreaded access to the surface cache, which
- // means that the result would be meaningless: another thread could insert a
- // surface or lock an image at any time.
- bool CanHoldAfterDiscarding(const Cost aCost) const
- {
- return aCost <= mMaxCost - mLockedCost;
- }
- void MarkUsed(NotNull<CachedSurface*> aSurface,
- NotNull<ImageSurfaceCache*> aCache,
- const StaticMutexAutoLock& aAutoLock)
- {
- if (aCache->IsLocked()) {
- LockSurface(aSurface, aAutoLock);
- } else {
- mExpirationTracker.MarkUsedLocked(aSurface, aAutoLock);
- }
- }
- void DoUnlockSurfaces(NotNull<ImageSurfaceCache*> aCache,
- const StaticMutexAutoLock& aAutoLock)
- {
- // Unlock all the surfaces the per-image cache is holding.
- for (auto iter = aCache->ConstIter(); !iter.Done(); iter.Next()) {
- NotNull<CachedSurface*> surface = WrapNotNull(iter.UserData());
- if (surface->IsPlaceholder() || !surface->IsLocked()) {
- continue;
- }
- StopTracking(surface, aAutoLock);
- surface->SetLocked(false);
- StartTracking(surface, aAutoLock);
- }
- }
- void RemoveEntry(const ImageKey aImageKey,
- const SurfaceKey& aSurfaceKey,
- const StaticMutexAutoLock& aAutoLock)
- {
- RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
- if (!cache) {
- return; // No cached surfaces for this image.
- }
- RefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
- if (!surface) {
- return; // Lookup in the per-image cache missed.
- }
- Remove(WrapNotNull(surface), aAutoLock);
- }
- struct SurfaceTracker : public ExpirationTrackerImpl<CachedSurface, 2,
- StaticMutex,
- StaticMutexAutoLock>
- {
- explicit SurfaceTracker(uint32_t aSurfaceCacheExpirationTimeMS)
- : ExpirationTrackerImpl<CachedSurface, 2,
- StaticMutex, StaticMutexAutoLock>(
- aSurfaceCacheExpirationTimeMS, "SurfaceTracker")
- { }
- protected:
- void NotifyExpiredLocked(CachedSurface* aSurface,
- const StaticMutexAutoLock& aAutoLock) override
- {
- sInstance->Remove(WrapNotNull(aSurface), aAutoLock);
- }
- StaticMutex& GetMutex() override
- {
- return sInstanceMutex;
- }
- };
- struct MemoryPressureObserver : public nsIObserver
- {
- NS_DECL_ISUPPORTS
- NS_IMETHOD Observe(nsISupports*,
- const char* aTopic,
- const char16_t*) override
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (sInstance && strcmp(aTopic, "memory-pressure") == 0) {
- sInstance->DiscardForMemoryPressure(lock);
- }
- return NS_OK;
- }
- private:
- virtual ~MemoryPressureObserver() { }
- };
- nsTArray<CostEntry> mCosts;
- nsRefPtrHashtable<nsPtrHashKey<Image>,
- ImageSurfaceCache> mImageCaches;
- SurfaceTracker mExpirationTracker;
- RefPtr<MemoryPressureObserver> mMemoryPressureObserver;
- const uint32_t mDiscardFactor;
- const Cost mMaxCost;
- Cost mAvailableCost;
- Cost mLockedCost;
- size_t mOverflowCount;
- };
- NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
- NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
- ///////////////////////////////////////////////////////////////////////////////
- // Public API
- ///////////////////////////////////////////////////////////////////////////////
- /* static */ void
- SurfaceCache::Initialize()
- {
- // Initialize preferences.
- MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(!sInstance, "Shouldn't initialize more than once");
- // See gfxPrefs for the default values of these preferences.
- // Length of time before an unused surface is removed from the cache, in
- // milliseconds.
- uint32_t surfaceCacheExpirationTimeMS =
- gfxPrefs::ImageMemSurfaceCacheMinExpirationMS();
- // What fraction of the memory used by the surface cache we should discard
- // when we get a memory pressure notification. This value is interpreted as
- // 1/N, so 1 means to discard everything, 2 means to discard about half of the
- // memory we're using, and so forth. We clamp it to avoid division by zero.
- uint32_t surfaceCacheDiscardFactor =
- max(gfxPrefs::ImageMemSurfaceCacheDiscardFactor(), 1u);
- // Maximum size of the surface cache, in kilobytes.
- uint64_t surfaceCacheMaxSizeKB = gfxPrefs::ImageMemSurfaceCacheMaxSizeKB();
- // A knob determining the actual size of the surface cache. Currently the
- // cache is (size of main memory) / (surface cache size factor) KB
- // or (surface cache max size) KB, whichever is smaller. The formula
- // may change in the future, though.
- // For example, a value of 4 would yield a 256MB cache on a 1GB machine.
- // The smallest machines we are likely to run this code on have 256MB
- // of memory, which would yield a 64MB cache on this setting.
- // We clamp this value to avoid division by zero.
- uint32_t surfaceCacheSizeFactor =
- max(gfxPrefs::ImageMemSurfaceCacheSizeFactor(), 1u);
- // Compute the size of the surface cache.
- uint64_t memorySize = PR_GetPhysicalMemorySize();
- if (memorySize == 0) {
- MOZ_ASSERT_UNREACHABLE("PR_GetPhysicalMemorySize not implemented here");
- memorySize = 256 * 1024 * 1024; // Fall back to 256MB.
- }
- uint64_t proposedSize = memorySize / surfaceCacheSizeFactor;
- uint64_t surfaceCacheSizeBytes = min(proposedSize,
- surfaceCacheMaxSizeKB * 1024);
- uint32_t finalSurfaceCacheSizeBytes =
- min(surfaceCacheSizeBytes, uint64_t(UINT32_MAX));
- // Create the surface cache singleton with the requested settings. Note that
- // the size is a limit that the cache may not grow beyond, but we do not
- // actually allocate any storage for surfaces at this time.
- sInstance = new SurfaceCacheImpl(surfaceCacheExpirationTimeMS,
- surfaceCacheDiscardFactor,
- finalSurfaceCacheSizeBytes);
- sInstance->InitMemoryReporter();
- }
- /* static */ void
- SurfaceCache::Shutdown()
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
- sInstance = nullptr;
- }
- /* static */ LookupResult
- SurfaceCache::Lookup(const ImageKey aImageKey,
- const SurfaceKey& aSurfaceKey)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return LookupResult(MatchType::NOT_FOUND);
- }
- return sInstance->Lookup(aImageKey, aSurfaceKey, lock);
- }
- /* static */ LookupResult
- SurfaceCache::LookupBestMatch(const ImageKey aImageKey,
- const SurfaceKey& aSurfaceKey)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return LookupResult(MatchType::NOT_FOUND);
- }
- return sInstance->LookupBestMatch(aImageKey, aSurfaceKey, lock);
- }
- /* static */ InsertOutcome
- SurfaceCache::Insert(NotNull<ISurfaceProvider*> aProvider)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return InsertOutcome::FAILURE;
- }
- return sInstance->Insert(aProvider, /* aSetAvailable = */ false, lock);
- }
- /* static */ bool
- SurfaceCache::CanHold(const IntSize& aSize, uint32_t aBytesPerPixel /* = 4 */)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return false;
- }
- Cost cost = ComputeCost(aSize, aBytesPerPixel);
- return sInstance->CanHold(cost);
- }
- /* static */ bool
- SurfaceCache::CanHold(size_t aSize)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return false;
- }
- return sInstance->CanHold(aSize);
- }
- /* static */ void
- SurfaceCache::SurfaceAvailable(NotNull<ISurfaceProvider*> aProvider)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return;
- }
- sInstance->SurfaceAvailable(aProvider, lock);
- }
- /* static */ void
- SurfaceCache::LockImage(const ImageKey aImageKey)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (sInstance) {
- return sInstance->LockImage(aImageKey);
- }
- }
- /* static */ void
- SurfaceCache::UnlockImage(const ImageKey aImageKey)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (sInstance) {
- return sInstance->UnlockImage(aImageKey, lock);
- }
- }
- /* static */ void
- SurfaceCache::UnlockEntries(const ImageKey aImageKey)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (sInstance) {
- return sInstance->UnlockEntries(aImageKey, lock);
- }
- }
- /* static */ void
- SurfaceCache::RemoveImage(const ImageKey aImageKey)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (sInstance) {
- sInstance->RemoveImage(aImageKey, lock);
- }
- }
- /* static */ void
- SurfaceCache::DiscardAll()
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (sInstance) {
- sInstance->DiscardAll(lock);
- }
- }
- /* static */ void
- SurfaceCache::CollectSizeOfSurfaces(const ImageKey aImageKey,
- nsTArray<SurfaceMemoryCounter>& aCounters,
- MallocSizeOf aMallocSizeOf)
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return;
- }
- return sInstance->CollectSizeOfSurfaces(aImageKey, aCounters, aMallocSizeOf);
- }
- /* static */ size_t
- SurfaceCache::MaximumCapacity()
- {
- StaticMutexAutoLock lock(sInstanceMutex);
- if (!sInstance) {
- return 0;
- }
- return sInstance->MaximumCapacity();
- }
- } // namespace image
- } // namespace mozilla
|