Add a stretchy benchmark

Test: this
Bug: 187718492
Change-Id: Idaf3a90567a4f90c7089159b496ddf4aac24bf05
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index 91022cf..eaf5988 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -35,7 +35,8 @@
 class TestScene {
 public:
     struct Options {
-        int count = 0;
+        int frameCount = 150;
+        int repeatCount = 1;
         int reportFrametimeWeight = 0;
         bool renderOffscreen = true;
     };
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
new file mode 100644
index 0000000..964b8bf
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2021 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 <SkFont.h>
+#include <cstdio>
+#include "TestSceneBase.h"
+#include "hwui/Paint.h"
+#include "tests/common/TestUtils.h"
+
+class StretchyListViewAnimation;
+class StretchyListViewHolePunch;
+class StretchyLinearListView;
+class StretchyLinearListViewHolePunch;
+
+static TestScene::Registrar _StretchyListViewAnimation(TestScene::Info{
+        "stretchylistview",
+        "A mock ListView of scrolling content that's stretching. Doesn't re-bind/re-record views "
+        "as they are recycled, so won't upload much content (either glyphs, or bitmaps).",
+        TestScene::simpleCreateScene<StretchyListViewAnimation>});
+
+static TestScene::Registrar _StretchyListViewHolePunch(TestScene::Info{
+        "stretchylistview_holepunch",
+        "A mock ListView of scrolling content that's stretching. Includes a hole punch",
+        TestScene::simpleCreateScene<StretchyListViewHolePunch>});
+
+static TestScene::Registrar _StretchyLinearListView(TestScene::Info{
+        "stretchylistview_linear",
+        "A mock ListView of scrolling content that's stretching using a linear stretch effect.",
+        TestScene::simpleCreateScene<StretchyLinearListView>});
+
+static TestScene::Registrar _StretchyLinearListViewHolePunch(TestScene::Info{
+        "stretchylistview_linear_holepunch",
+        "A mock ListView of scrolling content that's stretching using a linear stretch effect. "
+        "Includes a hole punch",
+        TestScene::simpleCreateScene<StretchyLinearListViewHolePunch>});
+
+class StretchyListViewAnimation : public TestScene {
+protected:
+    virtual StretchEffectBehavior stretchBehavior() { return StretchEffectBehavior::Shader; }
+    virtual bool haveHolePunch() { return false; }
+
+private:
+    int mItemHeight;
+    int mItemSpacing;
+    int mItemWidth;
+    int mItemLeft;
+    sp<RenderNode> mListView;
+    std::vector<sp<RenderNode> > mListItems;
+
+    sk_sp<Bitmap> createRandomCharIcon(int cardHeight) {
+        SkBitmap skBitmap;
+        int size = cardHeight - (dp(10) * 2);
+        sk_sp<Bitmap> bitmap(TestUtils::createBitmap(size, size, &skBitmap));
+        SkCanvas canvas(skBitmap);
+        canvas.clear(0);
+
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        SkColor randomColor = BrightColors[rand() % BrightColorsCount];
+        paint.setColor(randomColor);
+        canvas.drawCircle(size / 2, size / 2, size / 2, paint);
+
+        bool bgDark =
+                SkColorGetR(randomColor) + SkColorGetG(randomColor) + SkColorGetB(randomColor) <
+                128 * 3;
+        paint.setColor(bgDark ? Color::White : Color::Grey_700);
+
+        SkFont font;
+        font.setSize(size / 2);
+        char charToShow = 'A' + (rand() % 26);
+        const SkPoint pos = {SkIntToScalar(size / 2),
+                             /*approximate centering*/ SkFloatToScalar(size * 0.7f)};
+        canvas.drawSimpleText(&charToShow, 1, SkTextEncoding::kUTF8, pos.fX, pos.fY, font, paint);
+        return bitmap;
+    }
+
+    static sk_sp<Bitmap> createBoxBitmap(bool filled) {
+        int size = dp(20);
+        int stroke = dp(2);
+        SkBitmap skBitmap;
+        auto bitmap = TestUtils::createBitmap(size, size, &skBitmap);
+        SkCanvas canvas(skBitmap);
+        canvas.clear(Color::Transparent);
+
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setColor(filled ? Color::Yellow_500 : Color::Grey_700);
+        paint.setStyle(filled ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style);
+        paint.setStrokeWidth(stroke);
+        canvas.drawRect(SkRect::MakeLTRB(stroke, stroke, size - stroke, size - stroke), paint);
+        return bitmap;
+    }
+
+    void createListItem(RenderProperties& props, Canvas& canvas, int cardId, int itemWidth,
+                        int itemHeight) {
+        static sk_sp<Bitmap> filledBox(createBoxBitmap(true));
+        static sk_sp<Bitmap> strokedBox(createBoxBitmap(false));
+        const bool addHolePunch = cardId == 2 && haveHolePunch();
+        // TODO: switch to using round rect clipping, once merging correctly handles that
+        Paint roundRectPaint;
+        roundRectPaint.setAntiAlias(true);
+        roundRectPaint.setColor(Color::White);
+        if (addHolePunch) {
+            // Punch a hole but then cover it up, we don't want to actually see it
+            canvas.punchHole(SkRRect::MakeRect(SkRect::MakeWH(itemWidth, itemHeight)));
+        }
+        canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
+
+        Paint textPaint;
+        textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
+        textPaint.getSkFont().setSize(dp(20));
+        textPaint.setAntiAlias(true);
+        char buf[256];
+        snprintf(buf, sizeof(buf), "This card is #%d", cardId);
+        TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25));
+        textPaint.getSkFont().setSize(dp(15));
+        if (addHolePunch) {
+            TestUtils::drawUtf8ToCanvas(&canvas, "I have a hole punch", textPaint, itemHeight,
+                                        dp(45));
+        } else {
+            TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
+                                        itemHeight, dp(45));
+        }
+
+        auto randomIcon = createRandomCharIcon(itemHeight);
+        canvas.drawBitmap(*randomIcon, dp(10), dp(10), nullptr);
+
+        auto box = rand() % 2 ? filledBox : strokedBox;
+        canvas.drawBitmap(*box, itemWidth - dp(10) - box->width(), dp(10), nullptr);
+    }
+
+    void createContent(int width, int height, Canvas& canvas) override {
+        srand(0);
+        mItemHeight = dp(60);
+        mItemSpacing = dp(16);
+        mItemWidth = std::min((height - mItemSpacing * 2), (int)dp(300));
+        mItemLeft = (width - mItemWidth) / 2;
+        int heightWithSpacing = mItemHeight + mItemSpacing;
+        for (int y = 0; y < height + (heightWithSpacing - 1); y += heightWithSpacing) {
+            int id = mListItems.size();
+            auto node = TestUtils::createNode(mItemLeft, y, mItemLeft + mItemWidth, y + mItemHeight,
+                                              [this, id](RenderProperties& props, Canvas& canvas) {
+                                                  createListItem(props, canvas, id, mItemWidth,
+                                                                 mItemHeight);
+                                              });
+            mListItems.push_back(node);
+        }
+        mListView = TestUtils::createNode(0, 0, width, height,
+                                          [this](RenderProperties& props, Canvas& canvas) {
+                                              for (size_t ci = 0; ci < mListItems.size(); ci++) {
+                                                  canvas.drawRenderNode(mListItems[ci].get());
+                                              }
+                                          });
+
+        canvas.drawColor(Color::Grey_500, SkBlendMode::kSrcOver);
+        canvas.drawRenderNode(mListView.get());
+    }
+
+    void doFrame(int frameNr) override {
+        if (frameNr == 0) {
+            Properties::stretchEffectBehavior = stretchBehavior();
+        }
+        auto& props = mListView->mutateStagingProperties();
+        auto& stretch = props.mutateLayerProperties().mutableStretchEffect();
+        stretch.setEmpty();
+        frameNr = frameNr % 150;
+        // Animate from 0f to .1f
+        const float sY = (frameNr > 75 ? 150 - frameNr : frameNr) / 1500.f;
+        stretch.mergeWith({{.fX = 0, .fY = sY},
+                           static_cast<float>(props.getWidth()),
+                           static_cast<float>(props.getHeight())});
+        mListView->setPropertyFieldsDirty(RenderNode::GENERIC);
+    }
+};
+
+class StretchyListViewHolePunch : public StretchyListViewAnimation {
+    bool haveHolePunch() override { return true; }
+};
+
+class StretchyLinearListView : public StretchyListViewAnimation {
+    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::LinearScale; }
+};
+
+class StretchyLinearListViewHolePunch : public StretchyListViewAnimation {
+    StretchEffectBehavior stretchBehavior() override { return StretchEffectBehavior::LinearScale; }
+    bool haveHolePunch() override { return true; }
+};
\ No newline at end of file