blob: 83b71a30cd97856cd67df71919501727affb0bd3 [file]
/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkTypes.h"
#if !defined(SK_BUILD_FOR_GOOGLE3)
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkRect.h"
#include "include/pathops/SkPathOps.h"
#include "include/private/base/SkTo.h"
#include "modules/sksg/include/SkSGDraw.h"
#include "modules/sksg/include/SkSGGroup.h"
#include "modules/sksg/include/SkSGInvalidationController.h"
#include "modules/sksg/include/SkSGMerge.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "modules/sksg/include/SkSGPath.h"
#include "modules/sksg/include/SkSGRect.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "modules/sksg/include/SkSGTransform.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRectPriv.h"
#include "tests/Test.h"
#include <vector>
static void check_inval(skiatest::Reporter* reporter, const sk_sp<sksg::Node>& root,
const SkRect& expected_bounds,
const SkRect& expected_inval_bounds,
const std::vector<SkRect>* expected_damage) {
sksg::InvalidationController ic;
const auto bbox = root->revalidate(&ic, SkMatrix::I());
if ((false)) {
SkDebugf("** bbox: [%f %f %f %f], ibbox: [%f %f %f %f]\n",
bbox.fLeft, bbox.fTop, bbox.fRight, bbox.fBottom,
ic.bounds().left(), ic.bounds().top(), ic.bounds().right(), ic.bounds().bottom());
}
REPORTER_ASSERT(reporter, bbox == expected_bounds);
REPORTER_ASSERT(reporter, ic.bounds() == expected_inval_bounds);
if (expected_damage) {
const auto damage_count = SkTo<size_t>(ic.end() - ic.begin());
REPORTER_ASSERT(reporter, expected_damage->size() == damage_count);
for (size_t i = 0; i < std::min(expected_damage->size(), damage_count); ++i) {
const auto r1 = (*expected_damage)[i],
r2 = ic.begin()[i];
if ((false)) {
SkDebugf("*** expected inval: [%f %f %f %f], actual: [%f %f %f %f]\n",
r1.left(), r1.top(), r1.right(), r1.bottom(),
r2.left(), r2.top(), r2.right(), r2.bottom());
}
REPORTER_ASSERT(reporter, r1 == r2);
}
}
}
struct HitTest {
const SkPoint pt;
sk_sp<sksg::RenderNode> node;
};
static void check_hittest(skiatest::Reporter* reporter, const sk_sp<sksg::RenderNode>& root,
const std::vector<HitTest>& tests) {
for (const auto& tst : tests) {
const auto* node = root->nodeAt(tst.pt);
if (node != tst.node.get()) {
SkDebugf("*** nodeAt(%f, %f) - expected %p, got %p\n",
tst.pt.x(), tst.pt.y(), tst.node.get(), node);
}
REPORTER_ASSERT(reporter, tst.node.get() == node);
}
}
static void inval_test1(skiatest::Reporter* reporter) {
auto color = sksg::Color::Make(0xff000000);
auto r1 = sksg::Rect::Make(SkRect::MakeWH(100, 100)),
r2 = sksg::Rect::Make(SkRect::MakeWH(100, 100));
auto grp = sksg::Group::Make();
auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
auto root = sksg::TransformEffect::Make(grp, matrix);
auto d1 = sksg::Draw::Make(r1, color),
d2 = sksg::Draw::Make(r2, color);
grp->addChild(d1);
grp->addChild(d2);
{
// Initial revalidation.
check_inval(reporter, root,
SkRect::MakeWH(100, 100),
SkRectPriv::MakeLargeS32(),
nullptr);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 100, 0 }, nullptr },
{{ 0, 100 }, nullptr },
{{ 0, 0 }, d2 },
{{ 99, 99 }, d2 },
});
}
{
// Move r2 to (200 100).
r2->setL(200); r2->setT(100); r2->setR(300); r2->setB(200);
std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} };
check_inval(reporter, root,
SkRect::MakeWH(300, 200),
SkRect::MakeWH(300, 200),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 100, 0 }, nullptr },
{{ 0, 100 }, nullptr },
{{ 0, 0 }, d1 },
{{ 99, 99 }, d1 },
{{ 199, 100 }, nullptr },
{{ 200, 99 }, nullptr },
{{ 300, 100 }, nullptr },
{{ 200, 200 }, nullptr },
{{ 200, 100 }, d2 },
{{ 299, 199 }, d2 },
});
}
{
// Update the common color.
color->setColor(0xffff0000);
std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} };
check_inval(reporter, root,
SkRect::MakeWH(300, 200),
SkRect::MakeWH(300, 200),
&damage);
}
{
// Shrink r1.
r1->setR(50);
std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 50, 100} };
check_inval(reporter, root,
SkRect::MakeWH(300, 200),
SkRect::MakeWH(100, 100),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 50, 0 }, nullptr },
{{ 0, 100 }, nullptr },
{{ 0, 0 }, d1 },
{{ 49, 99 }, d1 },
{{ 199, 100 }, nullptr },
{{ 200, 99 }, nullptr },
{{ 300, 100 }, nullptr },
{{ 200, 200 }, nullptr },
{{ 200, 100 }, d2 },
{{ 299, 199 }, d2 },
});
}
{
// Update transform.
matrix->setMatrix(SkMatrix::Scale(2, 2));
std::vector<SkRect> damage = { {0, 0, 300, 200}, { 0, 0, 600, 400} };
check_inval(reporter, root,
SkRect::MakeWH(600, 400),
SkRect::MakeWH(600, 400),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 25, 0 }, nullptr },
{{ 0, 50 }, nullptr },
{{ 0, 0 }, d1 },
{{ 24, 49 }, d1 },
{{ 99, 50 }, nullptr },
{{ 100, 49 }, nullptr },
{{ 150, 50 }, nullptr },
{{ 100, 100 }, nullptr },
{{ 100, 50 }, d2 },
{{ 149, 99 }, d2 },
});
}
{
// Shrink r2 under transform.
r2->setR(250);
std::vector<SkRect> damage = { {400, 200, 600, 400}, { 400, 200, 500, 400} };
check_inval(reporter, root,
SkRect::MakeWH(500, 400),
SkRect::MakeLTRB(400, 200, 600, 400),
&damage);
check_hittest(reporter, root, {
{{ -1, 0 }, nullptr },
{{ 0, -1 }, nullptr },
{{ 25, 0 }, nullptr },
{{ 0, 50 }, nullptr },
{{ 0, 0 }, d1 },
{{ 24, 49 }, d1 },
{{ 99, 50 }, nullptr },
{{ 100, 49 }, nullptr },
{{ 125, 50 }, nullptr },
{{ 100, 100 }, nullptr },
{{ 100, 50 }, d2 },
{{ 124, 99 }, d2 },
});
}
}
static void inval_test2(skiatest::Reporter* reporter) {
auto color = sksg::Color::Make(0xff000000);
auto rect = sksg::Rect::Make(SkRect::MakeWH(100, 100));
auto m1 = sksg::Matrix<SkMatrix>::Make(SkMatrix::I()),
m2 = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
auto t1 = sksg::TransformEffect::Make(sksg::Draw::Make(rect, color),
sksg::Transform::MakeConcat(m1, m2)),
t2 = sksg::TransformEffect::Make(sksg::Draw::Make(rect, color), m1);
auto root = sksg::Group::Make();
root->addChild(t1);
root->addChild(t2);
{
// Initial revalidation.
check_inval(reporter, root,
SkRect::MakeWH(100, 100),
SkRectPriv::MakeLargeS32(),
nullptr);
}
{
// Update the shared color.
color->setColor(0xffff0000);
std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 100, 100} };
check_inval(reporter, root,
SkRect::MakeWH(100, 100),
SkRect::MakeWH(100, 100),
&damage);
}
{
// Update m2.
m2->setMatrix(SkMatrix::Scale(2, 2));
std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 200, 200} };
check_inval(reporter, root,
SkRect::MakeWH(200, 200),
SkRect::MakeWH(200, 200),
&damage);
}
{
// Update shared m1.
m1->setMatrix(SkMatrix::Translate(100, 100));
std::vector<SkRect> damage = { { 0, 0, 200, 200}, // draw1 prev bounds
{ 100, 100, 300, 300}, // draw1 new bounds
{ 0, 0, 100, 100}, // draw2 prev bounds
{ 100, 100, 200, 200} }; // draw2 new bounds
check_inval(reporter, root,
SkRect::MakeLTRB(100, 100, 300, 300),
SkRect::MakeLTRB( 0, 0, 300, 300),
&damage);
}
{
// Update shared rect.
rect->setR(50);
std::vector<SkRect> damage = { { 100, 100, 300, 300}, // draw1 prev bounds
{ 100, 100, 200, 300}, // draw1 new bounds
{ 100, 100, 200, 200}, // draw2 prev bounds
{ 100, 100, 150, 200} }; // draw2 new bounds
check_inval(reporter, root,
SkRect::MakeLTRB(100, 100, 200, 300),
SkRect::MakeLTRB(100, 100, 300, 300),
&damage);
}
}
static void inval_test3(skiatest::Reporter* reporter) {
auto color1 = sksg::Color::Make(0xff000000),
color2 = sksg::Color::Make(0xff000000);
auto group = sksg::Group::Make();
group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)),
color1));
group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeXYWH(200, 0, 100, 100)),
color2));
auto filter = sksg::DropShadowImageFilter::Make();
filter->setOffset({50, 75});
auto root = sksg::ImageFilterEffect::Make(group, filter);
{
// Initial revalidation.
check_inval(reporter, root,
SkRect::MakeXYWH(0, 0, 350, 175),
SkRectPriv::MakeLargeS32(),
nullptr);
}
{
// Shadow-only.
filter->setMode(sksg::DropShadowImageFilter::Mode::kShadowOnly);
std::vector<SkRect> damage = { {0, 0, 350, 175}, { 50, 75, 350, 175} };
check_inval(reporter, root,
SkRect::MakeLTRB(50, 75, 350, 175),
SkRect::MakeLTRB(0, 0, 350, 175),
&damage);
}
{
// Content change -> single/full filter bounds inval.
color1->setColor(0xffff0000);
std::vector<SkRect> damage = { { 50, 75, 350, 175} };
check_inval(reporter, root,
SkRect::MakeLTRB(50, 75, 350, 175),
SkRect::MakeLTRB(50, 75, 350, 175),
&damage);
}
{
// Visibility change -> full inval.
group->setVisible(false);
std::vector<SkRect> damage = { { 50, 75, 350, 175} };
check_inval(reporter, root,
SkRect::MakeLTRB(50, 75, 350, 175),
SkRect::MakeLTRB(50, 75, 350, 175),
&damage);
}
}
static void inval_group_remove(skiatest::Reporter* reporter) {
auto draw = sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)),
sksg::Color::Make(SK_ColorBLACK));
auto grp = sksg::Group::Make();
// Readding the child should not trigger asserts.
grp->addChild(draw);
grp->removeChild(draw);
grp->addChild(draw);
}
DEF_TEST(SGInvalidation, reporter) {
inval_test1(reporter);
inval_test2(reporter);
inval_test3(reporter);
inval_group_remove(reporter);
}
// Helper to stringify a verb.
static const char* verb_to_string(SkPath::Verb verb) {
switch (verb) {
case SkPath::kMove_Verb:
return "Move";
case SkPath::kLine_Verb:
return "Line";
case SkPath::kQuad_Verb:
return "Quad";
case SkPath::kConic_Verb:
return "Conic";
case SkPath::kCubic_Verb:
return "Cubic";
case SkPath::kClose_Verb:
return "Close";
case SkPath::kDone_Verb:
return "Done";
}
SkUNREACHABLE;
}
static void assert_paths_equal(skiatest::Reporter* reporter, const SkPath& a, const SkPath& b) {
if (a.getFillType() != b.getFillType()) {
ERRORF(reporter,
"Paths differ in FillType. Expected %d, got %d.",
(int)a.getFillType(),
(int)b.getFillType());
a.dump();
b.dump();
return;
}
if (SkPathPriv::GetConvexity(a) != SkPathPriv::GetConvexity(b)) {
ERRORF(reporter,
"Paths differ in Convexity. Expected %d, got %d.",
(int)SkPathPriv::GetConvexity(a),
(int)SkPathPriv::GetConvexity(b));
return;
}
SkPath::RawIter iterA(a);
SkPath::RawIter iterB(b);
SkPoint ptsA[4], ptsB[4];
int verbIndex = 0;
for (;;) {
SkPath::Verb verbA = iterA.next(ptsA);
SkPath::Verb verbB = iterB.next(ptsB);
if (verbA != verbB) {
ERRORF(reporter,
"Paths differ at verb index %d. Expected %s, got %s.",
verbIndex,
verb_to_string(verbA),
verb_to_string(verbB));
a.dump();
b.dump();
return;
}
if (verbA == SkPath::kDone_Verb) {
break;
}
const int numPts = SkPathPriv::PtsInIter(verbA);
for (int i = 0; i < numPts; ++i) {
if (ptsA[i] != ptsB[i]) {
ERRORF(reporter,
"Paths differ at verb index %d (%s), point %d. "
"Expected (%f, %f), got (%f, %f).",
verbIndex,
verb_to_string(verbA),
i,
ptsA[i].fX,
ptsA[i].fY,
ptsB[i].fX,
ptsB[i].fY);
a.dump();
b.dump();
return;
}
}
if (verbA == SkPath::kConic_Verb) {
const float weightA = iterA.conicWeight();
const float weightB = iterB.conicWeight();
if (weightA != weightB) {
ERRORF(reporter,
"Paths differ at verb index %d (Conic), weight. "
"Expected %f, got %f.",
verbIndex,
weightA,
weightB);
a.dump();
b.dump();
return;
}
}
verbIndex++;
}
}
static void test_merge(skiatest::Reporter* reporter,
const char* name,
std::vector<sksg::Merge::Rec>&& recs,
const SkPath& expected) {
skiatest::ReporterContext rc(reporter, name);
auto merge = sksg::Merge::Make(std::move(recs));
sksg::InvalidationController ic;
merge->revalidate(&ic, SkMatrix::I());
assert_paths_equal(reporter, merge->asPath(), expected);
}
DEF_TEST(SGMerge, reporter) {
const auto square = sksg::Rect::Make(SkRect::MakeXYWH(0, 0, 100, 100));
const auto rect = sksg::Path::Make(SkPath::Rect(SkRect::MakeXYWH(50, 50, 100, 150)));
const auto window = sksg::Path::Make(SkPath::Rect(SkRect::MakeXYWH(20, 30, 5, 60)));
{
const SkPath expected = SkPathBuilder()
.moveTo(0, 0)
.lineTo(100, 0)
.lineTo(100, 100)
.lineTo(0, 100)
.close()
.moveTo(50, 50)
.lineTo(150, 50)
.lineTo(150, 200)
.lineTo(50, 200)
.close()
.detach();
test_merge(reporter,
"Merge combines contours",
{{square, sksg::Merge::Mode::kMerge}, {rect, sksg::Merge::Mode::kMerge}},
expected);
}
{
const SkPath expected = SkPathBuilder()
.setFillType(SkPathFillType::kEvenOdd)
.moveTo(100, 0)
.lineTo(0, 0)
.lineTo(0, 100)
.lineTo(50, 100)
.lineTo(50, 200)
.lineTo(150, 200)
.lineTo(150, 50)
.lineTo(100, 50)
.lineTo(100, 0)
.close()
.detach();
test_merge(reporter,
"Union uses pathops",
{{square, sksg::Merge::Mode::kUnion}, {rect, sksg::Merge::Mode::kUnion}},
expected);
}
{
const SkPath expected = SkPathBuilder()
.setFillType(SkPathFillType::kEvenOdd)
.moveTo(50, 50)
.lineTo(100, 50)
.lineTo(100, 100)
.lineTo(50, 100)
.close()
.detach();
test_merge(reporter,
"Intersect uses pathops",
{{square, sksg::Merge::Mode::kUnion}, // first op is ignored
{rect, sksg::Merge::Mode::kIntersect}},
expected);
}
{
const SkPath expected = SkPathBuilder()
.setFillType(SkPathFillType::kEvenOdd)
.moveTo(100, 0)
.lineTo(0, 0)
.lineTo(0, 100)
.lineTo(100, 100)
.lineTo(100, 0)
.close()
.moveTo(25, 30)
.lineTo(20, 30)
.lineTo(20, 90)
.lineTo(25, 90)
.lineTo(25, 30)
.close()
.detach();
test_merge(reporter,
"Difference uses pathops",
{{square, sksg::Merge::Mode::kUnion}, // first op is ignored
{window, sksg::Merge::Mode::kDifference}},
expected);
}
}
#endif // !defined(SK_BUILD_FOR_GOOGLE3)