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);
+ }});