diff options
-rw-r--r-- | libs/hwui/tests/common/TestScene.h | 3 | ||||
-rw-r--r-- | libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp | 200 | ||||
-rw-r--r-- | libs/hwui/tests/macrobench/TestSceneRunner.cpp | 73 | ||||
-rw-r--r-- | libs/hwui/tests/macrobench/main.cpp | 32 |
4 files changed, 249 insertions, 59 deletions
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h index 91022cfe734b..eaf5988dca49 100644 --- a/libs/hwui/tests/common/TestScene.h +++ b/libs/hwui/tests/common/TestScene.h @@ -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; }; diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp new file mode 100644 index 000000000000..964b8bfb6fb1 --- /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 diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp index 13ac3671c9c7..cf9b0c5536fc 100644 --- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp +++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp @@ -62,53 +62,23 @@ private: T mAverage; }; +using BenchmarkResults = std::vector<benchmark::BenchmarkReporter::Run>; + 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"}, - }; - - // 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; + 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 + } + } +} diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index 174a14080eff..acbbb9562f12 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -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,10 +349,8 @@ 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()); - } + for (auto&& test : gRunTests) { + run(test, gOpts, gBenchmarkReporter.get()); } if (gBenchmarkReporter) { @@ -358,6 +360,8 @@ int main(int argc, char* argv[]) { renderthread::RenderProxy::trimMemory(100); HardwareBitmapUploader::terminate(); - LeakChecker::checkForLeaks(); + if (gRunLeakCheck) { + LeakChecker::checkForLeaks(); + } return 0; } |