Avoid performing the pathOp for clipped shadows if possible.

Bug: 64487466
Test: SystemUiJankTests#testRecentAppsFling
Change-Id: I2ca96bd6adba299cd31e12f005b2529c559740d2
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 61b3876..36a7475 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -32,6 +32,7 @@
 #include "protos/hwui.pb.h"
 #include "protos/ProtoHelpers.h"
 
+#include <SkPathOps.h>
 #include <algorithm>
 #include <sstream>
 #include <string>
@@ -555,5 +556,23 @@
     }
 }
 
+const SkPath* RenderNode::getClippedOutline(const SkRect& clipRect) const {
+    const SkPath* outlinePath = properties().getOutline().getPath();
+    const uint32_t outlineID = outlinePath->getGenerationID();
+
+    if (outlineID != mClippedOutlineCache.outlineID || clipRect != mClippedOutlineCache.clipRect) {
+        // update the cache keys
+        mClippedOutlineCache.outlineID = outlineID;
+        mClippedOutlineCache.clipRect = clipRect;
+
+        // update the cache value by recomputing a new path
+        SkPath clipPath;
+        clipPath.addRect(clipRect);
+        Op(*outlinePath, clipPath, kIntersect_SkPathOp, &mClippedOutlineCache.clippedOutline);
+
+    }
+    return &mClippedOutlineCache.clippedOutline;
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index c4ae82a..89e022f 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -365,6 +365,17 @@
         return mSkiaLayer.get();
     }
 
+    /**
+     * Returns the path that represents the outline of RenderNode intersected with
+     * the provided rect.  This call will internally cache the resulting path in
+     * order to potentially return that path for subsequent calls to this method.
+     * By reusing the same path we get better performance on the GPU backends since
+     * those resources are cached in the hardware based on the path's genID.
+     *
+     * The returned path is only guaranteed to be valid until this function is called
+     * again or the RenderNode's outline is mutated.
+     */
+    const SkPath* getClippedOutline(const SkRect& clipRect) const;
 private:
     /**
      * If this RenderNode has been used in a previous frame then the SkiaDisplayList
@@ -380,6 +391,16 @@
      * when it has been set to draw as a LayerType::RenderLayer.
      */
     std::unique_ptr<skiapipeline::SkiaLayer> mSkiaLayer;
+
+    struct ClippedOutlineCache {
+        // keys
+        uint32_t outlineID = 0;
+        SkRect clipRect;
+
+        // value
+        SkPath clippedOutline;
+    };
+    mutable ClippedOutlineCache mClippedOutlineCache;
 }; // class RenderNode
 
 class MarkAndSweepRemoved : public TreeObserver {
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 374d364..c8207bc 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -163,28 +163,22 @@
     hwuiMatrix.copyTo(shadowMatrix);
     canvas->concat(shadowMatrix);
 
-    const SkPath* casterOutlinePath = casterProperties.getOutline().getPath();
-    // holds temporary SkPath to store the result of intersections
-    SkPath tmpPath;
-    const SkPath* casterPath = casterOutlinePath;
+    // default the shadow-casting path to the outline of the caster
+    const SkPath* casterPath = casterProperties.getOutline().getPath();
 
-    // TODO: In to following course of code that calculates the final shape, is there an optimal
-    //       of doing the Op calculations?
+    // intersect the shadow-casting path with the clipBounds, if present
+    if (clippedToBounds && !casterClipRect.contains(casterPath->getBounds())) {
+        casterPath = caster->getRenderNode()->getClippedOutline(casterClipRect);
+    }
+
     // intersect the shadow-casting path with the reveal, if present
+    SkPath tmpPath; // holds temporary SkPath to store the result of intersections
     if (revealClipPath) {
         Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, &tmpPath);
         tmpPath.setIsVolatile(true);
         casterPath = &tmpPath;
     }
 
-    // intersect the shadow-casting path with the clipBounds, if present
-    if (clippedToBounds) {
-        SkPath clipBoundsPath;
-        clipBoundsPath.addRect(casterClipRect);
-        Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, &tmpPath);
-        tmpPath.setIsVolatile(true);
-        casterPath = &tmpPath;
-    }
     const Vector3 lightPos = SkiaPipeline::getLightCenter();
     SkPoint3 skiaLightPos = SkPoint3::Make(lightPos.x, lightPos.y, lightPos.z);
     SkPoint3 zParams;