Merge changes I9a231625,Idaf3a905 into sc-dev

* changes:
  Avoid creating a layer for linear stretch
  Add a stretchy benchmark
This commit is contained in:
TreeHugger Robot
2021-05-11 17:50:41 +00:00
committed by Android (Google) Code Review
7 changed files with 257 additions and 66 deletions

View File

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

View File

@@ -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};

View File

@@ -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()));

View File

@@ -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;
};

View 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; }
};

View File

@@ -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
}
}
}

View File

@@ -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;
}