Bug 1456558 - Part 3. Implement factor of 2 scaling support for SVGs in VectorImage. r=tnikkel

If FLAG_HIGH_QUALITY_SCALING is used, we should use
SurfaceCache::LookupBestMatch just like how it is done in RasterImage.
This may provide an alternative size at which we should rasterize the
SVG instead of the requested size. Since SurfaceCache imposes a maximum
size for which it will permit rasterized SVGs, we should also bypass the
cache entirely if we are well above that and simply draw directly to the
draw target in such cases.

With WebRender, it is somewhat more complicated. We will now return
NOT_SUPPORTED if the size is too big, and this should trigger fallback
to blob images. This should only produce drawing commands for the
relevant region and save us the high cost of rasterized a very large
surface on the main thread, which at the same time, looking as crisp as
a user would expect.
This commit is contained in:
Andrew Osmond 2018-09-20 18:15:34 -04:00
parent f799e4bd72
commit 73e08d1bf5
6 changed files with 136 additions and 43 deletions

View File

@ -160,6 +160,7 @@ ImageResource::GetImageContainerImpl(LayerManager* aManager,
case ImgDrawResult::SUCCESS: case ImgDrawResult::SUCCESS:
case ImgDrawResult::BAD_IMAGE: case ImgDrawResult::BAD_IMAGE:
case ImgDrawResult::BAD_ARGS: case ImgDrawResult::BAD_ARGS:
case ImgDrawResult::NOT_SUPPORTED:
container.forget(aOutContainer); container.forget(aOutContainer);
return entry->mLastDrawResult; return entry->mLastDrawResult;
case ImgDrawResult::NOT_READY: case ImgDrawResult::NOT_READY:
@ -218,6 +219,7 @@ ImageResource::GetImageContainerImpl(LayerManager* aManager,
case ImgDrawResult::SUCCESS: case ImgDrawResult::SUCCESS:
case ImgDrawResult::BAD_IMAGE: case ImgDrawResult::BAD_IMAGE:
case ImgDrawResult::BAD_ARGS: case ImgDrawResult::BAD_ARGS:
case ImgDrawResult::NOT_SUPPORTED:
container.forget(aOutContainer); container.forget(aOutContainer);
return entry->mLastDrawResult; return entry->mLastDrawResult;
case ImgDrawResult::NOT_READY: case ImgDrawResult::NOT_READY:

View File

@ -337,6 +337,24 @@ protected:
bool mAnimating:1; // Are we currently animating? bool mAnimating:1; // Are we currently animating?
bool mError:1; // Error handling bool mError:1; // Error handling
/**
* Attempt to find a matching cached surface in the SurfaceCache, and if not
* available, request the production of such a surface (either synchronously
* or asynchronously).
*
* If the draw result is BAD_IMAGE, BAD_ARGS or NOT_READY, the size will be
* the same as aSize. If it is TEMPORARY_ERROR, INCOMPLETE, or SUCCESS, the
* size is a hint as to what we expect the surface size to be, once the best
* fitting size is available. It may or may not match the size of the surface
* returned at this moment. This is useful for choosing how to store the final
* result (e.g. if going into an ImageContainer, ideally we would share the
* same container for many requested sizes, if they all end up with the same
* best fit size in the end).
*
* A valid surface should only be returned for SUCCESS and INCOMPLETE.
*
* Any other draw result is invalid.
*/
virtual Tuple<ImgDrawResult, gfx::IntSize, RefPtr<gfx::SourceSurface>> virtual Tuple<ImgDrawResult, gfx::IntSize, RefPtr<gfx::SourceSurface>>
GetFrameInternal(const gfx::IntSize& aSize, GetFrameInternal(const gfx::IntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext, const Maybe<SVGImageContext>& aSVGContext,

View File

@ -109,8 +109,11 @@ private:
DrawableSurface mSurface; DrawableSurface mSurface;
MatchType mMatchType; MatchType mMatchType;
/// If given, the size the caller should request a decode at. This may or may /// mSuggestedSize will be the size of the returned surface if the result is
/// not match the size the caller requested from the cache. /// SUBSTITUTE_BECAUSE_BEST. It will be empty for EXACT, and can contain a
/// non-empty size possibly different from the returned surface (if any) for
/// all other results. If non-empty, it will always be the size the caller
/// should request any decodes at.
gfx::IntSize mSuggestedSize; gfx::IntSize mSuggestedSize;
}; };

View File

@ -24,7 +24,8 @@ struct SVGDrawingParameters
typedef mozilla::gfx::SamplingFilter SamplingFilter; typedef mozilla::gfx::SamplingFilter SamplingFilter;
SVGDrawingParameters(gfxContext* aContext, SVGDrawingParameters(gfxContext* aContext,
const nsIntSize& aSize, const nsIntSize& aRasterSize,
const nsIntSize& aDrawSize,
const ImageRegion& aRegion, const ImageRegion& aRegion,
SamplingFilter aSamplingFilter, SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext, const Maybe<SVGImageContext>& aSVGContext,
@ -32,11 +33,12 @@ struct SVGDrawingParameters
uint32_t aFlags, uint32_t aFlags,
float aOpacity) float aOpacity)
: context(aContext) : context(aContext)
, size(aSize.width, aSize.height) , size(aRasterSize)
, drawSize(aDrawSize)
, region(aRegion) , region(aRegion)
, samplingFilter(aSamplingFilter) , samplingFilter(aSamplingFilter)
, svgContext(aSVGContext) , svgContext(aSVGContext)
, viewportSize(aSize) , viewportSize(aRasterSize)
, animationTime(aAnimationTime) , animationTime(aAnimationTime)
, flags(aFlags) , flags(aFlags)
, opacity(aOpacity) , opacity(aOpacity)
@ -50,7 +52,8 @@ struct SVGDrawingParameters
} }
gfxContext* context; gfxContext* context;
IntSize size; IntSize size; // Size to rasterize a surface at.
IntSize drawSize; // Size to draw the given surface at.
ImageRegion region; ImageRegion region;
SamplingFilter samplingFilter; SamplingFilter samplingFilter;
const Maybe<SVGImageContext>& svgContext; const Maybe<SVGImageContext>& svgContext;

