Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8427802 authored by John Reck's avatar John Reck
Browse files

Add a stretchy benchmark

Test: this
Bug: 187718492
Change-Id: Idaf3a90567a4f90c7089159b496ddf4aac24bf05
parent b04efdff
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -35,7 +35,8 @@ namespace test {
class TestScene {
public:
    struct Options {
        int count = 0;
        int frameCount = 150;
        int repeatCount = 1;
        int reportFrametimeWeight = 0;
        bool renderOffscreen = true;
    };
+200 −0
Original line number Diff line number Diff line
/*
 * 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
+29 −44
Original line number Diff line number Diff line
@@ -62,53 +62,23 @@ private:
    T mAverage;
};

void outputBenchmarkReport(const TestScene::Info& info, const TestScene::Options& opts,
                           benchmark::BenchmarkReporter* reporter, RenderProxy* proxy,
                           double durationInS) {
    using namespace benchmark;

    struct ReportInfo {
        int percentile;
        const char* suffix;
    };

    static std::array<ReportInfo, 4> REPORTS = {
            ReportInfo{50, "_50th"}, ReportInfo{90, "_90th"}, ReportInfo{95, "_95th"},
            ReportInfo{99, "_99th"},
    };
using BenchmarkResults = std::vector<benchmark::BenchmarkReporter::Run>;

    // Although a vector is used, it must stay with only a single element
    // otherwise the BenchmarkReporter will automatically compute
    // mean and stddev which doesn't make sense for our usage
    std::vector<BenchmarkReporter::Run> reports;
    BenchmarkReporter::Run report;
void outputBenchmarkReport(const TestScene::Info& info, const TestScene::Options& opts,
                           double durationInS, int repetationIndex, BenchmarkResults* reports) {
    benchmark::BenchmarkReporter::Run report;
    report.repetitions = opts.repeatCount;
    report.repetition_index = repetationIndex;
    report.run_name.function_name = info.name;
    report.iterations = static_cast<int64_t>(opts.count);
    report.iterations = static_cast<int64_t>(opts.frameCount);
    report.real_accumulated_time = durationInS;
    report.cpu_accumulated_time = durationInS;
    report.counters["items_per_second"] = opts.count / durationInS;
    reports.push_back(report);
    reporter->ReportRuns(reports);

    // Pretend the percentiles are single-iteration runs of the test
    // If rendering offscreen skip this as it's fps that's more interesting
    // in that test case than percentiles.
    if (!opts.renderOffscreen) {
        for (auto& ri : REPORTS) {
            reports[0].run_name.function_name = info.name;
            reports[0].run_name.function_name += ri.suffix;
            durationInS = proxy->frameTimePercentile(ri.percentile) / 1000.0;
            reports[0].real_accumulated_time = durationInS;
            reports[0].cpu_accumulated_time = durationInS;
            reports[0].iterations = 1;
            reports[0].counters["items_per_second"] = 0;
            reporter->ReportRuns(reports);
        }
    }
    report.counters["items_per_second"] = opts.frameCount / durationInS;
    reports->push_back(report);
}

void run(const TestScene::Info& info, const TestScene::Options& opts,
         benchmark::BenchmarkReporter* reporter) {
static void doRun(const TestScene::Info& info, const TestScene::Options& opts, int repetitionIndex,
                  BenchmarkResults* reports) {
    Properties::forceDrawFrame = true;
    TestContext testContext;
    testContext.setRenderOffscreen(opts.renderOffscreen);
@@ -158,7 +128,7 @@ void run(const TestScene::Info& info, const TestScene::Options& opts,
    ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight);

    nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC);
    for (int i = 0; i < opts.count; i++) {
    for (int i = 0; i < opts.frameCount; i++) {
        testContext.waitForVsync();
        nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
        {
@@ -182,9 +152,24 @@ void run(const TestScene::Info& info, const TestScene::Options& opts,
    proxy->fence();
    nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC);

    if (reporter) {
        outputBenchmarkReport(info, opts, reporter, proxy.get(), (end - start) / (double)s2ns(1));
    if (reports) {
        outputBenchmarkReport(info, opts, (end - start) / (double)s2ns(1), repetitionIndex,
                              reports);
    } else {
        proxy->dumpProfileInfo(STDOUT_FILENO, DumpFlags::JankStats);
    }
}

void run(const TestScene::Info& info, const TestScene::Options& opts,
         benchmark::BenchmarkReporter* reporter) {
    BenchmarkResults results;
    for (int i = 0; i < opts.repeatCount; i++) {
        doRun(info, opts, i, reporter ? &results : nullptr);
    }
    if (reporter) {
        reporter->ReportRuns(results);
        if (results.size() > 1) {
            // TODO: Report summary
        }
    }
}
+18 −14
Original line number Diff line number Diff line
@@ -40,9 +40,9 @@ using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::test;

static int gRepeatCount = 1;
static std::vector<TestScene::Info> gRunTests;
static TestScene::Options gOpts;
static bool gRunLeakCheck = true;
std::unique_ptr<benchmark::BenchmarkReporter> gBenchmarkReporter;

void run(const TestScene::Info& info, const TestScene::Options& opts,
@@ -69,6 +69,7 @@ OPTIONS:
                       are offscreen rendered
  --benchmark_format   Set output format. Possible values are tabular, json, csv
  --renderer=TYPE      Sets the render pipeline to use. May be skiagl or skiavk
  --skip-leak-check    Skips the memory leak check
)");
}

@@ -170,6 +171,7 @@ enum {
    Onscreen,
    Offscreen,
    Renderer,
    SkipLeakCheck,
};
}

@@ -185,6 +187,7 @@ static const struct option LONG_OPTIONS[] = {
        {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
        {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
        {"renderer", required_argument, nullptr, LongOpts::Renderer},
        {"skip-leak-check", no_argument, nullptr, LongOpts::SkipLeakCheck},
        {0, 0, 0, 0}};

static const char* SHORT_OPTIONS = "c:r:h";
@@ -214,20 +217,20 @@ void parseOptions(int argc, char* argv[]) {
                break;

            case 'c':
                gOpts.count = atoi(optarg);
                if (!gOpts.count) {
                gOpts.frameCount = atoi(optarg);
                if (!gOpts.frameCount) {
                    fprintf(stderr, "Invalid frames argument '%s'\n", optarg);
                    error = true;
                }
                break;

            case 'r':
                gRepeatCount = atoi(optarg);
                if (!gRepeatCount) {
                gOpts.repeatCount = atoi(optarg);
                if (!gOpts.repeatCount) {
                    fprintf(stderr, "Invalid repeat argument '%s'\n", optarg);
                    error = true;
                } else {
                    gRepeatCount = (gRepeatCount > 0 ? gRepeatCount : INT_MAX);
                    gOpts.repeatCount = (gOpts.repeatCount > 0 ? gOpts.repeatCount : INT_MAX);
                }
                break;

@@ -283,6 +286,10 @@ void parseOptions(int argc, char* argv[]) {
                gOpts.renderOffscreen = true;
                break;

            case LongOpts::SkipLeakCheck:
                gRunLeakCheck = false;
                break;

            case 'h':
                printHelp();
                exit(EXIT_SUCCESS);
@@ -322,9 +329,6 @@ void parseOptions(int argc, char* argv[]) {
}

int main(int argc, char* argv[]) {
    // set defaults
    gOpts.count = 150;

    Typeface::setRobotoTypefaceForTest();

    parseOptions(argc, argv);
@@ -345,11 +349,9 @@ int main(int argc, char* argv[]) {
        gBenchmarkReporter->ReportContext(context);
    }

    for (int i = 0; i < gRepeatCount; i++) {
    for (auto&& test : gRunTests) {
        run(test, gOpts, gBenchmarkReporter.get());
    }
    }

    if (gBenchmarkReporter) {
        gBenchmarkReporter->Finalize();
@@ -358,6 +360,8 @@ int main(int argc, char* argv[]) {
    renderthread::RenderProxy::trimMemory(100);
    HardwareBitmapUploader::terminate();

    if (gRunLeakCheck) {
        LeakChecker::checkForLeaks();
    }
    return 0;
}