Merge changes I9a231625,Idaf3a905 into sc-dev
* changes: Avoid creating a layer for linear stretch Add a stretchy benchmark
This commit is contained in:
committed by
Android (Google) Code Review
commit
46614cfa99
@@ -553,7 +553,7 @@ public:
|
||||
bool promotedToLayer() const {
|
||||
return mLayerProperties.mType == LayerType::None && fitsOnLayer() &&
|
||||
(mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr ||
|
||||
!mLayerProperties.getStretchEffect().isEmpty() ||
|
||||
mLayerProperties.getStretchEffect().requiresLayer() ||
|
||||
(!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
|
||||
mPrimitiveFields.mHasOverlappingRendering));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Properties.h"
|
||||
#include "utils/MathUtils.h"
|
||||
|
||||
#include <SkImage.h>
|
||||
@@ -108,6 +109,11 @@ public:
|
||||
return matrix;
|
||||
}
|
||||
|
||||
bool requiresLayer() const {
|
||||
return !(isEmpty() ||
|
||||
Properties::stretchEffectBehavior == StretchEffectBehavior::LinearScale);
|
||||
}
|
||||
|
||||
private:
|
||||
static sk_sp<SkRuntimeEffect> getStretchEffect();
|
||||
mutable SkVector mStretchDirection{0, 0};
|
||||
|
||||
@@ -171,16 +171,11 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
|
||||
displayList->mProjectedOutline = nullptr;
|
||||
}
|
||||
|
||||
static bool stretchNeedsLayer(const LayerProperties& properties) {
|
||||
return Properties::stretchEffectBehavior != StretchEffectBehavior::LinearScale &&
|
||||
!properties.getStretchEffect().isEmpty();
|
||||
}
|
||||
|
||||
static bool layerNeedsPaint(const sk_sp<SkImage>& snapshotImage, const LayerProperties& properties,
|
||||
float alphaMultiplier, SkPaint* paint) {
|
||||
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
|
||||
properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
|
||||
properties.getImageFilter() != nullptr || stretchNeedsLayer(properties)) {
|
||||
properties.getImageFilter() != nullptr || properties.getStretchEffect().requiresLayer()) {
|
||||
paint->setAlpha(properties.alpha() * alphaMultiplier);
|
||||
paint->setBlendMode(properties.xferMode());
|
||||
paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
|
||||
|
||||
@@ -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
libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
Normal file
200
libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
Normal file
@@ -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; }
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user