47cc769118
The initial intent is to use GrShape to simplify the mask blur code paths. However, I also want to use this to explore a more unified drawing code flow for different geometry types. The goal is to have a single representation for geometries+styling that attempts to always keep the geometry in the simplest form (e.g. preferring rrects to paths). It also allows for converting styling information into modified geometry and for computing consistent keys. BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1822723003 Committed: https://skia.googlesource.com/skia/+/c885dacfe4625af8b0e2e5c6e8a8ae8dc2d620a8 Committed: https://skia.googlesource.com/skia/+/824e58fc6df8fc149d9675f577f7deeaba698b09 Review URL: https://codereview.chromium.org/1822723003
317 lines
12 KiB
C++
317 lines
12 KiB
C++
/*
|
|
* Copyright 2016 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include <initializer_list>
|
|
#include <functional>
|
|
#include "Test.h"
|
|
#if SK_SUPPORT_GPU
|
|
#include "GrShape.h"
|
|
#include "SkPath.h"
|
|
#include "SkDashPathEffect.h"
|
|
|
|
namespace {
|
|
class TestCase {
|
|
public:
|
|
TestCase(const SkRRect& rrect, const SkPaint& paint) : fBase(rrect, paint) {
|
|
this->init();
|
|
}
|
|
|
|
struct SelfExpectations {
|
|
bool fPEHasEffect;
|
|
bool fPEHasValidKey;
|
|
bool fStrokeApplies;
|
|
};
|
|
|
|
void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
|
|
|
|
enum ComparisonExpecation {
|
|
kAllDifferent_ComparisonExpecation,
|
|
kSameUpToPE_ComparisonExpecation,
|
|
kSameUpToStroke_ComparisonExpecation,
|
|
kAllSame_ComparisonExpecation,
|
|
};
|
|
|
|
void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
|
|
|
|
private:
|
|
void init() {
|
|
fAppliedPE = fBase.applyPathEffect();
|
|
fAppliedPEThenStroke = fAppliedPE.applyFullStyle();
|
|
fAppliedFull = fBase.applyFullStyle();
|
|
|
|
fBaseKeyIsValid = MakeKey(&fBaseKey, fBase);
|
|
fAppliedPEKeyIsValid = MakeKey(&fAppliedPEKey, fAppliedPE);
|
|
fAppliedPEThenStrokeKeyIsValid = MakeKey(&fAppliedPEThenStrokeKey, fAppliedPEThenStroke);
|
|
fAppliedFullKeyIsValid = MakeKey(&fAppliedFullKey, fAppliedFull);
|
|
}
|
|
|
|
using Key = SkTArray<uint32_t>;
|
|
|
|
static bool MakeKey(Key* key, const GrShape& shape) {
|
|
int size = shape.unstyledKeySize();
|
|
if (size <= 0) {
|
|
return false;
|
|
}
|
|
key->reset(size);
|
|
shape.writeUnstyledKey(key->begin());
|
|
return true;
|
|
}
|
|
|
|
GrShape fBase;
|
|
GrShape fAppliedPE;
|
|
GrShape fAppliedPEThenStroke;
|
|
GrShape fAppliedFull;
|
|
|
|
Key fBaseKey;
|
|
Key fAppliedPEKey;
|
|
Key fAppliedPEThenStrokeKey;
|
|
Key fAppliedFullKey;
|
|
|
|
bool fBaseKeyIsValid;
|
|
bool fAppliedPEKeyIsValid;
|
|
bool fAppliedPEThenStrokeKeyIsValid;
|
|
bool fAppliedFullKeyIsValid;
|
|
};
|
|
|
|
void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
|
|
// Applying the path effect and then the stroke should always be the same as applying
|
|
// both in one go.
|
|
REPORTER_ASSERT(reporter, fAppliedPEThenStrokeKey == fAppliedFullKey);
|
|
// The base's key should always be valid (unless the path is volatile)
|
|
REPORTER_ASSERT(reporter, fBaseKeyIsValid);
|
|
if (expectations.fPEHasEffect) {
|
|
REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
|
|
REPORTER_ASSERT(reporter, expectations.fPEHasEffect == fAppliedPEKeyIsValid);
|
|
REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
|
|
REPORTER_ASSERT(reporter, expectations.fPEHasEffect == fAppliedFullKeyIsValid);
|
|
if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
|
|
REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
|
|
REPORTER_ASSERT(reporter, expectations.fPEHasEffect == fAppliedFullKeyIsValid);
|
|
}
|
|
} else {
|
|
REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
|
|
if (expectations.fStrokeApplies) {
|
|
REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
|
|
} else {
|
|
REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestCase::compare(skiatest::Reporter* reporter, const TestCase& that,
|
|
ComparisonExpecation expectation) const {
|
|
switch (expectation) {
|
|
case kAllDifferent_ComparisonExpecation:
|
|
REPORTER_ASSERT(reporter, fBaseKey != that.fBaseKey);
|
|
REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey);
|
|
REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey);
|
|
break;
|
|
case kSameUpToPE_ComparisonExpecation:
|
|
REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey);
|
|
REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey);
|
|
REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey);
|
|
break;
|
|
case kSameUpToStroke_ComparisonExpecation:
|
|
REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey);
|
|
REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey);
|
|
REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey);
|
|
break;
|
|
case kAllSame_ComparisonExpecation:
|
|
REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey);
|
|
REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey);
|
|
REPORTER_ASSERT(reporter, fAppliedFullKey == that.fAppliedFullKey);
|
|
break;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
static sk_sp<SkPathEffect> make_dash() {
|
|
static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
|
|
static const SkScalar kPhase = 0.75;
|
|
return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
|
|
}
|
|
|
|
static sk_sp<SkPathEffect> make_null_dash() {
|
|
static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
|
|
return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
|
|
}
|
|
|
|
static void test_basic(skiatest::Reporter* reporter, const SkRRect& rrect) {
|
|
sk_sp<SkPathEffect> dashPE = make_dash();
|
|
|
|
TestCase::SelfExpectations expectations;
|
|
SkPaint fill;
|
|
|
|
TestCase fillCase(rrect, fill);
|
|
expectations.fPEHasEffect = false;
|
|
expectations.fPEHasValidKey = false;
|
|
expectations.fStrokeApplies = false;
|
|
fillCase.testExpectations(reporter, expectations);
|
|
// Test that another GrShape instance built from the same primitive is the same.
|
|
TestCase(rrect, fill).compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
|
|
|
|
SkPaint stroke2RoundBevel;
|
|
stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
|
|
stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
|
|
stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
|
|
stroke2RoundBevel.setStrokeWidth(2.f);
|
|
TestCase stroke2RoundBevelCase(rrect, stroke2RoundBevel);
|
|
expectations.fPEHasValidKey = true;
|
|
expectations.fPEHasEffect = false;
|
|
expectations.fStrokeApplies = true;
|
|
stroke2RoundBevelCase.testExpectations(reporter, expectations);
|
|
TestCase(rrect, stroke2RoundBevel).compare(reporter, stroke2RoundBevelCase,
|
|
TestCase::kAllSame_ComparisonExpecation);
|
|
|
|
SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
|
|
stroke2RoundBevelDash.setPathEffect(make_dash());
|
|
TestCase stroke2RoundBevelDashCase(rrect, stroke2RoundBevelDash);
|
|
expectations.fPEHasValidKey = true;
|
|
expectations.fPEHasEffect = true;
|
|
expectations.fStrokeApplies = true;
|
|
stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
|
|
TestCase(rrect, stroke2RoundBevelDash).compare(reporter, stroke2RoundBevelDashCase,
|
|
TestCase::kAllSame_ComparisonExpecation);
|
|
|
|
fillCase.compare(reporter, stroke2RoundBevelCase,
|
|
TestCase::kSameUpToStroke_ComparisonExpecation);
|
|
fillCase.compare(reporter, stroke2RoundBevelDashCase,
|
|
TestCase::kSameUpToPE_ComparisonExpecation);
|
|
stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
|
|
TestCase::kSameUpToPE_ComparisonExpecation);
|
|
}
|
|
|
|
template <typename T>
|
|
static void test_stroke_param(skiatest::Reporter* reporter, const SkRRect& rrect,
|
|
std::function<void(SkPaint*, T)> setter, T a, T b) {
|
|
// Set the stroke width so that we don't get hairline. However, call the function second so that
|
|
// it can override.
|
|
SkPaint strokeA;
|
|
strokeA.setStyle(SkPaint::kStroke_Style);
|
|
strokeA.setStrokeWidth(2.f);
|
|
setter(&strokeA, a);
|
|
SkPaint strokeB;
|
|
strokeB.setStyle(SkPaint::kStroke_Style);
|
|
strokeB.setStrokeWidth(2.f);
|
|
setter(&strokeB, b);
|
|
|
|
TestCase strokeACase(rrect, strokeA);
|
|
TestCase strokeBCase(rrect, strokeB);
|
|
strokeACase.compare(reporter, strokeBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
|
|
|
|
// Make sure stroking params don't affect fill style.
|
|
SkPaint fillA = strokeA, fillB = strokeB;
|
|
fillA.setStyle(SkPaint::kFill_Style);
|
|
fillB.setStyle(SkPaint::kFill_Style);
|
|
TestCase fillACase(rrect, fillA);
|
|
TestCase fillBCase(rrect, fillB);
|
|
fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
|
|
|
|
// Make sure just applying the dash but not stroke gives the same key for both stroking
|
|
// variations.
|
|
SkPaint dashA = strokeA, dashB = strokeB;
|
|
dashA.setPathEffect(make_dash());
|
|
dashB.setPathEffect(make_dash());
|
|
TestCase dashACase(rrect, dashA);
|
|
TestCase dashBCase(rrect, dashB);
|
|
dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
|
|
}
|
|
|
|
static void test_miter_limit(skiatest::Reporter* reporter, const SkRRect& rrect) {
|
|
// Miter limit should only matter when stroking with miter joins. It shouldn't affect other
|
|
// joins or fills.
|
|
SkPaint miterA;
|
|
miterA.setStyle(SkPaint::kStroke_Style);
|
|
miterA.setStrokeWidth(2.f);
|
|
miterA.setStrokeJoin(SkPaint::kMiter_Join);
|
|
miterA.setStrokeMiter(0.5f);
|
|
SkPaint miterB = miterA;
|
|
miterA.setStrokeMiter(0.6f);
|
|
|
|
TestCase miterACase(rrect, miterA);
|
|
TestCase miterBCase(rrect, miterB);
|
|
miterACase.compare(reporter, miterBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
|
|
|
|
SkPaint noMiterA = miterA, noMiterB = miterB;
|
|
noMiterA.setStrokeJoin(SkPaint::kRound_Join);
|
|
noMiterB.setStrokeJoin(SkPaint::kRound_Join);
|
|
TestCase noMiterACase(rrect, noMiterA);
|
|
TestCase noMiterBCase(rrect, noMiterB);
|
|
noMiterACase.compare(reporter, noMiterBCase, TestCase::kAllSame_ComparisonExpecation);
|
|
|
|
SkPaint fillA = miterA, fillB = miterB;
|
|
fillA.setStyle(SkPaint::kFill_Style);
|
|
fillB.setStyle(SkPaint::kFill_Style);
|
|
TestCase fillACase(rrect, fillA);
|
|
TestCase fillBCase(rrect, fillB);
|
|
fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
|
|
}
|
|
|
|
static void test_dash_fill(skiatest::Reporter* reporter, const SkRRect& rrect) {
|
|
// A dash with no stroke should have no effect
|
|
using DashFactoryFn = sk_sp<SkPathEffect>(*)();
|
|
for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
|
|
SkPaint dashFill;
|
|
dashFill.setPathEffect((*md)());
|
|
TestCase dashFillCase(rrect, dashFill);
|
|
|
|
TestCase fillCase(rrect, SkPaint());
|
|
dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
|
|
}
|
|
}
|
|
|
|
void test_null_dash(skiatest::Reporter* reporter, const SkRRect& rrect) {
|
|
SkPaint fill;
|
|
SkPaint stroke;
|
|
stroke.setStyle(SkPaint::kStroke_Style);
|
|
stroke.setStrokeWidth(1.f);
|
|
SkPaint dash;
|
|
dash.setStyle(SkPaint::kStroke_Style);
|
|
dash.setStrokeWidth(1.f);
|
|
dash.setPathEffect(make_dash());
|
|
SkPaint nullDash;
|
|
nullDash.setStyle(SkPaint::kStroke_Style);
|
|
nullDash.setStrokeWidth(1.f);
|
|
nullDash.setPathEffect(make_null_dash());
|
|
|
|
TestCase fillCase(rrect, fill);
|
|
TestCase strokeCase(rrect, stroke);
|
|
TestCase dashCase(rrect, dash);
|
|
TestCase nullDashCase(rrect, nullDash);
|
|
|
|
nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation);
|
|
nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation);
|
|
nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
|
|
}
|
|
|
|
DEF_TEST(GrShape, reporter) {
|
|
sk_sp<SkPathEffect> dashPE = make_dash();
|
|
|
|
for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
|
|
SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4)}) {
|
|
test_basic(reporter, rr);
|
|
test_dash_fill(reporter, rr);
|
|
test_null_dash(reporter, rr);
|
|
// Test modifying various stroke params.
|
|
test_stroke_param<SkScalar>(
|
|
reporter, rr,
|
|
[](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
|
|
SkIntToScalar(2), SkIntToScalar(4));
|
|
test_stroke_param<SkPaint::Cap>(
|
|
reporter, rr,
|
|
[](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
|
|
SkPaint::kButt_Cap, SkPaint::kRound_Cap);
|
|
test_stroke_param<SkPaint::Join>(
|
|
reporter, rr,
|
|
[](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
|
|
SkPaint::kMiter_Join, SkPaint::kRound_Join);
|
|
test_miter_limit(reporter, rr);
|
|
}
|
|
}
|
|
|
|
#endif
|