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