mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-12 10:40:12 +00:00
Bug 1711061 - Part 9. Add blob recording support to SurfaceCache. r=tnikkel
Now that we no longer have the extra layer of ImageContainers providing a superficial level of caching/reuse of existing blob recordings, we need some way to share recordings. This part adds support to SurfaceCache to store BlobSurfaceProvider objects. This includes the specialized code for invalidating SVG images. In particular this is useful for animated SVG images. In general we want to avoid changing the image key whenever possible so that we avoid reallocating the underlying buffers in the compositor process for the rasterized blob images. We also need to track the ImageIntRegion used by the recording. If a caller only wants a slice of the SVG image, then we need to track this differentiation in our cache entries. At this time, we don't allow substitutes for entries with a region exclusion. Differential Revision: https://phabricator.services.mozilla.com/D126603
This commit is contained in:
parent
3a3c39b1b5
commit
ba2c6dea3f
@ -13,6 +13,7 @@
|
||||
#include "mozilla/gfx/Matrix.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
#include "nsSize.h"
|
||||
#include "PLDHashTable.h" // for PLDHashNumber
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
@ -239,6 +240,12 @@ class ImageIntRegion {
|
||||
(!mIsRestricted || mRestriction.IsEqualEdges(aOther.mRestriction));
|
||||
}
|
||||
|
||||
PLDHashNumber Hash() const {
|
||||
return HashGeneric(mRect.x, mRect.y, mRect.width, mRect.height,
|
||||
mRestriction.x, mRestriction.y, mRestriction.width,
|
||||
mRestriction.height, mExtendMode, mIsRestricted);
|
||||
}
|
||||
|
||||
/* ImageIntRegion() : mIsRestricted(false) { } */
|
||||
|
||||
private:
|
||||
|
@ -176,6 +176,8 @@ class CachedSurface {
|
||||
return aMallocSizeOf(this) + aMallocSizeOf(mProvider.get());
|
||||
}
|
||||
|
||||
void InvalidateRecording() { mProvider->InvalidateRecording(); }
|
||||
|
||||
// A helper type used by SurfaceCacheImpl::CollectSizeOfSurfaces.
|
||||
struct MOZ_STACK_CLASS SurfaceMemoryReport {
|
||||
SurfaceMemoryReport(nsTArray<SurfaceMemoryCounter>& aCounters,
|
||||
@ -276,8 +278,14 @@ class ImageSurfaceCache {
|
||||
[[nodiscard]] bool Insert(NotNull<CachedSurface*> aSurface) {
|
||||
MOZ_ASSERT(!mLocked || aSurface->IsPlaceholder() || aSurface->IsLocked(),
|
||||
"Inserting an unlocked surface for a locked image");
|
||||
return mSurfaces.InsertOrUpdate(aSurface->GetSurfaceKey(),
|
||||
RefPtr<CachedSurface>{aSurface}, fallible);
|
||||
const auto& surfaceKey = aSurface->GetSurfaceKey();
|
||||
if (surfaceKey.Region()) {
|
||||
// We don't allow substitutes for surfaces with regions, so we don't want
|
||||
// to allow factor of 2 mode pruning to release these surfaces.
|
||||
aSurface->SetCannotSubstitute();
|
||||
}
|
||||
return mSurfaces.InsertOrUpdate(surfaceKey, RefPtr<CachedSurface>{aSurface},
|
||||
fallible);
|
||||
}
|
||||
|
||||
already_AddRefed<CachedSurface> Remove(NotNull<CachedSurface*> aSurface) {
|
||||
@ -326,6 +334,10 @@ class ImageSurfaceCache {
|
||||
if (exactMatch->IsDecoded()) {
|
||||
return MakeTuple(exactMatch.forget(), MatchType::EXACT, IntSize());
|
||||
}
|
||||
} else if (aIdealKey.Region()) {
|
||||
// We cannot substitute if we have a region. Allow it to create an exact
|
||||
// match.
|
||||
return MakeTuple(exactMatch.forget(), MatchType::NOT_FOUND, IntSize());
|
||||
} else if (!mFactor2Mode) {
|
||||
// If no exact match is found, and we are not in factor of 2 mode, then
|
||||
// we know that we will trigger a decode because at best we will provide
|
||||
@ -353,8 +365,8 @@ class ImageSurfaceCache {
|
||||
NotNull<CachedSurface*> current = WrapNotNull(value);
|
||||
const SurfaceKey& currentKey = current->GetSurfaceKey();
|
||||
|
||||
// We never match a placeholder.
|
||||
if (current->IsPlaceholder()) {
|
||||
// We never match a placeholder or a surface with a region.
|
||||
if (current->IsPlaceholder() || currentKey.Region()) {
|
||||
continue;
|
||||
}
|
||||
// Matching the playback type and SVG context is required.
|
||||
@ -530,6 +542,28 @@ class ImageSurfaceCache {
|
||||
AfterMaybeRemove();
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
bool Invalidate(Function&& aRemoveCallback) {
|
||||
// Remove all non-blob recordings from the cache. Invalidate any blob
|
||||
// recordings.
|
||||
bool foundRecording = false;
|
||||
for (auto iter = mSurfaces.Iter(); !iter.Done(); iter.Next()) {
|
||||
NotNull<CachedSurface*> current = WrapNotNull(iter.UserData());
|
||||
|
||||
if (current->GetSurfaceKey().Flags() & SurfaceFlags::RECORD_BLOB) {
|
||||
foundRecording = true;
|
||||
current->InvalidateRecording();
|
||||
continue;
|
||||
}
|
||||
|
||||
aRemoveCallback(current);
|
||||
iter.Remove();
|
||||
}
|
||||
|
||||
AfterMaybeRemove();
|
||||
return foundRecording;
|
||||
}
|
||||
|
||||
IntSize SuggestedSize(const IntSize& aSize) const {
|
||||
IntSize suggestedSize = SuggestedSizeInternal(aSize);
|
||||
if (mIsVectorImage) {
|
||||
@ -1016,7 +1050,8 @@ class SurfaceCacheImpl final : public nsIMemoryReporter {
|
||||
MOZ_ASSERT_IF(
|
||||
matchType == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND ||
|
||||
matchType == MatchType::SUBSTITUTE_BECAUSE_PENDING,
|
||||
surface->GetSurfaceKey().SVGContext() == aSurfaceKey.SVGContext() &&
|
||||
surface->GetSurfaceKey().Region() == aSurfaceKey.Region() &&
|
||||
surface->GetSurfaceKey().SVGContext() == aSurfaceKey.SVGContext() &&
|
||||
surface->GetSurfaceKey().Playback() == aSurfaceKey.Playback() &&
|
||||
surface->GetSurfaceKey().Flags() == aSurfaceKey.Flags());
|
||||
|
||||
@ -1132,6 +1167,24 @@ class SurfaceCacheImpl final : public nsIMemoryReporter {
|
||||
MaybeRemoveEmptyCache(aImageKey, cache);
|
||||
}
|
||||
|
||||
bool InvalidateImage(const ImageKey aImageKey,
|
||||
const StaticMutexAutoLock& aAutoLock) {
|
||||
RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
|
||||
if (!cache) {
|
||||
return false; // No cached surfaces for this image, so nothing to do.
|
||||
}
|
||||
|
||||
bool rv = cache->Invalidate(
|
||||
[this, &aAutoLock](NotNull<CachedSurface*> aSurface) -> void {
|
||||
StopTracking(aSurface, /* aIsTracked */ true, aAutoLock);
|
||||
// Individual surfaces must be freed outside the lock.
|
||||
mCachedSurfacesDiscard.AppendElement(aSurface);
|
||||
});
|
||||
|
||||
MaybeRemoveEmptyCache(aImageKey, cache);
|
||||
return rv;
|
||||
}
|
||||
|
||||
void DiscardAll(const StaticMutexAutoLock& aAutoLock) {
|
||||
// Remove in order of cost because mCosts is an array and the other data
|
||||
// structures are all hash tables. Note that locked surfaces are not
|
||||
@ -1717,6 +1770,20 @@ void SurfaceCache::PruneImage(const ImageKey aImageKey) {
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool SurfaceCache::InvalidateImage(const ImageKey aImageKey) {
|
||||
nsTArray<RefPtr<CachedSurface>> discard;
|
||||
bool rv = false;
|
||||
{
|
||||
StaticMutexAutoLock lock(sInstanceMutex);
|
||||
if (sInstance) {
|
||||
rv = sInstance->InvalidateImage(aImageKey, lock);
|
||||
sInstance->TakeDiscard(discard, lock);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void SurfaceCache::DiscardAll() {
|
||||
nsTArray<RefPtr<CachedSurface>> discard;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxPoint.h" // for gfxSize
|
||||
#include "nsCOMPtr.h" // for already_AddRefed
|
||||
#include "ImageRegion.h"
|
||||
#include "PlaybackType.h"
|
||||
#include "SurfaceFlags.h"
|
||||
|
||||
@ -52,34 +53,43 @@ class SurfaceKey {
|
||||
|
||||
public:
|
||||
bool operator==(const SurfaceKey& aOther) const {
|
||||
return aOther.mSize == mSize && aOther.mSVGContext == mSVGContext &&
|
||||
aOther.mPlayback == mPlayback && aOther.mFlags == mFlags;
|
||||
return aOther.mSize == mSize && aOther.mRegion == mRegion &&
|
||||
aOther.mSVGContext == mSVGContext && aOther.mPlayback == mPlayback &&
|
||||
aOther.mFlags == mFlags;
|
||||
}
|
||||
|
||||
PLDHashNumber Hash() const {
|
||||
PLDHashNumber hash = HashGeneric(mSize.width, mSize.height);
|
||||
hash = AddToHash(hash, mRegion.map(HashIIR).valueOr(0));
|
||||
hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0));
|
||||
hash = AddToHash(hash, uint8_t(mPlayback), uint32_t(mFlags));
|
||||
return hash;
|
||||
}
|
||||
|
||||
SurfaceKey CloneWithSize(const IntSize& aSize) const {
|
||||
return SurfaceKey(aSize, mSVGContext, mPlayback, mFlags);
|
||||
return SurfaceKey(aSize, mRegion, mSVGContext, mPlayback, mFlags);
|
||||
}
|
||||
|
||||
const IntSize& Size() const { return mSize; }
|
||||
const Maybe<ImageIntRegion>& Region() const { return mRegion; }
|
||||
const Maybe<SVGImageContext>& SVGContext() const { return mSVGContext; }
|
||||
PlaybackType Playback() const { return mPlayback; }
|
||||
SurfaceFlags Flags() const { return mFlags; }
|
||||
|
||||
private:
|
||||
SurfaceKey(const IntSize& aSize, const Maybe<SVGImageContext>& aSVGContext,
|
||||
PlaybackType aPlayback, SurfaceFlags aFlags)
|
||||
SurfaceKey(const IntSize& aSize, const Maybe<ImageIntRegion>& aRegion,
|
||||
const Maybe<SVGImageContext>& aSVGContext, PlaybackType aPlayback,
|
||||
SurfaceFlags aFlags)
|
||||
: mSize(aSize),
|
||||
mRegion(aRegion),
|
||||
mSVGContext(aSVGContext),
|
||||
mPlayback(aPlayback),
|
||||
mFlags(aFlags) {}
|
||||
|
||||
static PLDHashNumber HashIIR(const ImageIntRegion& aIIR) {
|
||||
return aIIR.Hash();
|
||||
}
|
||||
|
||||
static PLDHashNumber HashSIC(const SVGImageContext& aSIC) {
|
||||
return aSIC.Hash();
|
||||
}
|
||||
@ -88,11 +98,16 @@ class SurfaceKey {
|
||||
PlaybackType);
|
||||
friend SurfaceKey VectorSurfaceKey(const IntSize&,
|
||||
const Maybe<SVGImageContext>&);
|
||||
friend SurfaceKey VectorSurfaceKey(const IntSize&,
|
||||
const Maybe<ImageIntRegion>&,
|
||||
const Maybe<SVGImageContext>&,
|
||||
SurfaceFlags, PlaybackType);
|
||||
friend SurfaceKey ContainerSurfaceKey(
|
||||
const gfx::IntSize& aSize, const Maybe<SVGImageContext>& aSVGContext,
|
||||
SurfaceFlags aFlags);
|
||||
|
||||
IntSize mSize;
|
||||
Maybe<ImageIntRegion> mRegion;
|
||||
Maybe<SVGImageContext> mSVGContext;
|
||||
PlaybackType mPlayback;
|
||||
SurfaceFlags mFlags;
|
||||
@ -101,7 +116,15 @@ class SurfaceKey {
|
||||
inline SurfaceKey RasterSurfaceKey(const gfx::IntSize& aSize,
|
||||
SurfaceFlags aFlags,
|
||||
PlaybackType aPlayback) {
|
||||
return SurfaceKey(aSize, Nothing(), aPlayback, aFlags);
|
||||
return SurfaceKey(aSize, Nothing(), Nothing(), aPlayback, aFlags);
|
||||
}
|
||||
|
||||
inline SurfaceKey VectorSurfaceKey(const gfx::IntSize& aSize,
|
||||
const Maybe<ImageIntRegion>& aRegion,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
SurfaceFlags aFlags,
|
||||
PlaybackType aPlayback) {
|
||||
return SurfaceKey(aSize, aRegion, aSVGContext, aPlayback, aFlags);
|
||||
}
|
||||
|
||||
inline SurfaceKey VectorSurfaceKey(const gfx::IntSize& aSize,
|
||||
@ -111,14 +134,15 @@ inline SurfaceKey VectorSurfaceKey(const gfx::IntSize& aSize,
|
||||
// *does* affect how a VectorImage renders, we'll have to change this.
|
||||
// Similarly, we don't accept a PlaybackType parameter because we don't
|
||||
// currently cache frames of animated SVG images.
|
||||
return SurfaceKey(aSize, aSVGContext, PlaybackType::eStatic,
|
||||
return SurfaceKey(aSize, Nothing(), aSVGContext, PlaybackType::eStatic,
|
||||
DefaultSurfaceFlags());
|
||||
}
|
||||
|
||||
inline SurfaceKey ContainerSurfaceKey(const gfx::IntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
SurfaceFlags aFlags) {
|
||||
return SurfaceKey(aSize, aSVGContext, PlaybackType::eStatic, aFlags);
|
||||
return SurfaceKey(aSize, Nothing(), aSVGContext, PlaybackType::eStatic,
|
||||
aFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -409,6 +433,17 @@ struct SurfaceCache {
|
||||
*/
|
||||
static void PruneImage(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Removes all rasterized cache entries (including placeholders) associated
|
||||
* with the given image from the cache. Any blob recordings are marked as
|
||||
* dirty and must be regenerated.
|
||||
*
|
||||
* @param aImageKey The image whose cache which should be regenerated.
|
||||
*
|
||||
* @returns true if any recordings were invalidated, else false.
|
||||
*/
|
||||
static bool InvalidateImage(const ImageKey aImageKey);
|
||||
|
||||
/**
|
||||
* Evicts all evictable entries from the cache.
|
||||
*
|
||||
|
@ -20,7 +20,8 @@ namespace image {
|
||||
enum class SurfaceFlags : uint8_t {
|
||||
NO_PREMULTIPLY_ALPHA = 1 << 0,
|
||||
NO_COLORSPACE_CONVERSION = 1 << 1,
|
||||
TO_SRGB_COLORSPACE = 2 << 1,
|
||||
TO_SRGB_COLORSPACE = 1 << 2,
|
||||
RECORD_BLOB = 1 << 3,
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfaceFlags)
|
||||
|
||||
@ -44,6 +45,9 @@ inline SurfaceFlags ToSurfaceFlags(uint32_t aFlags) {
|
||||
if (aFlags & imgIContainer::FLAG_DECODE_TO_SRGB_COLORSPACE) {
|
||||
flags |= SurfaceFlags::TO_SRGB_COLORSPACE;
|
||||
}
|
||||
if (aFlags & imgIContainer::FLAG_RECORD_BLOB) {
|
||||
flags |= SurfaceFlags::RECORD_BLOB;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
@ -62,6 +66,9 @@ inline uint32_t FromSurfaceFlags(SurfaceFlags aFlags) {
|
||||
if (aFlags & SurfaceFlags::TO_SRGB_COLORSPACE) {
|
||||
flags |= imgIContainer::FLAG_DECODE_TO_SRGB_COLORSPACE;
|
||||
}
|
||||
if (aFlags & SurfaceFlags::RECORD_BLOB) {
|
||||
flags |= imgIContainer::FLAG_RECORD_BLOB;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
@ -399,6 +399,39 @@ class imgMemoryReporter final : public nsIMemoryReporter {
|
||||
/* aRadix = */ 16);
|
||||
}
|
||||
|
||||
if (counter.Key().Region()) {
|
||||
const ImageIntRegion& region = counter.Key().Region().ref();
|
||||
const gfx::IntRect& rect = region.Rect();
|
||||
surfacePathPrefix.AppendLiteral(", region:[ rect=(");
|
||||
surfacePathPrefix.AppendInt(rect.x);
|
||||
surfacePathPrefix.AppendLiteral(",");
|
||||
surfacePathPrefix.AppendInt(rect.y);
|
||||
surfacePathPrefix.AppendLiteral(") ");
|
||||
surfacePathPrefix.AppendInt(rect.width);
|
||||
surfacePathPrefix.AppendLiteral("x");
|
||||
surfacePathPrefix.AppendInt(rect.height);
|
||||
if (region.IsRestricted()) {
|
||||
const gfx::IntRect& restrict = region.Restriction();
|
||||
if (restrict == rect) {
|
||||
surfacePathPrefix.AppendLiteral(", restrict=rect");
|
||||
} else {
|
||||
surfacePathPrefix.AppendLiteral(", restrict=(");
|
||||
surfacePathPrefix.AppendInt(restrict.x);
|
||||
surfacePathPrefix.AppendLiteral(",");
|
||||
surfacePathPrefix.AppendInt(restrict.y);
|
||||
surfacePathPrefix.AppendLiteral(") ");
|
||||
surfacePathPrefix.AppendInt(restrict.width);
|
||||
surfacePathPrefix.AppendLiteral("x");
|
||||
surfacePathPrefix.AppendInt(restrict.height);
|
||||
}
|
||||
}
|
||||
if (region.GetExtendMode() != gfx::ExtendMode::CLAMP) {
|
||||
surfacePathPrefix.AppendLiteral(", extendMode=");
|
||||
surfacePathPrefix.AppendInt(int32_t(region.GetExtendMode()));
|
||||
}
|
||||
surfacePathPrefix.AppendLiteral("]");
|
||||
}
|
||||
|
||||
if (counter.Key().SVGContext()) {
|
||||
const SVGImageContext& context = counter.Key().SVGContext().ref();
|
||||
surfacePathPrefix.AppendLiteral(", svgContext:[ ");
|
||||
|
Loading…
x
Reference in New Issue
Block a user