Add optional sw generated path coverage mask caching

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2335343008

All the bots except the painfully slow windows compiler have finished so,
NOTRY=true

Review-Url: https://codereview.chromium.org/2335343008
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index fbb59a3..6cec1da 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -97,6 +97,8 @@
     dtOptions.fMaxBatchLookahead = options.fMaxBatchLookahead;
     GrPathRendererChain::Options prcOptions;
     prcOptions.fDisableDistanceFieldRenderer = options.fDisableDistanceFieldPaths;
+    prcOptions.fAllowPathMaskCaching = options.fAllowPathMaskCaching;
+    prcOptions.fDisableAllPathRenderers = options.fForceSWPathMasks;
     fDrawingManager.reset(new GrDrawingManager(this, dtOptions, prcOptions, options.fImmediateMode,
                                                &fSingleOwner));
 
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 9dd5613..f4ac150 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -203,7 +203,9 @@
     GrPathRenderer* pr = fPathRendererChain->getPathRenderer(args, drawType, stencilSupport);
     if (!pr && allowSW) {
         if (!fSoftwarePathRenderer) {
-            fSoftwarePathRenderer = new GrSoftwarePathRenderer(fContext->textureProvider());
+            fSoftwarePathRenderer =
+                    new GrSoftwarePathRenderer(fContext->textureProvider(),
+                                               fOptionsForPathRendererChain.fAllowPathMaskCaching);
         }
         pr = fSoftwarePathRenderer;
     }
