| Index: tests/GrShapeTest.cpp | 
| diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..62440d4ba528c7458e326d4f4158fb6a80c278cd | 
| --- /dev/null | 
| +++ b/tests/GrShapeTest.cpp | 
| @@ -0,0 +1,316 @@ | 
| +/* | 
| + * 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 | 
|  |