Move clip optimizations into GrFillRRectOp

Adds a new clipToShape() virtual on GrDrawOp and implements it with
GrFillRRectOp. GrClipStack now calls this method before attempting to
clip by any other means.

Since clips are often round rects, and a round rect/round rect
intersection can itself be round rect, this allows GrFillRRectOp to
bypass clipping entirely in many cases.

Bug: chromium:928984
Change-Id: Ic19b3f481ee489e3ca85817ba11d132172089a11
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/426297
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/ops/GrFillRRectOp.cpp b/src/gpu/ops/GrFillRRectOp.cpp
index 06c8368..10c9c2a 100644
--- a/src/gpu/ops/GrFillRRectOp.cpp
+++ b/src/gpu/ops/GrFillRRectOp.cpp
@@ -18,6 +18,7 @@
 #include "src/gpu/GrResourceProvider.h"
 #include "src/gpu/GrVertexWriter.h"
 #include "src/gpu/GrVx.h"
+#include "src/gpu/geometry/GrShape.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 #include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
 #include "src/gpu/glsl/GrGLSLVarying.h"
@@ -46,6 +47,9 @@
 
     FixedFunctionFlags fixedFunctionFlags() const final { return fHelper.fixedFunctionFlags(); }
 
+    ClipResult clipToShape(GrSurfaceDrawContext*, SkClipOp, const SkMatrix& clipMatrix,
+                           const GrShape&, GrAA) override;
+
     GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) final;
     CombineResult onCombineIfPossible(GrOp*, SkArenaAlloc*, const GrCaps&) final;
 
@@ -201,6 +205,88 @@
                     GrOp::IsHairline::kNo);
 }
 
+GrDrawOp::ClipResult FillRRectOp::clipToShape(GrSurfaceDrawContext* sdc, SkClipOp clipOp,
+                                              const SkMatrix& clipMatrix, const GrShape& shape,
+                                              GrAA aa) {
+    SkASSERT(fInstanceCount == 1);  // This needs to be called before combining.
+    SkASSERT(fHeadInstance->fNext == nullptr);
+
+    if ((shape.isRect() || shape.isRRect()) &&
+        clipOp == SkClipOp::kIntersect &&
+        (aa == GrAA::kNo) == (fProcessorFlags & ProcessorFlags::kFakeNonAA)) {
+        // The clip shape is a round rect. Attempt to map it to a round rect in "viewMatrix" space.
+        SkRRect clipRRect;
+        if (clipMatrix == fHeadInstance->fViewMatrix) {
+            if (shape.isRect()) {
+                clipRRect.setRect(shape.rect());
+            } else {
+                clipRRect = shape.rrect();
+            }
+        } else {
+            // Find a matrix that maps from "clipMatrix" space to "viewMatrix" space.
+            SkASSERT(!fHeadInstance->fViewMatrix.hasPerspective());
+            if (clipMatrix.hasPerspective()) {
+                return ClipResult::kFail;
+            }
+            SkMatrix clipToView;
+            if (!fHeadInstance->fViewMatrix.invert(&clipToView)) {
+                return ClipResult::kClippedOut;
+            }
+            clipToView.preConcat(clipMatrix);
+            SkASSERT(!clipToView.hasPerspective());
+            if (!SkScalarNearlyZero(clipToView.getSkewX()) ||
+                !SkScalarNearlyZero(clipToView.getSkewY())) {
+                // A rect in "clipMatrix" space is not a rect in "viewMatrix" space.
+                return ClipResult::kFail;
+            }
+            clipToView.setSkewX(0);
+            clipToView.setSkewY(0);
+            SkASSERT(clipToView.rectStaysRect());
+
+            if (shape.isRect()) {
+                clipRRect.setRect(clipToView.mapRect(shape.rect()));
+            } else {
+                if (!shape.rrect().transform(clipToView, &clipRRect)) {
+                    // Transforming the rrect failed. This shouldn't generally happen except in
+                    // cases of fp32 overflow.
+                    return ClipResult::kFail;
+                }
+            }
+        }
+
+        // Intersect our round rect with the clip shape.
+        SkRRect isectRRect;
+        if (fHeadInstance->fRRect.isRect() && clipRRect.isRect()) {
+            SkRect isectRect;
+            if (!isectRect.intersect(fHeadInstance->fRRect.rect(), clipRRect.rect())) {
+                return ClipResult::kClippedOut;
+            }
+            isectRRect.setRect(isectRect);
+        } else {
+            isectRRect = SkRRectPriv::ConservativeIntersect(fHeadInstance->fRRect, clipRRect);
+            if (isectRRect.isEmpty()) {
+                // The round rects did not intersect at all or the intersection was too complicated
+                // to compute quickly.
+                return ClipResult::kFail;
+            }
+        }
+
+        // Update the local rect.
+        auto rect = skvx::bit_pun<grvx::float4>(fHeadInstance->fRRect.rect());
+        auto local = skvx::bit_pun<grvx::float4>(fHeadInstance->fLocalRect);
+        auto isect = skvx::bit_pun<grvx::float4>(isectRRect.rect());
+        auto rectToLocalSize = (local - skvx::shuffle<2,3,0,1>(local)) /
+                               (rect - skvx::shuffle<2,3,0,1>(rect));
+        fHeadInstance->fLocalRect = skvx::bit_pun<SkRect>((isect - rect) * rectToLocalSize + local);
+
+        // Update the round rect.
+        fHeadInstance->fRRect = isectRRect;
+        return ClipResult::kClippedGeometrically;
+    }
+
+    return ClipResult::kFail;
+}
+
 GrProcessorSet::Analysis FillRRectOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
                                                GrClampType clampType) {
     SkASSERT(fInstanceCount == 1);