mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1060869 (Part 1) - Add lifetime control to SurfaceCache. r=dholbert
--HG-- extra : rebase_source : f206133c8076aa17afc93d6ffd7f6fabbc8de4d5
This commit is contained in:
parent
0f9ff85399
commit
35a8f74544
@ -211,7 +211,8 @@ public:
|
||||
// Insert the new surface into the cache immediately. We need to do this so
|
||||
// that we won't start multiple scaling jobs for the same size.
|
||||
SurfaceCache::Insert(mDstRef.get(), ImageKey(mImage.get()),
|
||||
RasterSurfaceKey(mDstSize.ToIntSize(), mImageFlags));
|
||||
RasterSurfaceKey(mDstSize.ToIntSize(), mImageFlags),
|
||||
Lifetime::Transient);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -265,9 +266,9 @@ public:
|
||||
NS_WARNING("HQ scaling failed");
|
||||
|
||||
// Remove the frame from the cache since we know we don't need it.
|
||||
SurfaceCache::RemoveIfPresent(ImageKey(mImage.get()),
|
||||
RasterSurfaceKey(mDstSize.ToIntSize(),
|
||||
mImageFlags));
|
||||
SurfaceCache::RemoveSurface(ImageKey(mImage.get()),
|
||||
RasterSurfaceKey(mDstSize.ToIntSize(),
|
||||
mImageFlags));
|
||||
|
||||
// Release everything we're holding, too.
|
||||
mSrcRef.reset();
|
||||
@ -374,7 +375,7 @@ RasterImage::~RasterImage()
|
||||
}
|
||||
|
||||
// Release any HQ scaled frames from the surface cache.
|
||||
SurfaceCache::Discard(this);
|
||||
SurfaceCache::RemoveImage(ImageKey(this));
|
||||
|
||||
mAnim = nullptr;
|
||||
|
||||
|
@ -118,18 +118,18 @@ class CachedSurface
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(CachedSurface)
|
||||
|
||||
CachedSurface(imgFrame* aSurface,
|
||||
const IntSize aTargetSize,
|
||||
const Cost aCost,
|
||||
const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
CachedSurface(imgFrame* aSurface,
|
||||
const Cost aCost,
|
||||
const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
const Lifetime aLifetime)
|
||||
: mSurface(aSurface)
|
||||
, mTargetSize(aTargetSize)
|
||||
, mCost(aCost)
|
||||
, mImageKey(aImageKey)
|
||||
, mSurfaceKey(aSurfaceKey)
|
||||
, mLifetime(aLifetime)
|
||||
{
|
||||
MOZ_ASSERT(mSurface, "Must have a valid SourceSurface");
|
||||
MOZ_ASSERT(mSurface, "Must have a valid surface");
|
||||
MOZ_ASSERT(mImageKey, "Must have a valid image key");
|
||||
}
|
||||
|
||||
@ -138,18 +138,34 @@ public:
|
||||
return mSurface->DrawableRef();
|
||||
}
|
||||
|
||||
void SetLocked(bool aLocked)
|
||||
{
|
||||
if (aLocked && mLifetime == Lifetime::Persistent) {
|
||||
// This may fail, and that's OK. We make no guarantees about whether
|
||||
// locking is successful if you call SurfaceCache::LockImage() after
|
||||
// SurfaceCache::Insert().
|
||||
mDrawableRef = mSurface->DrawableRef();
|
||||
} else {
|
||||
mDrawableRef.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsLocked() const { return bool(mDrawableRef); }
|
||||
|
||||
ImageKey GetImageKey() const { return mImageKey; }
|
||||
SurfaceKey GetSurfaceKey() const { return mSurfaceKey; }
|
||||
CostEntry GetCostEntry() { return image::CostEntry(this, mCost); }
|
||||
nsExpirationState* GetExpirationState() { return &mExpirationState; }
|
||||
Lifetime GetLifetime() const { return mLifetime; }
|
||||
|
||||
private:
|
||||
nsExpirationState mExpirationState;
|
||||
nsRefPtr<imgFrame> mSurface;
|
||||
const IntSize mTargetSize;
|
||||
DrawableFrameRef mDrawableRef;
|
||||
const Cost mCost;
|
||||
const ImageKey mImageKey;
|
||||
const SurfaceKey mSurfaceKey;
|
||||
const Lifetime mLifetime;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -157,11 +173,16 @@ private:
|
||||
* 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() {}
|
||||
~ImageSurfaceCache() { }
|
||||
public:
|
||||
ImageSurfaceCache() : mLocked(false) { }
|
||||
|
||||
NS_INLINE_DECL_REFCOUNTING(ImageSurfaceCache)
|
||||
|
||||
typedef nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
|
||||
@ -171,6 +192,9 @@ public:
|
||||
void Insert(const SurfaceKey& aKey, CachedSurface* aSurface)
|
||||
{
|
||||
MOZ_ASSERT(aSurface, "Should have a surface");
|
||||
MOZ_ASSERT(!mLocked || aSurface->GetLifetime() != Lifetime::Persistent ||
|
||||
aSurface->IsLocked(),
|
||||
"Inserting an unlocked persistent surface for a locked image");
|
||||
mSurfaces.Put(aKey, aSurface);
|
||||
}
|
||||
|
||||
@ -195,8 +219,12 @@ public:
|
||||
mSurfaces.EnumerateRead(aFunction, aData);
|
||||
}
|
||||
|
||||
void SetLocked(bool aLocked) { mLocked = aLocked; }
|
||||
bool IsLocked() const { return mLocked; }
|
||||
|
||||
private:
|
||||
SurfaceTable mSurfaces;
|
||||
bool mLocked;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -218,6 +246,7 @@ public:
|
||||
, mMemoryPressureObserver(new MemoryPressureObserver)
|
||||
, mMaxCost(aSurfaceCacheSize)
|
||||
, mAvailableCost(aSurfaceCacheSize)
|
||||
, mLockedCost(0)
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (os)
|
||||
@ -239,23 +268,22 @@ public:
|
||||
RegisterWeakMemoryReporter(this);
|
||||
}
|
||||
|
||||
void Insert(imgFrame* aSurface,
|
||||
IntSize aTargetSize,
|
||||
bool Insert(imgFrame* aSurface,
|
||||
const Cost aCost,
|
||||
const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
Lifetime aLifetime)
|
||||
{
|
||||
MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey),
|
||||
"Inserting a duplicate surface into the SurfaceCache");
|
||||
|
||||
// If this is bigger than the maximum cache size, refuse to cache it.
|
||||
if (!CanHold(aCost))
|
||||
return;
|
||||
// If this is bigger than we can hold after discarding everything we can,
|
||||
// refuse to cache it.
|
||||
if (!CanHoldAfterDiscarding(aCost))
|
||||
return false;
|
||||
|
||||
nsRefPtr<CachedSurface> surface =
|
||||
new CachedSurface(aSurface, aTargetSize, aCost, aImageKey, aSurfaceKey);
|
||||
|
||||
// Remove elements in order of cost until we can fit this in the cache.
|
||||
// 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 (aCost > mAvailableCost) {
|
||||
MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit");
|
||||
Remove(mCosts.LastElement().GetSurface());
|
||||
@ -269,10 +297,24 @@ public:
|
||||
mImageCaches.Put(aImageKey, cache);
|
||||
}
|
||||
|
||||
nsRefPtr<CachedSurface> surface =
|
||||
new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime);
|
||||
|
||||
// We require that locking succeed if the image is locked and the surface is
|
||||
// persistent; the caller may need to know this to handle errors correctly.
|
||||
if (cache->IsLocked() && aLifetime == Lifetime::Persistent) {
|
||||
surface->SetLocked(true);
|
||||
if (!surface->IsLocked()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert.
|
||||
MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost");
|
||||
cache->Insert(aSurfaceKey, surface);
|
||||
StartTracking(surface);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Remove(CachedSurface* aSurface)
|
||||
@ -286,8 +328,9 @@ public:
|
||||
StopTracking(aSurface);
|
||||
cache->Remove(aSurface);
|
||||
|
||||
// Remove the per-image cache if it's unneeded now.
|
||||
if (cache->IsEmpty()) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@ -299,8 +342,14 @@ public:
|
||||
"Cost too large and the caller didn't catch it");
|
||||
|
||||
mAvailableCost -= costEntry.GetCost();
|
||||
mCosts.InsertElementSorted(costEntry);
|
||||
mExpirationTracker.AddObject(aSurface);
|
||||
|
||||
if (aSurface->IsLocked()) {
|
||||
mLockedCost += costEntry.GetCost();
|
||||
MOZ_ASSERT(mLockedCost <= mMaxCost, "Locked more than we can hold?");
|
||||
} else {
|
||||
mCosts.InsertElementSorted(costEntry);
|
||||
mExpirationTracker.AddObject(aSurface);
|
||||
}
|
||||
}
|
||||
|
||||
void StopTracking(CachedSurface* aSurface)
|
||||
@ -308,12 +357,21 @@ public:
|
||||
MOZ_ASSERT(aSurface, "Should have a surface");
|
||||
CostEntry costEntry = aSurface->GetCostEntry();
|
||||
|
||||
mExpirationTracker.RemoveObject(aSurface);
|
||||
DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
|
||||
mAvailableCost += costEntry.GetCost();
|
||||
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 {
|
||||
mExpirationTracker.RemoveObject(aSurface);
|
||||
DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
|
||||
MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
|
||||
}
|
||||
|
||||
MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
|
||||
MOZ_ASSERT(mAvailableCost <= mMaxCost, "More available cost than we started with");
|
||||
mAvailableCost += costEntry.GetCost();
|
||||
MOZ_ASSERT(mAvailableCost <= mMaxCost,
|
||||
"More available cost than we started with");
|
||||
}
|
||||
|
||||
DrawableFrameRef Lookup(const ImageKey aImageKey,
|
||||
@ -335,12 +393,15 @@ public:
|
||||
return DrawableFrameRef();
|
||||
}
|
||||
|
||||
mExpirationTracker.MarkUsed(surface);
|
||||
if (!surface->IsLocked()) {
|
||||
mExpirationTracker.MarkUsed(surface);
|
||||
}
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
void RemoveIfPresent(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
void RemoveSurface(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
{
|
||||
nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
|
||||
if (!cache)
|
||||
@ -358,7 +419,33 @@ public:
|
||||
return aCost <= mMaxCost;
|
||||
}
|
||||
|
||||
void Discard(const ImageKey aImageKey)
|
||||
void LockImage(const ImageKey aImageKey)
|
||||
{
|
||||
nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
|
||||
if (!cache) {
|
||||
cache = new ImageSurfaceCache;
|
||||
mImageCaches.Put(aImageKey, cache);
|
||||
}
|
||||
|
||||
cache->SetLocked(true);
|
||||
|
||||
// Try to lock all the surfaces the per-image cache is holding.
|
||||
cache->ForEach(DoLockSurface, this);
|
||||
}
|
||||
|
||||
void UnlockImage(const ImageKey aImageKey)
|
||||
{
|
||||
nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
|
||||
if (!cache)
|
||||
return; // Already unlocked and removed.
|
||||
|
||||
cache->SetLocked(false);
|
||||
|
||||
// Unlock all the surfaces the per-image cache is holding.
|
||||
cache->ForEach(DoUnlockSurface, this);
|
||||
}
|
||||
|
||||
void RemoveImage(const ImageKey aImageKey)
|
||||
{
|
||||
nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
|
||||
if (!cache)
|
||||
@ -372,13 +459,16 @@ public:
|
||||
cache->ForEach(DoStopTracking, this);
|
||||
|
||||
// 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()
|
||||
{
|
||||
// Remove in order of cost because mCosts is an array and the other data
|
||||
// structures are all hash tables.
|
||||
// structures are all hash tables. Note that locked surfaces (persistent
|
||||
// surfaces belonging to locked images) are not removed, since they aren't
|
||||
// present in mCosts.
|
||||
while (!mCosts.IsEmpty()) {
|
||||
Remove(mCosts.LastElement().GetSurface());
|
||||
}
|
||||
@ -392,14 +482,64 @@ public:
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
|
||||
bool aAnonymize)
|
||||
static PLDHashOperator DoLockSurface(const SurfaceKey&,
|
||||
CachedSurface* aSurface,
|
||||
void* aCache)
|
||||
{
|
||||
return MOZ_COLLECT_REPORT(
|
||||
"imagelib-surface-cache", KIND_OTHER, UNITS_BYTES,
|
||||
SizeOfSurfacesEstimate(),
|
||||
"Memory used by the imagelib temporary surface cache.");
|
||||
if (aSurface->GetLifetime() == Lifetime::Transient ||
|
||||
aSurface->IsLocked()) {
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
auto cache = static_cast<SurfaceCacheImpl*>(aCache);
|
||||
cache->StopTracking(aSurface);
|
||||
|
||||
// Lock the surface. This can fail.
|
||||
aSurface->SetLocked(true);
|
||||
cache->StartTracking(aSurface);
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
static PLDHashOperator DoUnlockSurface(const SurfaceKey&,
|
||||
CachedSurface* aSurface,
|
||||
void* aCache)
|
||||
{
|
||||
if (aSurface->GetLifetime() == Lifetime::Transient ||
|
||||
!aSurface->IsLocked()) {
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
auto cache = static_cast<SurfaceCacheImpl*>(aCache);
|
||||
cache->StopTracking(aSurface);
|
||||
|
||||
aSurface->SetLocked(false);
|
||||
cache->StartTracking(aSurface);
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
CollectReports(nsIHandleReportCallback* aHandleReport,
|
||||
nsISupports* aData,
|
||||
bool aAnonymize)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
rv = MOZ_COLLECT_REPORT("imagelib-surface-cache-total",
|
||||
KIND_OTHER, UNITS_BYTES,
|
||||
SizeOfSurfacesEstimate(),
|
||||
"Total memory used by the imagelib surface cache.");
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = MOZ_COLLECT_REPORT("imagelib-surface-cache-locked",
|
||||
KIND_OTHER, UNITS_BYTES,
|
||||
SizeOfLockedSurfacesEstimate(),
|
||||
"Memory used by locked surfaces in the imagelib "
|
||||
"surface cache.");
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// XXX(seth): This is currently only an estimate and, since we don't know
|
||||
@ -411,6 +551,11 @@ public:
|
||||
return mMaxCost - mAvailableCost;
|
||||
}
|
||||
|
||||
Cost SizeOfLockedSurfacesEstimate() const
|
||||
{
|
||||
return mLockedCost;
|
||||
}
|
||||
|
||||
private:
|
||||
already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
|
||||
{
|
||||
@ -419,6 +564,16 @@ private:
|
||||
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 if we start permitting multithreaded access to the surface
|
||||
// cache, which seems likely, then the result would be meaningless: another
|
||||
// thread could insert a persistent surface or lock an image at any time.
|
||||
bool CanHoldAfterDiscarding(const Cost aCost) const
|
||||
{
|
||||
return aCost + mLockedCost <= mMaxCost;
|
||||
}
|
||||
|
||||
struct SurfaceTracker : public nsExpirationTracker<CachedSurface, 2>
|
||||
{
|
||||
SurfaceTracker(SurfaceCacheImpl* aCache, uint32_t aSurfaceCacheExpirationTimeMS)
|
||||
@ -461,6 +616,7 @@ private:
|
||||
nsRefPtr<MemoryPressureObserver> mMemoryPressureObserver;
|
||||
const Cost mMaxCost;
|
||||
Cost mAvailableCost;
|
||||
Cost mLockedCost;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
|
||||
@ -527,17 +683,19 @@ SurfaceCache::Lookup(const ImageKey aImageKey,
|
||||
return sInstance->Lookup(aImageKey, aSurfaceKey);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
/* static */ bool
|
||||
SurfaceCache::Insert(imgFrame* aSurface,
|
||||
const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
Lifetime aLifetime)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
Cost cost = ComputeCost(aSurfaceKey.Size());
|
||||
sInstance->Insert(aSurface, aSurfaceKey.Size(), cost, aImageKey,
|
||||
aSurfaceKey);
|
||||
if (!sInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cost cost = ComputeCost(aSurfaceKey.Size());
|
||||
return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
@ -553,21 +711,39 @@ SurfaceCache::CanHold(const IntSize& aSize)
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
SurfaceCache::RemoveIfPresent(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
SurfaceCache::LockImage(Image* aImageKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
sInstance->RemoveIfPresent(aImageKey, aSurfaceKey);
|
||||
return sInstance->LockImage(aImageKey);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
SurfaceCache::Discard(Image* aImageKey)
|
||||
SurfaceCache::UnlockImage(Image* aImageKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
sInstance->Discard(aImageKey);
|
||||
return sInstance->UnlockImage(aImageKey);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
SurfaceCache::RemoveSurface(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
sInstance->RemoveSurface(aImageKey, aSurfaceKey);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
SurfaceCache::RemoveImage(Image* aImageKey)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInstance) {
|
||||
sInstance->RemoveImage(aImageKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,16 +109,33 @@ VectorSurfaceKey(const gfx::IntSize& aSize,
|
||||
return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0);
|
||||
}
|
||||
|
||||
MOZ_BEGIN_ENUM_CLASS(Lifetime, uint8_t)
|
||||
Transient,
|
||||
Persistent
|
||||
MOZ_END_ENUM_CLASS(Lifetime)
|
||||
|
||||
/**
|
||||
* SurfaceCache is an imagelib-global service that allows caching of temporary
|
||||
* surfaces. Surfaces expire from the cache automatically if they go too long
|
||||
* without being accessed.
|
||||
* surfaces. Surfaces normally expire from the cache automatically if they go
|
||||
* too long without being accessed.
|
||||
*
|
||||
* SurfaceCache does not hold surfaces directly; instead, it holds imgFrame
|
||||
* objects, which hold surfaces but also layer on additional features specific
|
||||
* to imagelib's needs like animation, padding support, and transparent support
|
||||
* for volatile buffers.
|
||||
*
|
||||
* Sometime it's useful to temporarily prevent surfaces from expiring from the
|
||||
* cache. This is most often because losing the data could harm the user
|
||||
* experience (for example, we often don't want to allow surfaces that are
|
||||
* currently visible to expire) or because it's not possible to rematerialize
|
||||
* the surface. SurfaceCache supports this through the use of image locking and
|
||||
* surface lifetimes; see the comments for Insert() and LockImage() for more
|
||||
* details.
|
||||
*
|
||||
* Any image which stores surfaces in the SurfaceCache *must* ensure that it
|
||||
* calls RemoveImage() before it is destroyed. See the comments for
|
||||
* RemoveImage() for more details.
|
||||
*
|
||||
* SurfaceCache is not thread-safe; it should only be accessed from the main
|
||||
* thread.
|
||||
*/
|
||||
@ -126,23 +143,25 @@ struct SurfaceCache
|
||||
{
|
||||
typedef gfx::IntSize IntSize;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Initialize static data. Called during imagelib module initialization.
|
||||
*/
|
||||
static void Initialize();
|
||||
|
||||
/*
|
||||
/**
|
||||
* Release static data. Called during imagelib module shutdown.
|
||||
*/
|
||||
static void Shutdown();
|
||||
|
||||
/*
|
||||
/**
|
||||
* Look up the imgFrame containing a surface in the cache and returns a
|
||||
* drawable reference to that imgFrame.
|
||||
*
|
||||
* If the imgFrame was found in the cache, but had stored its surface in a
|
||||
* volatile buffer which was discarded by the OS, then it is automatically
|
||||
* removed from the cache and an empty DrawableFrameRef is returned.
|
||||
* removed from the cache and an empty DrawableFrameRef is returned. Note that
|
||||
* this will never happen to persistent surfaces associated with a locked
|
||||
* image; the cache keeps a strong reference to such surfaces internally.
|
||||
*
|
||||
* @param aImageKey Key data identifying which image the surface belongs to.
|
||||
* @param aSurfaceKey Key data which uniquely identifies the requested surface.
|
||||
@ -153,21 +172,46 @@ struct SurfaceCache
|
||||
static DrawableFrameRef Lookup(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Insert a surface into the cache. It is an error to call this function
|
||||
* without first calling Lookup to verify that the surface is not already in
|
||||
* the cache.
|
||||
*
|
||||
* Each surface in the cache has a lifetime, either Transient or Persistent.
|
||||
* Transient surfaces can expire from the cache at any time. Persistent
|
||||
* surfaces can ordinarily also expire from the cache at any time, but if the
|
||||
* image they're associated with is locked, then these surfaces will never
|
||||
* expire. This means that surfaces which cannot be rematerialized should be
|
||||
* inserted with a persistent lifetime *after* the image is locked with
|
||||
* LockImage(); if you use the other order, the surfaces might expire before
|
||||
* LockImage() gets called.
|
||||
*
|
||||
* If a surface cannot be rematerialized, it may be important to know whether
|
||||
* it was inserted into the cache successfully. Insert() returns false if it
|
||||
* failed to insert the surface, which could happen because of capacity
|
||||
* reasons, or because it was already freed by the OS. If you aren't inserting
|
||||
* a surface with persistent lifetime, or if the surface isn't associated with
|
||||
* a locked image, the return value is useless: the surface might expire
|
||||
* immediately after being inserted, even though Insert() returned true. Thus,
|
||||
* most callers do not need to check the return value.
|
||||
*
|
||||
* @param aTarget The new surface (wrapped in an imgFrame) to insert into
|
||||
* the cache.
|
||||
* @param aImageKey Key data identifying which image the surface belongs to.
|
||||
* @param aSurfaceKey Key data which uniquely identifies the requested surface.
|
||||
* @param aLifetime Whether this is a transient surface that can always be
|
||||
* allowed to expire, or a persistent surface that
|
||||
* shouldn't expire if the image is locked.
|
||||
* @return false if the surface could not be inserted. Only check this if
|
||||
* inserting a persistent surface associated with a locked image (see
|
||||
* above for more information).
|
||||
*/
|
||||
static void Insert(imgFrame* aSurface,
|
||||
static bool Insert(imgFrame* aSurface,
|
||||
const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey);
|
||||
const SurfaceKey& aSurfaceKey,
|
||||
Lifetime aLifetime);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Checks if a surface of a given size could possibly be stored in the cache.
|
||||
* If CanHold() returns false, Insert() will always fail to insert the
|
||||
* surface, but the inverse is not true: Insert() may take more information
|
||||
@ -183,30 +227,69 @@ struct SurfaceCache
|
||||
*/
|
||||
static bool CanHold(const IntSize& aSize);
|
||||
|
||||
/*
|
||||
* Removes a surface from the cache, if it's present.
|
||||
/**
|
||||
* Locks an image, preventing any of that image's surfaces from expiring
|
||||
* unless they have a transient lifetime.
|
||||
*
|
||||
* Regardless of locking, any of an image's surfaces may be removed using
|
||||
* RemoveSurface(), and all of an image's surfaces are removed by
|
||||
* RemoveImage(), whether the image is locked or not.
|
||||
*
|
||||
* It's safe to call LockImage() on an image that's already locked; this has
|
||||
* no effect.
|
||||
*
|
||||
* You must always unlock any image you lock. You may do this explicitly by
|
||||
* calling UnlockImage(), or implicitly by calling RemoveImage(). Since you're
|
||||
* required to call RemoveImage() when you destroy an image, this doesn't
|
||||
* impose any additional requirements, but it's preferable to call
|
||||
* UnlockImage() earlier if it's possible.
|
||||
*
|
||||
* @param aImageKey The image to lock.
|
||||
*/
|
||||
static void LockImage(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Unlocks an image, allowing any of its surfaces to expire at any time.
|
||||
*
|
||||
* It's OK to call UnlockImage() on an image that's already unlocked; this has
|
||||
* no effect.
|
||||
*
|
||||
* @param aImageKey The image to lock.
|
||||
*/
|
||||
static void UnlockImage(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Removes a surface from the cache, if it's present. If it's not present,
|
||||
* RemoveSurface() has no effect.
|
||||
*
|
||||
* Use this function to remove individual surfaces that have become invalid.
|
||||
* Prefer Discard() or DiscardAll() when they're applicable, as they have much
|
||||
* better performance than calling this function repeatedly.
|
||||
* Prefer RemoveImage() or DiscardAll() when they're applicable, as they have
|
||||
* much better performance than calling this function repeatedly.
|
||||
*
|
||||
* @param aImageKey Key data identifying which image the surface belongs to.
|
||||
* @param aSurfaceKey Key data which uniquely identifies the requested surface.
|
||||
*/
|
||||
static void RemoveIfPresent(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey);
|
||||
/*
|
||||
* Evicts any cached surfaces associated with the given image from the cache.
|
||||
* This MUST be called, at a minimum, when the image is destroyed. If
|
||||
* another image were allocated at the same address it could result in
|
||||
* subtle, difficult-to-reproduce bugs.
|
||||
static void RemoveSurface(const ImageKey aImageKey,
|
||||
const SurfaceKey& aSurfaceKey);
|
||||
|
||||
/**
|
||||
* Removes all cached surfaces associated with the given image from the cache.
|
||||
* If the image is locked, it is automatically unlocked.
|
||||
*
|
||||
* This MUST be called, at a minimum, when an Image which could be storing
|
||||
* surfaces in the surface cache is destroyed. If another image were allocated
|
||||
* at the same address it could result in subtle, difficult-to-reproduce bugs.
|
||||
*
|
||||
* @param aImageKey The image which should be removed from the cache.
|
||||
*/
|
||||
static void Discard(const ImageKey aImageKey);
|
||||
static void RemoveImage(const ImageKey aImageKey);
|
||||
|
||||
/*
|
||||
* Evicts all caches surfaces from ths cache.
|
||||
/**
|
||||
* Evicts all evictable surfaces from the cache.
|
||||
*
|
||||
* All surfaces are evictable except for persistent surfaces associated with
|
||||
* locked images. Non-evictable surfaces can only be removed by
|
||||
* RemoveSurface() or RemoveImage().
|
||||
*/
|
||||
static void DiscardAll();
|
||||
|
||||
|
@ -338,7 +338,7 @@ VectorImage::VectorImage(imgStatusTracker* aStatusTracker,
|
||||
VectorImage::~VectorImage()
|
||||
{
|
||||
CancelAllListeners();
|
||||
SurfaceCache::Discard(this);
|
||||
SurfaceCache::RemoveImage(ImageKey(this));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@ -570,7 +570,7 @@ VectorImage::SendInvalidationNotifications()
|
||||
// notifications directly in |InvalidateObservers...|.
|
||||
|
||||
if (mStatusTracker) {
|
||||
SurfaceCache::Discard(this);
|
||||
SurfaceCache::RemoveImage(ImageKey(this));
|
||||
mStatusTracker->FrameChanged(&nsIntRect::GetMaxSizedIntRect());
|
||||
mStatusTracker->OnStopFrame();
|
||||
}
|
||||
@ -914,7 +914,8 @@ VectorImage::CreateSurfaceAndShow(const SVGDrawingParameters& aParams)
|
||||
SurfaceCache::Insert(frame, ImageKey(this),
|
||||
VectorSurfaceKey(aParams.size,
|
||||
aParams.svgContext,
|
||||
aParams.animationTime));
|
||||
aParams.animationTime),
|
||||
Lifetime::Transient);
|
||||
|
||||
// Draw.
|
||||
nsRefPtr<gfxDrawable> drawable =
|
||||
@ -982,7 +983,7 @@ VectorImage::UnlockImage()
|
||||
NS_IMETHODIMP
|
||||
VectorImage::RequestDiscard()
|
||||
{
|
||||
SurfaceCache::Discard(this);
|
||||
SurfaceCache::RemoveImage(ImageKey(this));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user