SurfaceCache.cpp 38 KB

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