Skip to content

Commit

Permalink
Bug 1456558 - Part 2. Implement factor of 2 scaling support for SVGs …
Browse files Browse the repository at this point in the history
…in the surface cache. r=tnikkel

There is one main difference between raster images and vector images
with respect to factor of 2 scaling. Vector images may be scaled
infinitely and so we need to extend factor of 2 scaling to permit
growing instead of just shrinking. Also, we don't want to scale
infinitely, so we should configure a maximum size limit. This size limit
will apply even outside of factor of 2 scaling, and so the caller
(VectorImage) will need to be careful to take this into account.
  • Loading branch information
aosmond committed Sep 20, 2018
1 parent 0362a85 commit f799e4b
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 17 deletions.
1 change: 1 addition & 0 deletions gfx/thebes/gfxPrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ class gfxPrefs final
DECL_GFX_PREF(Live, "image.animated.generate-full-frames", ImageAnimatedGenerateFullFrames, bool, false);
DECL_GFX_PREF(Live, "image.animated.resume-from-last-displayed", ImageAnimatedResumeFromLastDisplayed, bool, false);
DECL_GFX_PREF(Live, "image.cache.factor2.threshold-surfaces", ImageCacheFactor2ThresholdSurfaces, int32_t, -1);
DECL_GFX_PREF(Live, "image.cache.max-rasterized-svg-threshold-kb", ImageCacheMaxRasterizedSVGThresholdKB, int32_t, 90*1024);
DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
DECL_GFX_PREF(Once, "image.cache.timeweight", ImageCacheTimeWeight, int32_t, 500);
DECL_GFX_PREF(Live, "image.decode-immediately.enabled", ImageDecodeImmediatelyEnabled, bool, false);
Expand Down
103 changes: 86 additions & 17 deletions image/SurfaceCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <algorithm>
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Likely.h"
#include "mozilla/Move.h"
Expand Down Expand Up @@ -251,10 +252,11 @@ class ImageSurfaceCache
{
~ImageSurfaceCache() { }
public:
ImageSurfaceCache()
explicit ImageSurfaceCache(const ImageKey aImageKey)
: mLocked(false)
, mFactor2Mode(false)
, mFactor2Pruned(false)
, mIsVectorImage(aImageKey->GetType() == imgIContainer::TYPE_VECTOR)
{ }

MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageSurfaceCache)
Expand Down Expand Up @@ -330,7 +332,7 @@ class ImageSurfaceCache

