diff --git a/image/Image.cpp b/image/Image.cpp index 650c9da83916..fa6b9c0657bc 100644 --- a/image/Image.cpp +++ b/image/Image.cpp @@ -160,6 +160,7 @@ ImageResource::GetImageContainerImpl(LayerManager* aManager, case ImgDrawResult::SUCCESS: case ImgDrawResult::BAD_IMAGE: case ImgDrawResult::BAD_ARGS: + case ImgDrawResult::NOT_SUPPORTED: container.forget(aOutContainer); return entry->mLastDrawResult; case ImgDrawResult::NOT_READY: @@ -218,6 +219,7 @@ ImageResource::GetImageContainerImpl(LayerManager* aManager, case ImgDrawResult::SUCCESS: case ImgDrawResult::BAD_IMAGE: case ImgDrawResult::BAD_ARGS: + case ImgDrawResult::NOT_SUPPORTED: container.forget(aOutContainer); return entry->mLastDrawResult; case ImgDrawResult::NOT_READY: diff --git a/image/Image.h b/image/Image.h index ab405e6228a5..d72bd67cd99a 100644 --- a/image/Image.h +++ b/image/Image.h @@ -337,6 +337,24 @@ protected: bool mAnimating:1; // Are we currently animating? 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> GetFrameInternal(const gfx::IntSize& aSize, const Maybe& aSVGContext, diff --git a/image/LookupResult.h b/image/LookupResult.h index b49615d00748..264c576e8757 100644 --- a/image/LookupResult.h +++ b/image/LookupResult.h @@ -109,8 +109,11 @@ private: DrawableSurface mSurface; MatchType mMatchType; - /// If given, the size the caller should request a decode at. This may or may - /// not match the size the caller requested from the cache. + /// mSuggestedSize will be the size of the returned surface if the result is + /// 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; }; diff --git a/image/SVGDrawingParameters.h b/image/SVGDrawingParameters.h index 322151a68367..0d243b3b1711 100644 --- a/image/SVGDrawingParameters.h +++ b/image/SVGDrawingParameters.h @@ -24,7 +24,8 @@ struct SVGDrawingParameters typedef mozilla::gfx::SamplingFilter SamplingFilter; SVGDrawingParameters(gfxContext* aContext, - const nsIntSize& aSize, + const nsIntSize& aRasterSize, + const nsIntSize& aDrawSize, const ImageRegion& aRegion, SamplingFilter aSamplingFilter, const Maybe& aSVGContext, @@ -32,11 +33,12 @@ struct SVGDrawingParameters uint32_t aFlags, float aOpacity) : context(aContext) - , size(aSize.width, aSize.height) + , size(aRasterSize) + , drawSize(aDrawSize) , region(aRegion) , samplingFilter(aSamplingFilter) , svgContext(aSVGContext) - , viewportSize(aSize) + , viewportSize(aRasterSize) , animationTime(aAnimationTime) , flags(aFlags) , opacity(aOpacity) @@ -50,7 +52,8 @@ struct SVGDrawingParameters } gfxContext* context; - IntSize size; + IntSize size; // Size to rasterize a surface at. + IntSize drawSize; // Size to draw the given surface at. ImageRegion region; SamplingFilter samplingFilter; const Maybe& svgContext; diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp index 597a3fd63169..92298ebc074c 100644 --- a/image/VectorImage.cpp +++ b/image/VectorImage.cpp @@ -807,15 +807,21 @@ VectorImage::GetFrameInternal(const IntSize& aSize, RefPtr()); } - RefPtr 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; + IntSize decodeSize; + Tie(sourceSurface, decodeSize) = LookupCachedSurface(aSize, aSVGContext, aFlags); if (sourceSurface) { - return MakeTuple(ImgDrawResult::SUCCESS, aSize, std::move(sourceSurface)); + return MakeTuple(ImgDrawResult::SUCCESS, decodeSize, std::move(sourceSurface)); } if (mIsDrawing) { NS_WARNING("Refusing to make re-entrant call to VectorImage::Draw"); - return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, aSize, + return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, decodeSize, RefPtr()); } @@ -824,7 +830,8 @@ VectorImage::GetFrameInternal(const IntSize& aSize, // flags, having an animation, etc). Otherwise CreateSurface will assume that // the caller is capable of drawing directly to its own draw target if we // cannot cache. - SVGDrawingParameters params(nullptr, aSize, ImageRegion::Create(aSize), + SVGDrawingParameters params(nullptr, decodeSize, aSize, + ImageRegion::Create(decodeSize), SamplingFilter::POINT, aSVGContext, mSVGDocumentWrapper->GetCurrentTime(), aFlags, 1.0); @@ -840,12 +847,12 @@ VectorImage::GetFrameInternal(const IntSize& aSize, CreateSurface(params, svgDrawable, didCache); if (!surface) { MOZ_ASSERT(!didCache); - return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, aSize, + return MakeTuple(ImgDrawResult::TEMPORARY_ERROR, decodeSize, RefPtr()); } 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 // 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, layers::ImageContainer** aOutContainer) { + if (!UseSurfaceCacheForSize(aSize)) { + return ImgDrawResult::NOT_SUPPORTED; + } + Maybe newSVGContext; MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags); - // Since we do not support high quality scaling with SVG, we mask it off so - // that container requests with and without it map to the same container. - // Similarly the aspect ratio flag was already handled as part of the SVG - // context restriction above. - uint32_t flags = aFlags & ~(FLAG_HIGH_QUALITY_SCALING | - FLAG_FORCE_PRESERVEASPECTRATIO_NONE); + // The aspect ratio flag was already handled as part of the SVG context + // restriction above. + uint32_t flags = aFlags & ~(FLAG_FORCE_PRESERVEASPECTRATIO_NONE); return GetImageContainerImpl(aManager, aSize, newSVGContext ? newSVGContext : aSVGContext, flags, aOutContainer); @@ -1002,10 +1012,13 @@ VectorImage::Draw(gfxContext* aContext, SendOnUnlockedDraw(aFlags); } - // We should always bypass the cache when using DrawTargetRecording because - // we prefer the drawing commands in general to the rasterized surface. This - // allows blob images to avoid rasterized SVGs with WebRender. - if (aContext->GetDrawTarget()->GetBackendType() == BackendType::RECORDING) { + // We should bypass the cache when: + // - We are using a DrawTargetRecording because we prefer the drawing commands + // in general to the rasterized surface. This allows blob images to avoid + // 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; } @@ -1021,18 +1034,19 @@ VectorImage::Draw(gfxContext* aContext, bool contextPaint = MaybeRestrictSVGContext(newSVGContext, aSVGContext, aFlags); - SVGDrawingParameters params(aContext, aSize, aRegion, aSamplingFilter, + SVGDrawingParameters params(aContext, aSize, aSize, aRegion, aSamplingFilter, newSVGContext ? newSVGContext : aSVGContext, animTime, aFlags, aOpacity); // If we have an prerasterized version of this image that matches the // drawing parameters, use that. - RefPtr sourceSurface = + RefPtr sourceSurface; + Tie(sourceSurface, params.size) = LookupCachedSurface(aSize, params.svgContext, aFlags); if (sourceSurface) { - RefPtr svgDrawable = - new gfxSurfaceDrawable(sourceSurface, sourceSurface->GetSize()); - Show(svgDrawable, params); + RefPtr drawable = + new gfxSurfaceDrawable(sourceSurface, params.size); + Show(drawable, params); return ImgDrawResult::SUCCESS; } @@ -1076,29 +1090,56 @@ VectorImage::CreateSVGDrawable(const SVGDrawingParameters& aParams) return svgDrawable.forget(); } -already_AddRefed +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, IntSize> VectorImage::LookupCachedSurface(const IntSize& aSize, const Maybe& aSVGContext, uint32_t aFlags) { // If we're not allowed to use a cached surface, don't attempt a lookup. if (aFlags & FLAG_BYPASS_SURFACE_CACHE) { - return nullptr; + return MakeTuple(RefPtr(), aSize); } // We don't do any caching if we have animation, so don't bother with a lookup // in this case either. if (mHaveAnimations) { - return nullptr; + return MakeTuple(RefPtr(), aSize); } - LookupResult result = - SurfaceCache::Lookup(ImageKey(this), - VectorSurfaceKey(aSize, aSVGContext)); + LookupResult result(MatchType::NOT_FOUND); + SurfaceKey surfaceKey = 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!"); - if (!result) { - return nullptr; // No matching surface, or the OS freed the volatile buffer. + IntSize rasterSize = result.SuggestedSize().IsEmpty() + ? aSize : result.SuggestedSize(); + 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(), rasterSize); } RefPtr sourceSurface = result.Surface()->GetSourceSurface(); @@ -1106,10 +1147,10 @@ VectorImage::LookupCachedSurface(const IntSize& aSize, // Something went wrong. (Probably a GPU driver crash or device reset.) // Attempt to recover. RecoverFromLossOfSurfaces(); - return nullptr; + return MakeTuple(RefPtr(), rasterSize); } - return sourceSurface.forget(); + return MakeTuple(std::move(sourceSurface), rasterSize); } already_AddRefed @@ -1189,7 +1230,15 @@ VectorImage::CreateSurface(const SVGDrawingParameters& aParams, SurfaceKey surfaceKey = VectorSurfaceKey(aParams.size, aParams.svgContext); NotNull> provider = MakeNotNull(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(); } @@ -1224,10 +1273,21 @@ VectorImage::SendFrameComplete(bool aDidCache, uint32_t aFlags) void 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"); gfxUtils::DrawPixelSnapped(aParams.context, aDrawable, SizeDouble(aParams.size), - aParams.region, + region, SurfaceFormat::B8G8R8A8, aParams.samplingFilter, aParams.flags, aParams.opacity, false); diff --git a/image/VectorImage.h b/image/VectorImage.h index bbc5ac6e12ea..114de1c7a956 100644 --- a/image/VectorImage.h +++ b/image/VectorImage.h @@ -92,8 +92,12 @@ private: const IntSize& aSize, uint32_t aFlags) override; - /// Attempt to find a matching cached surface in the SurfaceCache. - already_AddRefed + /** + * 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, IntSize> LookupCachedSurface(const IntSize& aSize, const Maybe& aSVGContext, uint32_t aFlags); @@ -106,6 +110,9 @@ private: already_AddRefed 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 /// not the new surface was put into the cache. already_AddRefed