View File

@ -807,15 +807,21 @@ VectorImage::GetFrameInternal(const IntSize& aSize,
RefPtr<SourceSurface>()); RefPtr<SourceSurface>());
} }
RefPtr<SourceSurface> sourceSurface = // We don't allow large surfaces to be rasterized on the Draw and
// GetImageContainerAtSize paths, because those have alternatives. If we get
// here however, then we know it came from GetFrame(AtSize) and that path does
// not have any fallback method, so we don't check UseSurfaceCacheForSize.
RefPtr<SourceSurface> sourceSurface;
IntSize decodeSize;
Tie(sourceSurface, decodeSize) =
LookupCachedSurface(aSize, aSVGContext, aFlags); LookupCachedSurface(aSize, aSVGContext, aFlags);
if (sourceSurface) { if (sourceSurface) {
return MakeTuple(ImgDrawResult::SUCCESS, aSize, std::move(sourceSurface)); return MakeTuple(ImgDrawResult::SUCCESS, decodeSize, std::move(sourceSurface));
} }
if (mIsDrawing) { if (mIsDrawing) {
NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw"); NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw");
return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, aSize, return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, decodeSize,
RefPtr<SourceSurface>()); RefPtr<SourceSurface>());
} }
@ -824,7 +830,8 @@ VectorImage::GetFrameInternal(const IntSize& aSize,
// flags, having an animation, etc). Otherwise CreateSurface will assume that // flags, having an animation, etc). Otherwise CreateSurface will assume that
// the caller is capable of drawing directly to its own draw target if we // the caller is capable of drawing directly to its own draw target if we
// cannot cache. // cannot cache.
SVGDrawingParameters params(nullptr, aSize, ImageRegion::Create(aSize), SVGDrawingParameters params(nullptr, decodeSize, aSize,
ImageRegion::Create(decodeSize),
SamplingFilter::POINT, aSVGContext, SamplingFilter::POINT, aSVGContext,
mSVGDocumentWrapper->GetCurrentTime(), mSVGDocumentWrapper->GetCurrentTime(),
aFlags, 1.0); aFlags, 1.0);
@ -840,12 +847,12 @@ VectorImage::GetFrameInternal(const IntSize& aSize,
CreateSurface(params, svgDrawable, didCache); CreateSurface(params, svgDrawable, didCache);
if (!surface) { if (!surface) {
MOZ_ASSERT(!didCache); MOZ_ASSERT(!didCache);
return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, aSize, return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, decodeSize,
RefPtr<SourceSurface>()); RefPtr<SourceSurface>());
} }
SendFrameComplete(didCache, params.flags); SendFrameComplete(didCache, params.flags);
return MakeTuple(ImgDrawResult::SUCCESS, aSize, std::move(surface)); return MakeTuple(ImgDrawResult::SUCCESS, decodeSize, std::move(surface));
} }
//****************************************************************************** //******************************************************************************
@ -904,7 +911,9 @@ VectorImage::IsImageContainerAvailableAtSize(LayerManager* aManager,
{ {
// Since we only support image containers with WebRender, and it can handle // Since we only support image containers with WebRender, and it can handle
// textures larger than the hw max texture size, we don't need to check aSize. // textures larger than the hw max texture size, we don't need to check aSize.
return !aSize.IsEmpty() && IsImageContainerAvailable(aManager, aFlags); return !aSize.IsEmpty() &&
UseSurfaceCacheForSize(aSize) &&
IsImageContainerAvailable(aManager, aFlags);
} }
//****************************************************************************** //******************************************************************************
@ -915,15 +924,16 @@ VectorImage::GetImageContainerAtSize(layers::LayerManager* aManager,
uint32_t aFlags, uint32_t aFlags,
layers::ImageContainer** aOutContainer) layers::ImageContainer** aOutContainer)
{ {
if (!UseSurfaceCacheForSize(aSize)) {
return ImgDrawResult::NOT_SUPPORTED;
}
Maybe<SVGImageContext> newSVGContext; Maybe<SVGImageContext> newSVGContext;
MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags); MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags);
// Since we do not support high quality scaling with SVG, we mask it off so // The aspect ratio flag was already handled as part of the SVG context
// that container requests with and without it map to the same container. // restriction above.
// Similarly the aspect ratio flag was already handled as part of the SVG uint32_t flags = aFlags & ~(FLAG_FORCE_PRESERVEASPECTRATIO_NONE);
// context restriction above.
uint32_t flags = aFlags & ~(FLAG_HIGH_QUALITY_SCALING |
FLAG_FORCE_PRESERVEASPECTRATIO_NONE);
return GetImageContainerImpl(aManager, aSize, return GetImageContainerImpl(aManager, aSize,
newSVGContext ? newSVGContext : aSVGContext, newSVGContext ? newSVGContext : aSVGContext,
flags, aOutContainer); flags, aOutContainer);
@ -1002,10 +1012,13 @@ VectorImage::Draw(gfxContext* aContext,
SendOnUnlockedDraw(aFlags); SendOnUnlockedDraw(aFlags);
} }
// We should always bypass the cache when using DrawTargetRecording because // We should bypass the cache when:
// we prefer the drawing commands in general to the rasterized surface. This // - We are using a DrawTargetRecording because we prefer the drawing commands
// allows blob images to avoid rasterized SVGs with WebRender. // in general to the rasterized surface. This allows blob images to avoid
if (aContext->GetDrawTarget()->GetBackendType() == BackendType::RECORDING) { // rasterized SVGs with WebRender.
// - The size exceeds what we are will to cache as a rasterized surface.
if (aContext->GetDrawTarget()->GetBackendType() == BackendType::RECORDING ||
!UseSurfaceCacheForSize(aSize)) {
aFlags |= FLAG_BYPASS_SURFACE_CACHE; aFlags |= FLAG_BYPASS_SURFACE_CACHE;
} }
@ -1021,18 +1034,19 @@ VectorImage::Draw(gfxContext* aContext,
bool contextPaint = bool contextPaint =
MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags); MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags);
SVGDrawingParameters params(aContext, aSize, aRegion, aSamplingFilter, SVGDrawingParameters params(aContext, aSize, aSize, aRegion, aSamplingFilter,
newSVGContext ? newSVGContext : aSVGContext, newSVGContext ? newSVGContext : aSVGContext,
animTime, aFlags, aOpacity); animTime, aFlags, aOpacity);
// If we have an prerasterized version of this image that matches the // If we have an prerasterized version of this image that matches the
// drawing parameters, use that. // drawing parameters, use that.
RefPtr<SourceSurface> sourceSurface = RefPtr<SourceSurface> sourceSurface;
Tie(sourceSurface, params.size) =
LookupCachedSurface(aSize, params.svgContext, aFlags); LookupCachedSurface(aSize, params.svgContext, aFlags);
if (sourceSurface) { if (sourceSurface) {
RefPtr<gfxDrawable> svgDrawable = RefPtr<gfxDrawable> drawable =
new gfxSurfaceDrawable(sourceSurface, sourceSurface->GetSize()); new gfxSurfaceDrawable(sourceSurface, params.size);
Show(svgDrawable, params); Show(drawable, params);
return ImgDrawResult::SUCCESS; return ImgDrawResult::SUCCESS;
} }
@ -1076,29 +1090,56 @@ VectorImage::CreateSVGDrawable(const SVGDrawingParameters& aParams)
return svgDrawable.forget(); return svgDrawable.forget();
} }
already_AddRefed<SourceSurface> bool
VectorImage::UseSurfaceCacheForSize(const IntSize& aSize) const
{
int32_t maxSizeKB = gfxPrefs::ImageCacheMaxRasterizedSVGThresholdKB();
if (maxSizeKB <= 0) {
return true;
}
if (!SurfaceCache::IsLegalSize(aSize)) {
return false;
}
// With factor of 2 mode, we should be willing to use a surface which is up
// to twice the width, and twice the height, of the maximum sized surface
// before switching to drawing to the target directly. That means the size in
// KB works out to be:
// width * height * 4 [bytes/pixel] * / 1024 [bytes/KB] <= 2 * 2 * maxSizeKB
return aSize.width * aSize.height / 1024 <= maxSizeKB;
}
Tuple<RefPtr<SourceSurface>, IntSize>
VectorImage::LookupCachedSurface(const IntSize& aSize, VectorImage::LookupCachedSurface(const IntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext, const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags) uint32_t aFlags)
{ {
// If we're not allowed to use a cached surface, don't attempt a lookup. // If we're not allowed to use a cached surface, don't attempt a lookup.
if (aFlags & FLAG_BYPASS_SURFACE_CACHE) { if (aFlags & FLAG_BYPASS_SURFACE_CACHE) {
return nullptr; return MakeTuple(RefPtr<SourceSurface>(), aSize);
} }
// We don't do any caching if we have animation, so don't bother with a lookup // We don't do any caching if we have animation, so don't bother with a lookup
// in this case either. // in this case either.
if (mHaveAnimations) { if (mHaveAnimations) {
return nullptr; return MakeTuple(RefPtr<SourceSurface>(), aSize);
} }
LookupResult result = LookupResult result(MatchType::NOT_FOUND);
SurfaceCache::Lookup(ImageKey(this), SurfaceKey surfaceKey = VectorSurfaceKey(aSize, aSVGContext);
VectorSurfaceKey(aSize, aSVGContext)); if ((aFlags & FLAG_SYNC_DECODE) || !(aFlags & FLAG_HIGH_QUALITY_SCALING)) {
result = SurfaceCache::Lookup(ImageKey(this), surfaceKey);
} else {
result = SurfaceCache::LookupBestMatch(ImageKey(this), surfaceKey);
}
MOZ_ASSERT(result.SuggestedSize().IsEmpty(), "SVG should not substitute!"); IntSize rasterSize = result.SuggestedSize().IsEmpty()
if (!result) { ? aSize : result.SuggestedSize();
return nullptr; // No matching surface, or the OS freed the volatile buffer. MOZ_ASSERT(result.Type() != MatchType::SUBSTITUTE_BECAUSE_PENDING);
if (!result || result.Type() == MatchType::SUBSTITUTE_BECAUSE_NOT_FOUND) {
// No matching surface, or the OS freed the volatile buffer.
return MakeTuple(RefPtr<SourceSurface>(), rasterSize);
} }
RefPtr<SourceSurface> sourceSurface = result.Surface()->GetSourceSurface(); RefPtr<SourceSurface> sourceSurface = result.Surface()->GetSourceSurface();
@ -1106,10 +1147,10 @@ VectorImage::LookupCachedSurface(const IntSize& aSize,
// Something went wrong. (Probably a GPU driver crash or device reset.) // Something went wrong. (Probably a GPU driver crash or device reset.)
// Attempt to recover. // Attempt to recover.
RecoverFromLossOfSurfaces(); RecoverFromLossOfSurfaces();
return nullptr; return MakeTuple(RefPtr<SourceSurface>(), rasterSize);
} }
return sourceSurface.forget(); return MakeTuple(std::move(sourceSurface), rasterSize);
} }
already_AddRefed<SourceSurface> already_AddRefed<SourceSurface>
@ -1189,7 +1230,15 @@ VectorImage::CreateSurface(const SVGDrawingParameters& aParams,
SurfaceKey surfaceKey = VectorSurfaceKey(aParams.size, aParams.svgContext); SurfaceKey surfaceKey = VectorSurfaceKey(aParams.size, aParams.svgContext);
NotNull<RefPtr<ISurfaceProvider>> provider = NotNull<RefPtr<ISurfaceProvider>> provider =
MakeNotNull<SimpleSurfaceProvider*>(ImageKey(this), surfaceKey, frame); MakeNotNull<SimpleSurfaceProvider*>(ImageKey(this), surfaceKey, frame);
SurfaceCache::Insert(provider);
if (SurfaceCache::Insert(provider) == InsertOutcome::SUCCESS &&
aParams.size != aParams.drawSize) {
// We created a new surface that wasn't the size we requested, which means
// we entered factor-of-2 mode. We should purge any surfaces we no longer
// need rather than waiting for the cache to expire them.
SurfaceCache::PruneImage(ImageKey(this));
}
return surface.forget(); return surface.forget();
} }
@ -1224,10 +1273,21 @@ VectorImage::SendFrameComplete(bool aDidCache, uint32_t aFlags)
void void
VectorImage::Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams) VectorImage::Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams)
{ {
// The surface size may differ from the size at which we wish to draw. As
// such, we may need to adjust the context/region to take this into account.
gfxContextMatrixAutoSaveRestore saveMatrix(aParams.context);
ImageRegion region(aParams.region);
if (aParams.drawSize != aParams.size) {
gfx::Size scale(double(aParams.drawSize.width) / aParams.size.width,
double(aParams.drawSize.height) / aParams.size.height);
aParams.context->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
region.Scale(1.0 / scale.width, 1.0 / scale.height);
}
MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now"); MOZ_ASSERT(aDrawable, "Should have a gfxDrawable by now");
gfxUtils::DrawPixelSnapped(aParams.context, aDrawable, gfxUtils::DrawPixelSnapped(aParams.context, aDrawable,
SizeDouble(aParams.size), SizeDouble(aParams.size),
aParams.region, region,
SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8,
aParams.samplingFilter, aParams.samplingFilter,
aParams.flags, aParams.opacity, false); aParams.flags, aParams.opacity, false);

View File

@ -92,8 +92,12 @@ private:
const IntSize& aSize, const IntSize& aSize,
uint32_t aFlags) override; uint32_t aFlags) override;
/// Attempt to find a matching cached surface in the SurfaceCache. /**
already_AddRefed<SourceSurface> * Attempt to find a matching cached surface in the SurfaceCache. Returns the
* cached surface, if found, and the size to rasterize at, if applicable.
* If we cannot rasterize, it will be the requested size to draw at (aSize).
*/
Tuple<RefPtr<SourceSurface>, IntSize>
LookupCachedSurface(const IntSize& aSize, LookupCachedSurface(const IntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext, const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags); uint32_t aFlags);
@ -106,6 +110,9 @@ private:
already_AddRefed<gfxDrawable> already_AddRefed<gfxDrawable>
CreateSVGDrawable(const SVGDrawingParameters& aParams); CreateSVGDrawable(const SVGDrawingParameters& aParams);
/// Returns true if we use the surface cache to store rasterized copies.
bool UseSurfaceCacheForSize(const IntSize& aSize) const;
/// Rasterize the SVG into a surface. aWillCache will be set to whether or /// Rasterize the SVG into a surface. aWillCache will be set to whether or
/// not the new surface was put into the cache. /// not the new surface was put into the cache.
already_AddRefed<SourceSurface> already_AddRefed<SourceSurface>