// Try for a best match second, if using compact.
IntSize suggestedSize = SuggestedSize(aIdealKey.Size());
if (mFactor2Mode) {
if (suggestedSize != aIdealKey.Size()) {
if (!exactMatch) {
SurfaceKey compactKey = aIdealKey.CloneWithSize(suggestedSize);
mSurfaces.Get(compactKey, getter_AddRefs(exactMatch));
Expand Down Expand Up @@ -401,7 +403,7 @@ class ImageSurfaceCache
} else if (aIdealKey.Size() != bestMatch->GetSurfaceKey().Size()) {
// The best factor of 2 match is still decoding, but the best we've got.
MOZ_ASSERT(suggestedSize != aIdealKey.Size());
MOZ_ASSERT(mFactor2Mode);
MOZ_ASSERT(mFactor2Mode || mIsVectorImage);
matchType = MatchType::SUBSTITUTE_BECAUSE_BEST;
} else {
// The exact match is still decoding, but it's the best we've got.
Expand Down Expand Up @@ -433,20 +435,18 @@ class ImageSurfaceCache
return;
}

// Determine how many native surfaces this image has. Zero means we either
// don't know yet (in which case do nothing), or we don't want to limit the
// number of surfaces for this image.
//
// XXX(aosmond): Vector images have zero native sizes. This is because they
// are regenerated at the given size. There isn't an equivalent concept to
// the native size (and w/h ratio) to provide a frame of reference to what
// are "good" sizes. While it is desirable to have a similar mechanism as
// that for raster images, it will need a different approach.
// Determine how many native surfaces this image has. If it is zero, and it
// is a vector image, then we should impute a single native size. Otherwise,
// it may be zero because we don't know yet, or the image has an error, or
// it isn't supported.
auto first = ConstIter();
NotNull<CachedSurface*> current = WrapNotNull(first.UserData());
Image* image = static_cast<Image*>(current->GetImageKey());
size_t nativeSizes = image->GetNativeSizesLength();
if (nativeSizes == 0) {
if (mIsVectorImage) {
MOZ_ASSERT(nativeSizes == 0);
nativeSizes = 1;
} else if (nativeSizes == 0) {
return;
}

Expand Down Expand Up @@ -530,6 +530,33 @@ class ImageSurfaceCache
}

IntSize SuggestedSize(const IntSize& aSize) const
{
IntSize suggestedSize = SuggestedSizeInternal(aSize);
MOZ_ASSERT(SurfaceCache::IsLegalSize(suggestedSize));

// Whether or not we are in factor of 2 mode, vector image rasterization is
// clamped at a configured maximum if the caller is willing to accept
// substitutes.
if (mIsVectorImage) {
// If we exceed the maximum, we need to scale the size downwards to fit.
// It shouldn't get here if it is significantly larger because
// VectorImage::UseSurfaceCacheForSize should prevent us from requesting
// a rasterized version of a surface greater than 4x the maximum.
int32_t maxSizeKB = gfxPrefs::ImageCacheMaxRasterizedSVGThresholdKB();
int32_t proposedKB = suggestedSize.width * suggestedSize.height / 256;
if (maxSizeKB >= proposedKB) {
return suggestedSize;
}

double scale = sqrt(double(maxSizeKB) / proposedKB);
suggestedSize.width = int32_t(scale * suggestedSize.width);
suggestedSize.height = int32_t(scale * suggestedSize.height);
}

return suggestedSize;
}

IntSize SuggestedSizeInternal(const IntSize& aSize) const
{
// When not in factor of 2 mode, we can always decode at the given size.
if (!mFactor2Mode) {
Expand All @@ -552,11 +579,48 @@ class ImageSurfaceCache
NS_FAILED(image->GetHeight(&factorSize.height)) ||
factorSize.IsEmpty()) {
// We should not have entered factor of 2 mode without a valid size, and
// several successfully decoded surfaces.
// several successfully decoded surfaces. Note that valid vector images
// may have a default size of 0x0, and those are not yet supported.
MOZ_ASSERT_UNREACHABLE("Expected valid native size!");
return aSize;
}

if (mIsVectorImage) {
// Ensure the aspect ratio matches the native size before forcing the
// caller to accept a factor of 2 size. The difference between the aspect
// ratios is:
//
// delta = nativeWidth/nativeHeight - desiredWidth/desiredHeight
//
// delta*nativeHeight*desiredHeight = nativeWidth*desiredHeight
// - desiredWidth*nativeHeight
//
// Using the maximum accepted delta as a constant, we can avoid the
// floating point division and just compare after some integer ops.
int32_t delta = factorSize.width * aSize.height - aSize.width * factorSize.height;
int32_t maxDelta = (factorSize.height * aSize.height) >> 4;
if (delta > maxDelta || delta < -maxDelta) {
return aSize;
}

// If the requested size is bigger than the native size, we actually need
// to grow the native size instead of shrinking it.
if (factorSize.width < aSize.width) {
do {
IntSize candidate(factorSize.width * 2, factorSize.height * 2);
if (!SurfaceCache::IsLegalSize(candidate)) {
break;
}

factorSize = candidate;
} while (factorSize.width < aSize.width);

return factorSize;
}

// Otherwise we can find the best fit as normal.
}

// Start with the native size as the best first guess.
IntSize bestSize = factorSize;
factorSize.width /= 2;
Expand Down Expand Up @@ -674,6 +738,10 @@ class ImageSurfaceCache
// True if all non-factor of 2 surfaces have been removed from the cache. Note
// that this excludes unsubstitutable sizes.
bool mFactor2Pruned;

// True if the surfaces are produced from a vector image. If so, it must match
// the aspect ratio when using factor of 2 mode.
bool mIsVectorImage;
};

/**
Expand Down Expand Up @@ -760,9 +828,10 @@ class SurfaceCacheImpl final : public nsIMemoryReporter

// Locate the appropriate per-image cache. If there's not an existing cache
// for this image, create it.
RefPtr<ImageSurfaceCache> cache = GetImageCache(aProvider->GetImageKey());
const ImageKey imageKey = aProvider->GetImageKey();
RefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
if (!cache) {
cache = new ImageSurfaceCache;
cache = new ImageSurfaceCache(imageKey);
mImageCaches.Put(aProvider->GetImageKey(), cache);
}

Expand Down Expand Up @@ -1014,7 +1083,7 @@ class SurfaceCacheImpl final : public nsIMemoryReporter
{
RefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
if (!cache) {
cache = new ImageSurfaceCache;
cache = new ImageSurfaceCache(aImageKey);
mImageCaches.Put(aImageKey, cache);
}

Expand Down
4 changes: 4 additions & 0 deletions modules/libpref/init/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -4620,6 +4620,10 @@ pref("image.animated.resume-from-last-displayed", true);
// same data at different sizes.
pref("image.cache.factor2.threshold-surfaces", 4);

// Maximum number of pixels in either dimension that we are willing to upscale
// an SVG to when we are in "factor of 2" mode.
pref("image.cache.max-rasterized-svg-threshold-kb", 92160);

// The maximum size, in bytes, of the decoded images we cache
pref("image.cache.size", 5242880);

Expand Down

0 comments on commit f799e4b

Please sign in to comment.