Enable path clipping for View outlines

Clipping Views to Outlines has existed for several releases,
as has providing a Path for shaping Oulines. However, using
a Path-shaped Outline to clip a View against was specifically
disabled internally, due to historical functionality limitations
(prior to enabling Skia for HWUI rendering) as well as performance
concerns.

On current (even relatively low-end) hardware, path clipping is now
sufficiently performant that we are enabling this functionality.
This functionality will be used by AndroidX APIs that enable easier
shaping via paths.

Bug: 201807515
Test: Manual testing including hwui performance tests and CTS
OutlineTest

Change-Id: Ic61d9393cb72c6ad3517954177e5037a383a0c4d
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
index 2eb2c7c..e16fd8c 100644
--- a/libs/hwui/Outline.h
+++ b/libs/hwui/Outline.h
@@ -88,14 +88,10 @@
 
     bool getShouldClip() const { return mShouldClip; }
 
-    bool willClip() const {
-        // only round rect outlines can be used for clipping
-        return mShouldClip && (mType == Type::RoundRect);
-    }
+    bool willClip() const { return mShouldClip; }
 
-    bool willRoundRectClip() const {
-        // only round rect outlines can be used for clipping
-        return willClip() && MathUtils::isPositive(mRadius);
+    bool willComplexClip() const {
+        return mShouldClip && (mType != Type::RoundRect || MathUtils::isPositive(mRadius));
     }
 
     bool getAsRoundRect(Rect* outRect, float* outRadius) const {
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index dd84396..3d0ca0a 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -137,6 +137,7 @@
     histogramGPUForEach([fd](HistogramEntry entry) {
         dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
     });
+    dprintf(fd, "\n");
 }
 
 uint32_t ProfileData::findPercentile(int percentile) const {
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index cd622eb..064ba7a 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -165,11 +165,11 @@
     bool prepareForFunctorPresence(bool willHaveFunctor, bool ancestorDictatesFunctorsNeedLayer) {
         // parent may have already dictated that a descendant layer is needed
         bool functorsNeedLayer =
-                ancestorDictatesFunctorsNeedLayer
-                || CC_UNLIKELY(isClipMayBeComplex())
+                ancestorDictatesFunctorsNeedLayer ||
+                CC_UNLIKELY(isClipMayBeComplex())
 
                 // Round rect clipping forces layer for functors
-                || CC_UNLIKELY(getOutline().willRoundRectClip()) ||
+                || CC_UNLIKELY(getOutline().willComplexClip()) ||
                 CC_UNLIKELY(getRevealClip().willClip())
 
                 // Complex matrices forces layer, due to stencil clipping
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 48145d2..507d3dc 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -88,6 +88,10 @@
         if (pendingClip) {
             canvas->clipRect(*pendingClip);
         }
+        const SkPath* path = outline.getPath();
+        if (path) {
+            canvas->clipPath(*path, SkClipOp::kIntersect, true);
+        }
         return;
     }
 
diff --git a/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
new file mode 100644
index 0000000..1e343c1
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/PathClippingAnimation.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include "TestSceneBase.h"
+
+class PathClippingAnimation : public TestScene {
+public:
+    int mSpacing, mSize;
+    bool mClip, mAnimateClip;
+    int mMaxCards;
+    std::vector<sp<RenderNode> > cards;
+
+    PathClippingAnimation(int spacing, int size, bool clip, bool animateClip, int maxCards)
+            : mSpacing(spacing)
+            , mSize(size)
+            , mClip(clip)
+            , mAnimateClip(animateClip)
+            , mMaxCards(maxCards) {}
+
+    PathClippingAnimation(int spacing, int size, bool clip, bool animateClip)
+            : PathClippingAnimation(spacing, size, clip, animateClip, INT_MAX) {}
+
+    void createContent(int width, int height, Canvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
+        canvas.enableZ(true);
+        int ci = 0;
+        int numCards = 0;
+
+        for (int x = 0; x < width; x += mSpacing) {
+            for (int y = 0; y < height; y += mSpacing) {
+                auto color = BrightColors[ci++ % BrightColorsCount];
+                auto card = TestUtils::createNode(
+                        x, y, x + mSize, y + mSize, [&](RenderProperties& props, Canvas& canvas) {
+                            canvas.drawColor(color, SkBlendMode::kSrcOver);
+                            if (mClip) {
+                                // Create circular path that rounds around the inside of all
+                                // four corners of the given square defined by mSize*mSize
+                                SkPath path = setPath(mSize);
+                                props.mutableOutline().setPath(&path, 1);
+                                props.mutableOutline().setShouldClip(true);
+                            }
+                        });
+                canvas.drawRenderNode(card.get());
+                cards.push_back(card);
+                ++numCards;
+                if (numCards >= mMaxCards) {
+                    break;
+                }
+            }
+            if (numCards >= mMaxCards) {
+                break;
+            }
+        }
+
+        canvas.enableZ(false);
+    }
+
+    SkPath setPath(int size) {
+        SkPath path;
+        path.moveTo(0, size / 2);
+        path.cubicTo(0, size * .75, size * .25, size, size / 2, size);
+        path.cubicTo(size * .75, size, size, size * .75, size, size / 2);
+        path.cubicTo(size, size * .25, size * .75, 0, size / 2, 0);
+        path.cubicTo(size / 4, 0, 0, size / 4, 0, size / 2);
+        return path;
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 50;
+        if (curFrame > 25) curFrame = 50 - curFrame;
+        for (auto& card : cards) {
+            if (mAnimateClip) {
+                SkPath path = setPath(mSize - curFrame);
+                card->mutateStagingProperties().mutableOutline().setPath(&path, 1);
+            }
+            card->mutateStagingProperties().setTranslationX(curFrame);
+            card->mutateStagingProperties().setTranslationY(curFrame);
+            card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::DISPLAY_LIST);
+        }
+    }
+};
+
+static TestScene::Registrar _PathClippingUnclipped(TestScene::Info{
+        "pathClipping-unclipped", "Multiple RenderNodes, unclipped.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(80), false, false);
+        }});
+
+static TestScene::Registrar _PathClippingUnclippedSingle(TestScene::Info{
+        "pathClipping-unclippedsingle", "A single RenderNode, unclipped.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(80), false, false, 1);
+        }});
+
+static TestScene::Registrar _PathClippingUnclippedSingleLarge(TestScene::Info{
+        "pathClipping-unclippedsinglelarge", "A single large RenderNode, unclipped.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(350), false, false, 1);
+        }});
+
+static TestScene::Registrar _PathClippingClipped80(TestScene::Info{
+        "pathClipping-clipped80", "Multiple RenderNodes, clipped by paths.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(80), true, false);
+        }});
+
+static TestScene::Registrar _PathClippingClippedSingle(TestScene::Info{
+        "pathClipping-clippedsingle", "A single RenderNode, clipped by a path.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(80), true, false, 1);
+        }});
+
+static TestScene::Registrar _PathClippingClippedSingleLarge(TestScene::Info{
+        "pathClipping-clippedsinglelarge", "A single large RenderNode, clipped by a path.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(350), true, false, 1);
+        }});
+
+static TestScene::Registrar _PathClippingAnimated(TestScene::Info{
+        "pathClipping-animated",
+        "Multiple RenderNodes, clipped by paths which are being altered every frame.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(80), true, true);
+        }});
+
+static TestScene::Registrar _PathClippingAnimatedSingle(TestScene::Info{
+        "pathClipping-animatedsingle",
+        "A single RenderNode, clipped by a path which is being altered every frame.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(80), true, true, 1);
+        }});
+
+static TestScene::Registrar _PathClippingAnimatedSingleLarge(TestScene::Info{
+        "pathClipping-animatedsinglelarge",
+        "A single large RenderNode, clipped by a path which is being altered every frame.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new PathClippingAnimation(dp(100), dp(350), true, true, 1);
+        }});
diff --git a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
index 163745b..e9f353d 100644
--- a/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/RoundRectClippingAnimation.cpp
@@ -21,14 +21,17 @@
 class RoundRectClippingAnimation : public TestScene {
 public:
     int mSpacing, mSize;
+    int mMaxCards;
 
-    RoundRectClippingAnimation(int spacing, int size) : mSpacing(spacing), mSize(size) {}
+    RoundRectClippingAnimation(int spacing, int size, int maxCards = INT_MAX)
+            : mSpacing(spacing), mSize(size), mMaxCards(maxCards) {}
 
     std::vector<sp<RenderNode> > cards;
     void createContent(int width, int height, Canvas& canvas) override {
         canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver);
         canvas.enableZ(true);
         int ci = 0;
+        int numCards = 0;
 
         for (int x = 0; x < width; x += mSpacing) {
             for (int y = 0; y < height; y += mSpacing) {
@@ -42,6 +45,13 @@
                         });
                 canvas.drawRenderNode(card.get());
                 cards.push_back(card);
+                ++numCards;
+                if (numCards >= mMaxCards) {
+                    break;
+                }
+            }
+            if (numCards >= mMaxCards) {
+                break;
             }
         }
 
@@ -71,3 +81,22 @@
         [](const TestScene::Options&) -> test::TestScene* {
             return new RoundRectClippingAnimation(dp(20), dp(20));
         }});
+
+static TestScene::Registrar _RoundRectClippingGrid(TestScene::Info{
+        "roundRectClipping-grid", "A grid of RenderNodes with round rect clipping outlines.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new RoundRectClippingAnimation(dp(100), dp(80));
+        }});
+
+static TestScene::Registrar _RoundRectClippingSingle(TestScene::Info{
+        "roundRectClipping-single", "A single RenderNodes with round rect clipping outline.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new RoundRectClippingAnimation(dp(100), dp(80), 1);
+        }});
+
+static TestScene::Registrar _RoundRectClippingSingleLarge(TestScene::Info{
+        "roundRectClipping-singlelarge",
+        "A single large RenderNodes with round rect clipping outline.",
+        [](const TestScene::Options&) -> test::TestScene* {
+            return new RoundRectClippingAnimation(dp(100), dp(350), 1);
+        }});