/* * 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 "SkTypes.h" #if !defined(SK_BUILD_FOR_GOOGLE3) #include "SkRect.h" #include "SkRectPriv.h" #include "SkSGColor.h" #include "SkSGDraw.h" #include "SkSGGroup.h" #include "SkSGInvalidationController.h" #include "SkSGRect.h" #include "SkSGRenderEffect.h" #include "SkSGTransform.h" #include "SkTo.h" #include "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 (0) { 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 (0) { 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::MakeScale(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::MakeScale(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::MakeTrans(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); } } 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); } #endif // !defined(SK_BUILD_FOR_GOOGLE3)