diff --git a/src/gpu/GrPathRendererChain.cpp b/src/gpu/GrPathRendererChain.cpp
index 12ad51e..95105ba 100644
--- a/src/gpu/GrPathRendererChain.cpp
+++ b/src/gpu/GrPathRendererChain.cpp
@@ -26,30 +26,32 @@
 #include "batches/GrTessellatingPathRenderer.h"
 
 GrPathRendererChain::GrPathRendererChain(GrContext* context, const Options& options) {
-    const GrCaps& caps = *context->caps();
-    this->addPathRenderer(new GrDashLinePathRenderer)->unref();
+    if (!options.fDisableAllPathRenderers) {
+        const GrCaps& caps = *context->caps();
+        this->addPathRenderer(new GrDashLinePathRenderer)->unref();
 
-    if (GrPathRenderer* pr = GrStencilAndCoverPathRenderer::Create(context->resourceProvider(),
-                                                                   caps)) {
-        this->addPathRenderer(pr)->unref();
+        if (GrPathRenderer* pr = GrStencilAndCoverPathRenderer::Create(context->resourceProvider(),
+                                                                       caps)) {
+            this->addPathRenderer(pr)->unref();
+        }
+    #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK
+        if (caps.sampleShadingSupport()) {
+            this->addPathRenderer(new GrMSAAPathRenderer)->unref();
+        }
+    #endif
+        this->addPathRenderer(new GrAAHairLinePathRenderer)->unref();
+        this->addPathRenderer(new GrAAConvexPathRenderer)->unref();
+        this->addPathRenderer(new GrAALinearizingConvexPathRenderer)->unref();
+        if (caps.shaderCaps()->plsPathRenderingSupport()) {
+            this->addPathRenderer(new GrPLSPathRenderer)->unref();
+        }
+        if (!options.fDisableDistanceFieldRenderer) {
+            this->addPathRenderer(new GrAADistanceFieldPathRenderer)->unref();
+        }
+        this->addPathRenderer(new GrTessellatingPathRenderer)->unref();
+        this->addPathRenderer(new GrDefaultPathRenderer(caps.twoSidedStencilSupport(),
+                                                        caps.stencilWrapOpsSupport()))->unref();
     }
-#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    if (caps.sampleShadingSupport()) {
-        this->addPathRenderer(new GrMSAAPathRenderer)->unref();
-    }
-#endif
-    this->addPathRenderer(new GrAAHairLinePathRenderer)->unref();
-    this->addPathRenderer(new GrAAConvexPathRenderer)->unref();
-    this->addPathRenderer(new GrAALinearizingConvexPathRenderer)->unref();
-    if (caps.shaderCaps()->plsPathRenderingSupport()) {
-        this->addPathRenderer(new GrPLSPathRenderer)->unref();
-    }
-    if (!options.fDisableDistanceFieldRenderer) {
-        this->addPathRenderer(new GrAADistanceFieldPathRenderer)->unref();
-    }
-    this->addPathRenderer(new GrTessellatingPathRenderer)->unref();
-    this->addPathRenderer(new GrDefaultPathRenderer(caps.twoSidedStencilSupport(),
-                                                    caps.stencilWrapOpsSupport()))->unref();
 }
 
 GrPathRendererChain::~GrPathRendererChain() {
diff --git a/src/gpu/GrPathRendererChain.h b/src/gpu/GrPathRendererChain.h
index 2a11a19..8788374 100644
--- a/src/gpu/GrPathRendererChain.h
+++ b/src/gpu/GrPathRendererChain.h
@@ -25,6 +25,8 @@
 public:
     struct Options {
         bool fDisableDistanceFieldRenderer = false;
+        bool fAllowPathMaskCaching = false;
+        bool fDisableAllPathRenderers = false;
     };
     GrPathRendererChain(GrContext* context, const Options&);
 
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index 66d60ab..a20eacb 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -99,13 +99,17 @@
 /**
  * Get a texture (from the texture cache) of the correct size & format.
  */
-GrTexture* GrSWMaskHelper::createTexture() {
+GrTexture* GrSWMaskHelper::createTexture(TextureType textureType) {
     GrSurfaceDesc desc;
     desc.fWidth = fPixels.width();
     desc.fHeight = fPixels.height();
     desc.fConfig = kAlpha_8_GrPixelConfig;
 
-    return fTexProvider->createApproxTexture(desc);
+    if (TextureType::kApproximateFit == textureType) {
+        return fTexProvider->createApproxTexture(desc);
+    } else {
+        return fTexProvider->createTexture(desc, SkBudgeted::kYes);
+    }
 }
 
 /**
@@ -138,6 +142,7 @@
                                                   const GrShape& shape,
                                                   const SkIRect& resultBounds,
                                                   bool antiAlias,
+                                                  TextureType textureType,
                                                   const SkMatrix* matrix) {
     GrSWMaskHelper helper(texProvider);
 
@@ -147,7 +152,7 @@
 
     helper.drawShape(shape, SkRegion::kReplace_Op, antiAlias, 0xFF);
 
-    GrTexture* texture(helper.createTexture());
+    GrTexture* texture(helper.createTexture(textureType));
     if (!texture) {
         return nullptr;
     }
@@ -163,23 +168,22 @@
                                                const GrUserStencilSettings& userStencilSettings,
                                                const GrClip& clip,
                                                const SkMatrix& viewMatrix,
-                                               const SkIRect& rect) {
+                                               const SkIPoint& textureOriginInDeviceSpace,
+                                               const SkIRect& deviceSpaceRectToDraw) {
     SkMatrix invert;
     if (!viewMatrix.invert(&invert)) {
         return;
     }
 
-    SkRect dstRect = SkRect::MakeLTRB(SK_Scalar1 * rect.fLeft,
-                                      SK_Scalar1 * rect.fTop,
-                                      SK_Scalar1 * rect.fRight,
-                                      SK_Scalar1 * rect.fBottom);
+    SkRect dstRect = SkRect::Make(deviceSpaceRectToDraw);
 
     // We use device coords to compute the texture coordinates. We take the device coords and apply
     // a translation so that the top-left of the device bounds maps to 0,0, and then a scaling
     // matrix to normalized coords.
     SkMatrix maskMatrix;
     maskMatrix.setIDiv(texture->width(), texture->height());
-    maskMatrix.preTranslate(SkIntToScalar(-rect.fLeft), SkIntToScalar(-rect.fTop));
+    maskMatrix.preTranslate(SkIntToScalar(-textureOriginInDeviceSpace.fX),
+                            SkIntToScalar(-textureOriginInDeviceSpace.fY));
 
     GrPipelineBuilder pipelineBuilder(paint, drawContext->mustUseHWAA(paint));
     pipelineBuilder.setUserStencil(&userStencilSettings);
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index f2bb34b..46520a9 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -8,8 +8,9 @@
 #ifndef GrSWMaskHelper_DEFINED
 #define GrSWMaskHelper_DEFINED
 
-#include "SkAutoPixmapStorage.h"
 #include "GrColor.h"
+#include "GrTextureProvider.h"
+#include "SkAutoPixmapStorage.h"
 #include "SkBitmap.h"
 #include "SkDraw.h"
 #include "SkMatrix.h"
@@ -66,36 +67,37 @@
         fPixels.erase(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF));
     }
 
+
+    enum class TextureType {
+        kExactFit,
+        kApproximateFit
+    };
+
     // Canonical usage utility that draws a single path and uploads it
     // to the GPU. The result is returned.
     static GrTexture* DrawShapeMaskToTexture(GrTextureProvider*,
                                              const GrShape&,
                                              const SkIRect& resultBounds,
                                              bool antiAlias,
+                                             TextureType,
                                              const SkMatrix* matrix);
 
-    // This utility routine is used to add a shape's mask to some other draw.
-    // The GrClipStackClip uses it to accumulate clip masks while the
-    // GrSoftwarePathRenderer uses it to fulfill a drawPath call.
-    // It draws with "texture" as a path mask into "target" using "rect" as
-    // geometry and the current drawState. The current drawState is altered to
-    // accommodate the mask.
-    // Note that this method assumes that the GrPaint::kTotalStages slot in
-    // the draw state can be used to hold the mask texture stage.
-    // This method is really only intended to be used with the
-    // output of DrawPathMaskToTexture.
+    // This utility draws a path mask generated by DrawShapeMaskToTexture using a provided paint.
+    // The rectangle is drawn in device space. The 'viewMatrix' will be used to ensure the correct
+    // local coords are provided to any fragment processors in the paint.
     static void DrawToTargetWithShapeMask(GrTexture* texture,
                                           GrDrawContext*,
                                           const GrPaint& paint,
                                           const GrUserStencilSettings& userStencilSettings,
                                           const GrClip&,
                                           const SkMatrix& viewMatrix,
-                                          const SkIRect& rect);
+                                          const SkIPoint& textureOriginInDeviceSpace,
+                                          const SkIRect& deviceSpaceRectToDraw);
 
 private:
     // Helper function to get a scratch texture suitable for capturing the
     // result (i.e., right size & format)
-    GrTexture* createTexture();
+    GrTexture* createTexture(TextureType);
 
     GrTextureProvider*  fTexProvider;
     SkMatrix            fMatrix;
diff --git a/src/gpu/GrSoftwarePathRenderer.cpp b/src/gpu/GrSoftwarePathRenderer.cpp
index 05b3ef8..c22ce66 100644
--- a/src/gpu/GrSoftwarePathRenderer.cpp
+++ b/src/gpu/GrSoftwarePathRenderer.cpp
@@ -9,6 +9,7 @@
 #include "GrAuditTrail.h"
 #include "GrClip.h"
 #include "GrPipelineBuilder.h"
+#include "GrGpuResourcePriv.h"
 #include "GrSWMaskHelper.h"
 #include "GrTextureProvider.h"
 #include "batches/GrRectBatchFactory.h"
@@ -20,39 +21,38 @@
     return !args.fShape->style().applies() && SkToBool(fTexProvider);
 }
 
-namespace {
-
 ////////////////////////////////////////////////////////////////////////////////
-// gets device coord bounds of path (not considering the fill) and clip. The
-// path bounds will be a subset of the clip bounds. returns false if
-// path bounds would be empty.
-bool get_shape_and_clip_bounds(int width, int height,
-                               const GrClip& clip,
-                               const GrShape& shape,
-                               const SkMatrix& matrix,
-                               SkIRect* devShapeBounds,
-                               SkIRect* devClipBounds) {
+static bool get_unclipped_shape_dev_bounds(const GrShape& shape, const SkMatrix& matrix,
+                                           SkIRect* devBounds) {
+    SkRect shapeBounds = shape.styledBounds();
+    if (shapeBounds.isEmpty()) {
+        return false;
+    }
+    SkRect shapeDevBounds;
+    matrix.mapRect(&shapeDevBounds, shapeBounds);
+    shapeDevBounds.roundOut(devBounds);
+    return true;
+}
+
+// Gets the shape bounds, the clip bounds, and the intersection (if any). Returns false if there
+// is no intersection.
+static bool get_shape_and_clip_bounds(int width, int height,
+                                      const GrClip& clip,
+                                      const GrShape& shape,
+                                      const SkMatrix& matrix,
+                                      SkIRect* unclippedDevShapeBounds,
+                                      SkIRect* clippedDevShapeBounds,
+                                      SkIRect* devClipBounds) {
     // compute bounds as intersection of rt size, clip, and path
     clip.getConservativeBounds(width, height, devClipBounds);
 
-    if (devClipBounds->isEmpty()) {
-        *devShapeBounds = SkIRect::MakeWH(width, height);
+    if (!get_unclipped_shape_dev_bounds(shape, matrix, unclippedDevShapeBounds)) {
+        *unclippedDevShapeBounds = SkIRect::EmptyIRect();
+        *clippedDevShapeBounds = SkIRect::EmptyIRect();
         return false;
     }
-    SkRect shapeBounds = shape.styledBounds();
-    if (!shapeBounds.isEmpty()) {
-        SkRect shapeSBounds;
-        matrix.mapRect(&shapeSBounds, shapeBounds);
-        SkIRect shapeIBounds;
-        shapeSBounds.roundOut(&shapeIBounds);
-        *devShapeBounds = *devClipBounds;
-        if (!devShapeBounds->intersect(shapeIBounds)) {
-            // set the correct path bounds, as this would be used later.
-            *devShapeBounds = shapeIBounds;
-            return false;
-        }
-    } else {
-        *devShapeBounds = SkIRect::EmptyIRect();
+    if (!clippedDevShapeBounds->intersect(*devClipBounds, *unclippedDevShapeBounds)) {
+        *clippedDevShapeBounds = SkIRect::EmptyIRect();
         return false;
     }
     return true;
@@ -60,8 +60,6 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-}
-
 void GrSoftwarePathRenderer::DrawNonAARect(GrDrawContext* drawContext,
                                            const GrPaint& paint,
                                            const GrUserStencilSettings& userStencilSettings,
@@ -133,35 +131,97 @@
     SkASSERT(!args.fShape->style().applies());
     inverseFilled = args.fShape->inverseFilled();
 
-    SkIRect devShapeBounds, devClipBounds;
+    SkIRect unclippedDevShapeBounds, clippedDevShapeBounds, devClipBounds;
+    // To prevent overloading the cache with entries during animations we limit the cache of masks
+    // to cases where the matrix preserves axis alignment.
+    bool useCache = fAllowCaching && !inverseFilled && args.fViewMatrix->preservesAxisAlignment() &&
+                    args.fShape->hasUnstyledKey() && args.fAntiAlias;
+
     if (!get_shape_and_clip_bounds(args.fDrawContext->width(), args.fDrawContext->height(),
                                    *args.fClip, *args.fShape,
-                                   *args.fViewMatrix, &devShapeBounds, &devClipBounds)) {
+                                   *args.fViewMatrix, &unclippedDevShapeBounds,
+                                   &clippedDevShapeBounds,
+                                   &devClipBounds)) {
         if (inverseFilled) {
             DrawAroundInvPath(args.fDrawContext, *args.fPaint, *args.fUserStencilSettings,
                               *args.fClip,
-                              *args.fViewMatrix, devClipBounds, devShapeBounds);
+                              *args.fViewMatrix, devClipBounds, unclippedDevShapeBounds);
 
         }
         return true;
     }
 
-    SkAutoTUnref<GrTexture> texture(
-            GrSWMaskHelper::DrawShapeMaskToTexture(fTexProvider, *args.fShape, devShapeBounds,
-                                                   args.fAntiAlias, args.fViewMatrix));
-    if (!texture) {
-        return false;
+    const SkIRect* boundsForMask = &clippedDevShapeBounds;
+    if (useCache) {
+        // Use the cache only if >50% of the path is visible.
+        int unclippedWidth = unclippedDevShapeBounds.width();
+        int unclippedHeight = unclippedDevShapeBounds.height();
+        int unclippedArea = unclippedWidth * unclippedHeight;
+        int clippedArea = clippedDevShapeBounds.width() * clippedDevShapeBounds.height();
+        int maxTextureSize = args.fDrawContext->caps()->maxTextureSize();
+        if (unclippedArea > 2 * clippedArea || unclippedWidth > maxTextureSize ||
+            unclippedHeight > maxTextureSize) {
+            useCache = false;
+        } else {
+            boundsForMask = &unclippedDevShapeBounds;
+        }
     }
 
-    GrSWMaskHelper::DrawToTargetWithShapeMask(texture, args.fDrawContext, *args.fPaint,
+    GrUniqueKey maskKey;
+    struct KeyData {
+        SkScalar fFractionalTranslateX;
+        SkScalar fFractionalTranslateY;
+    };
+
+    if (useCache) {
+        // We require the upper left 2x2 of the matrix to match exactly for a cache hit.
+        SkScalar sx = args.fViewMatrix->get(SkMatrix::kMScaleX);
+        SkScalar sy = args.fViewMatrix->get(SkMatrix::kMScaleY);
+        SkScalar kx = args.fViewMatrix->get(SkMatrix::kMSkewX);
+        SkScalar ky = args.fViewMatrix->get(SkMatrix::kMSkewY);
+        SkScalar tx = args.fViewMatrix->get(SkMatrix::kMTransX);
+        SkScalar ty = args.fViewMatrix->get(SkMatrix::kMTransY);
+        // Allow 8 bits each in x and y of subpixel positioning.
+        SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00;
+        SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00;
+        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+        GrUniqueKey::Builder builder(&maskKey, kDomain, 5 + args.fShape->unstyledKeySize());
+        builder[0] = SkFloat2Bits(sx);
+        builder[1] = SkFloat2Bits(sy);
+        builder[2] = SkFloat2Bits(kx);
+        builder[3] = SkFloat2Bits(ky);
+        builder[4] = fracX | (fracY >> 8);
+        args.fShape->writeUnstyledKey(&builder[5]);
+    }
+
+    sk_sp<GrTexture> texture;
+    if (useCache) {
+        texture.reset(args.fResourceProvider->findAndRefTextureByUniqueKey(maskKey));
+    }
+    if (!texture) {
+         GrSWMaskHelper::TextureType type = useCache ? GrSWMaskHelper::TextureType::kExactFit
+                                                     : GrSWMaskHelper::TextureType::kApproximateFit;
+         texture.reset(GrSWMaskHelper::DrawShapeMaskToTexture(fTexProvider, *args.fShape,
+                                                              *boundsForMask, args.fAntiAlias,
+                                                              type, args.fViewMatrix));
+         if (!texture) {
+             return false;
+         }
+         if (useCache) {
+             texture->resourcePriv().setUniqueKey(maskKey);
+         }
+    }
+
+    GrSWMaskHelper::DrawToTargetWithShapeMask(texture.get(), args.fDrawContext, *args.fPaint,
                                               *args.fUserStencilSettings,
                                               *args.fClip, *args.fViewMatrix,
-                                              devShapeBounds);
+                                              SkIPoint {boundsForMask->fLeft, boundsForMask->fTop},
+                                              *boundsForMask);
 
     if (inverseFilled) {
         DrawAroundInvPath(args.fDrawContext, *args.fPaint, *args.fUserStencilSettings,
                           *args.fClip,
-                          *args.fViewMatrix, devClipBounds, devShapeBounds);
+                          *args.fViewMatrix, devClipBounds, unclippedDevShapeBounds);
     }
 
     return true;
diff --git a/src/gpu/GrSoftwarePathRenderer.h b/src/gpu/GrSoftwarePathRenderer.h
index 1548fa6..72d9673 100644
--- a/src/gpu/GrSoftwarePathRenderer.h
+++ b/src/gpu/GrSoftwarePathRenderer.h
@@ -18,7 +18,9 @@
  */
 class GrSoftwarePathRenderer : public GrPathRenderer {
 public:
-    GrSoftwarePathRenderer(GrTextureProvider* texProvider) : fTexProvider(texProvider) { }
+    GrSoftwarePathRenderer(GrTextureProvider* texProvider, bool allowCaching)
+            : fTexProvider(texProvider)
+            , fAllowCaching(allowCaching) {}
 private:
     static void DrawNonAARect(GrDrawContext* drawContext,
                               const GrPaint& paint,
@@ -45,6 +47,7 @@
 
 private:
     GrTextureProvider*     fTexProvider;
+    bool                   fAllowCaching;
 
     typedef GrPathRenderer INHERITED;
 };