Commit d7159f19 authored by Xianzhu Wang's avatar Xianzhu Wang Committed by Commit Bot

[PE] Detect clip and effect node change in their transform space etc.

Previously, if a clip was between two transforms, and the clip result
was not tight, and the combination of the two transforms didn't change,
we missed raster invalidation for the changed effective clip.

Now check for change of LocalTransformSpace for clip and effect nodes.

Bug: 848782
Cq-Include-Trybots: luci.chromium.try:linux_layout_tests_slimming_paint_v2;master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: I1b60ecc2bc79cba69c667e188053b024de670361
Reviewed-on: https://chromium-review.googlesource.com/1094202
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: default avatarTien-Ren Chen <trchen@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#567045}
parent 8a4de1d2
<!DOCTYPE html>
<div style="width: 0; height: 0; transform: rotate(5deg) translateX(200px)">
<div style="width: 200px; height: 200px; overflow: hidden">
<div style="width: 400px; height: 400px; background: blue; transform: translateX(-200px)">
</div>
</div>
</div>
<!DOCTYPE html>
<div id="outer" style="width: 0; height: 0; transform: rotate(5deg) translateX(100px)">
<div id="clip" style="width: 200px; height: 200px; overflow: hidden">
<div id="inner" style="width: 400px; height: 400px; background: blue; transform: translateX(-100px)">
</div>
</div>
</div>
<script src="../../../resources/run-after-layout-and-paint.js"></script>
<script>
runAfterLayoutAndPaint(function() {
// Change both outer and inner transforms, but keep the combined transform
// unchanged. We should invalidate because the effective clip changes
// between the transforms.
outer.style.transform = "rotate(5deg) translateX(200px)";
inner.style.transform = "translateX(-200px)";
}, true);
</script>
......@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
namespace blink {
......@@ -17,10 +18,27 @@ const ClipPaintPropertyNode& ClipPaintPropertyNode::Root() {
return *root;
}
bool ClipPaintPropertyNode::Changed(
const PropertyTreeState& relative_to_state,
const TransformPaintPropertyNode* transform_not_to_check) const {
for (const auto* node = this; node && node != relative_to_state.Clip();
node = node->Parent()) {
if (node->NodeChanged())
return true;
if (node->LocalTransformSpace() != transform_not_to_check &&
node->LocalTransformSpace()->Changed(*relative_to_state.Transform()))
return true;
}
return false;
}
std::unique_ptr<JSONObject> ClipPaintPropertyNode::ToJSON() const {
auto json = JSONObject::Create();
if (Parent())
json->SetString("parent", String::Format("%p", Parent()));
if (NodeChanged())
json->SetBoolean("changed", true);
json->SetString("localTransformSpace",
String::Format("%p", state_.local_transform_space.get()));
json->SetString("rect", state_.clip_rect.ToString());
......
......@@ -17,6 +17,7 @@
namespace blink {
class GeometryMapperClipCache;
class PropertyTreeState;
// A clip rect created by a css property such as "overflow" or "clip".
// Along with a reference to the transform space the clip rect is based on,
......@@ -72,6 +73,15 @@ class PLATFORM_EXPORT ClipPaintPropertyNode
return true;
}
// Checks if the accumulated clip from |this| to |relative_to_state.Clip()|
// has changed in the space of |relative_to_state.Transform()|. We check for
// changes of not only clip nodes, but also LocalTransformSpace relative to
// |relative_to_state.Transform()| of the clip nodes. |transform_not_to_check|
// specifies a transform node that the caller has checked or will check its
// change in other ways and this function should treat it as unchanged.
bool Changed(const PropertyTreeState& relative_to_state,
const TransformPaintPropertyNode* transform_not_to_check) const;
bool EqualIgnoringHitTestRects(const ClipPaintPropertyNode* parent,
const State& state) const {
return parent == Parent() && state_.EqualIgnoringHitTestRects(state);
......
......@@ -4,6 +4,8 @@
#include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h"
#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
namespace blink {
const EffectPaintPropertyNode& EffectPaintPropertyNode::Root() {
......@@ -22,10 +24,30 @@ FloatRect EffectPaintPropertyNode::MapRect(const FloatRect& input_rect) const {
return result;
}
bool EffectPaintPropertyNode::Changed(
const PropertyTreeState& relative_to_state,
const TransformPaintPropertyNode* transform_not_to_check) const {
for (const auto* node = this; node && node != relative_to_state.Effect();
node = node->Parent()) {
if (node->NodeChanged())
return true;
if (node->HasFilterThatMovesPixels() &&
node->LocalTransformSpace() != transform_not_to_check &&
node->LocalTransformSpace()->Changed(*relative_to_state.Transform()))
return true;
// We don't check for change of OutputClip here to avoid N^3 complexity.
// The caller should check for clip change in other ways.
}
return false;
}
std::unique_ptr<JSONObject> EffectPaintPropertyNode::ToJSON() const {
auto json = JSONObject::Create();
if (Parent())
json->SetString("parent", String::Format("%p", Parent()));
if (NodeChanged())
json->SetBoolean("changed", true);
json->SetString("localTransformSpace",
String::Format("%p", state_.local_transform_space.get()));
json->SetString("outputClip", String::Format("%p", state_.output_clip.get()));
......
......@@ -16,6 +16,8 @@
namespace blink {
class PropertyTreeState;
// Effect nodes are abstraction of isolated groups, along with optional effects
// that can be applied to the composited output of the group.
//
......@@ -82,6 +84,17 @@ class PLATFORM_EXPORT EffectPaintPropertyNode
return true;
}
// Checks if the accumulated effect from |this| to |relative_to_state
// .Effect()| has changed in the space of |relative_to_state.Transform()|.
// We check for changes of not only effect nodes, but also LocalTransformSpace
// relative to |relative_to_state.Transform()| of the effect nodes having
// filters that move pixels. Change of OutputClip is not checked and the
// caller should check in other ways. |transform_not_to_check| specifies the
// transform node that the caller has checked or will check its change in
// other ways and this function should treat it as unchanged.
bool Changed(const PropertyTreeState& relative_to_state,
const TransformPaintPropertyNode* transform_not_to_check) const;
const TransformPaintPropertyNode* LocalTransformSpace() const {
return state_.local_transform_space.get();
}
......
......@@ -69,30 +69,6 @@ class PaintPropertyNode : public RefCounted<NodeType> {
return true;
}
// TODO(wangxianzhu): Changed() and ClearChangedToRoot() are inefficient
// due to the tree walks. Optimize this if this affects overall performance.
// Returns true if any node (excluding the lowest common ancestor of
// |relative_to_node| and |this|) is marked changed along the shortest path
// from |this| to |relative_to_node|.
bool Changed(const NodeType& relative_to_node) const {
if (this == &relative_to_node)
return false;
bool changed = false;
for (const auto* n = this; n; n = n->Parent()) {
if (n == &relative_to_node)
return changed;
if (n->changed_)
changed = true;
}
// We reach here if |relative_to_node| is not an ancestor of |this|.
const auto& lca = LowestCommonAncestor(static_cast<const NodeType&>(*this),
relative_to_node);
return Changed(lca) || relative_to_node.Changed(lca);
}
void ClearChangedToRoot() const {
for (auto* n = this; n; n = n->Parent())
n->changed_ = false;
......@@ -129,8 +105,11 @@ class PaintPropertyNode : public RefCounted<NodeType> {
}
void SetChanged() { changed_ = true; }
bool NodeChanged() const { return changed_; }
private:
friend class PaintPropertyNodeTest;
scoped_refptr<const NodeType> parent_;
mutable bool changed_ = true;
......
......@@ -5,145 +5,397 @@
#include "third_party/blink/renderer/platform/graphics/paint/paint_property_node.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h"
#include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h"
#include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
#include "third_party/blink/renderer/platform/testing/paint_property_test_helpers.h"
namespace blink {
class PaintPropertyNodeTest : public testing::Test {
protected:
void SetUp() override {
root = &ClipPaintPropertyNode::Root();
node = CreateClip(*root, nullptr, FloatRoundedRect());
child1 = CreateClip(*node, nullptr, FloatRoundedRect());
child2 = CreateClip(*node, nullptr, FloatRoundedRect());
grandchild1 = CreateClip(*child1, nullptr, FloatRoundedRect());
grandchild2 = CreateClip(*child2, nullptr, FloatRoundedRect());
template <typename NodeType>
struct Tree {
const NodeType* root;
scoped_refptr<NodeType> ancestor;
scoped_refptr<NodeType> child1;
scoped_refptr<NodeType> child2;
scoped_refptr<NodeType> grandchild1;
scoped_refptr<NodeType> grandchild2;
};
void SetUp() override {
// root
// |
// node
// ancestor
// / \
// child1 child2
// | |
// grandchild1 grandchild2
transform.root = &TransformPaintPropertyNode::Root();
transform.ancestor =
CreateTransform(*transform.root, TransformationMatrix());
transform.child1 =
CreateTransform(*transform.ancestor, TransformationMatrix());
transform.child2 =
CreateTransform(*transform.ancestor, TransformationMatrix());
transform.grandchild1 =
CreateTransform(*transform.child1, TransformationMatrix());
transform.grandchild2 =
CreateTransform(*transform.child2, TransformationMatrix());
clip.root = &ClipPaintPropertyNode::Root();
clip.ancestor =
CreateClip(*clip.root, transform.ancestor.get(), FloatRoundedRect());
clip.child1 =
CreateClip(*clip.ancestor, transform.child1.get(), FloatRoundedRect());
clip.child2 =
CreateClip(*clip.ancestor, transform.child2.get(), FloatRoundedRect());
clip.grandchild1 = CreateClip(*clip.child1, transform.grandchild1.get(),
FloatRoundedRect());
clip.grandchild2 = CreateClip(*clip.child2, transform.grandchild2.get(),
FloatRoundedRect());
effect.root = &EffectPaintPropertyNode::Root();
effect.ancestor = CreateOpacityEffect(
*effect.root, transform.ancestor.get(), clip.ancestor.get(), 0.5);
effect.child1 = CreateOpacityEffect(
*effect.ancestor, transform.child1.get(), clip.child1.get(), 0.5);
effect.child2 = CreateOpacityEffect(
*effect.ancestor, transform.child2.get(), clip.child2.get(), 0.5);
effect.grandchild1 =
CreateOpacityEffect(*effect.child1, transform.grandchild1.get(),
clip.grandchild1.get(), 0.5);
effect.grandchild2 =
CreateOpacityEffect(*effect.child2, transform.grandchild2.get(),
clip.grandchild2.get(), 0.5);
}
template <typename NodeType>
void ResetAllChanged(Tree<NodeType>& tree) {
tree.grandchild1->ClearChangedToRoot();
tree.grandchild2->ClearChangedToRoot();
}
void ResetAllChanged() {
grandchild1->ClearChangedToRoot();
grandchild2->ClearChangedToRoot();
ResetAllChanged(transform);
ResetAllChanged(clip);
ResetAllChanged(effect);
}
static void Update(ClipPaintPropertyNode& node,
const ClipPaintPropertyNode& new_parent,
const FloatRoundedRect& new_clip_rect) {
node.Update(new_parent,
ClipPaintPropertyNode::State{nullptr, new_clip_rect});
template <typename NodeType>
void ExpectInitialState(const Tree<NodeType>& tree) {
EXPECT_TRUE(tree.root->NodeChanged());
EXPECT_TRUE(tree.ancestor->NodeChanged());
EXPECT_TRUE(tree.child1->NodeChanged());
EXPECT_TRUE(tree.child2->NodeChanged());
EXPECT_TRUE(tree.grandchild1->NodeChanged());
EXPECT_TRUE(tree.grandchild2->NodeChanged());
}
void ExpectInitialState() {
EXPECT_FALSE(root->Changed(*root));
EXPECT_TRUE(node->Changed(*root));
EXPECT_TRUE(child1->Changed(*node));
EXPECT_TRUE(child2->Changed(*node));
EXPECT_TRUE(grandchild1->Changed(*child1));
EXPECT_TRUE(grandchild2->Changed(*child2));
template <typename NodeType>
void ExpectUnchangedState(const Tree<NodeType>& tree) {
EXPECT_FALSE(tree.root->NodeChanged());
EXPECT_FALSE(tree.ancestor->NodeChanged());
EXPECT_FALSE(tree.child1->NodeChanged());
EXPECT_FALSE(tree.child2->NodeChanged());
EXPECT_FALSE(tree.grandchild1->NodeChanged());
EXPECT_FALSE(tree.grandchild2->NodeChanged());
}
void ExpectUnchangedState() {
EXPECT_FALSE(root->Changed(*root));
EXPECT_FALSE(node->Changed(*root));
EXPECT_FALSE(child1->Changed(*root));
EXPECT_FALSE(child2->Changed(*root));
EXPECT_FALSE(grandchild1->Changed(*root));
EXPECT_FALSE(grandchild2->Changed(*root));
ExpectUnchangedState(transform);
ExpectUnchangedState(clip);
ExpectUnchangedState(effect);
}
const ClipPaintPropertyNode* root;
scoped_refptr<ClipPaintPropertyNode> node;
scoped_refptr<ClipPaintPropertyNode> child1;
scoped_refptr<ClipPaintPropertyNode> child2;
scoped_refptr<ClipPaintPropertyNode> grandchild1;
scoped_refptr<ClipPaintPropertyNode> grandchild2;
Tree<TransformPaintPropertyNode> transform;
Tree<ClipPaintPropertyNode> clip;
Tree<EffectPaintPropertyNode> effect;
};
#define STATE(node) \
PropertyTreeState(&*transform.node, &*clip.node, &*effect.node)
TEST_F(PaintPropertyNodeTest, LowestCommonAncestor) {
EXPECT_EQ(node, &LowestCommonAncestor(*node, *node));
EXPECT_EQ(root, &LowestCommonAncestor(*root, *root));
EXPECT_EQ(transform.ancestor,
&LowestCommonAncestor(*transform.ancestor, *transform.ancestor));
EXPECT_EQ(transform.root,
&LowestCommonAncestor(*transform.root, *transform.root));
EXPECT_EQ(node, &LowestCommonAncestor(*grandchild1, *grandchild2));
EXPECT_EQ(node, &LowestCommonAncestor(*grandchild1, *child2));
EXPECT_EQ(root, &LowestCommonAncestor(*grandchild1, *root));
EXPECT_EQ(child1, &LowestCommonAncestor(*grandchild1, *child1));
EXPECT_EQ(transform.ancestor, &LowestCommonAncestor(*transform.grandchild1,
*transform.grandchild2));
EXPECT_EQ(transform.ancestor,
&LowestCommonAncestor(*transform.grandchild1, *transform.child2));
EXPECT_EQ(transform.root,
&LowestCommonAncestor(*transform.grandchild1, *transform.root));
EXPECT_EQ(transform.child1,
&LowestCommonAncestor(*transform.grandchild1, *transform.child1));
EXPECT_EQ(node, &LowestCommonAncestor(*grandchild2, *grandchild1));
EXPECT_EQ(node, &LowestCommonAncestor(*grandchild2, *child1));
EXPECT_EQ(root, &LowestCommonAncestor(*grandchild2, *root));
EXPECT_EQ(child2, &LowestCommonAncestor(*grandchild2, *child2));
EXPECT_EQ(transform.ancestor, &LowestCommonAncestor(*transform.grandchild2,
*transform.grandchild1));
EXPECT_EQ(transform.ancestor,
&LowestCommonAncestor(*transform.grandchild2, *transform.child1));
EXPECT_EQ(transform.root,
&LowestCommonAncestor(*transform.grandchild2, *transform.root));
EXPECT_EQ(transform.child2,
&LowestCommonAncestor(*transform.grandchild2, *transform.child2));
EXPECT_EQ(node, &LowestCommonAncestor(*child1, *child2));
EXPECT_EQ(node, &LowestCommonAncestor(*child2, *child1));
EXPECT_EQ(transform.ancestor,
&LowestCommonAncestor(*transform.child1, *transform.child2));
EXPECT_EQ(transform.ancestor,
&LowestCommonAncestor(*transform.child2, *transform.child1));
}
TEST_F(PaintPropertyNodeTest, InitialStateAndReset) {
ExpectInitialState();
ExpectInitialState(transform);
ResetAllChanged(transform);
ExpectUnchangedState(transform);
}
TEST_F(PaintPropertyNodeTest, TransformChangeAncestor) {
ResetAllChanged();
ExpectUnchangedState();
transform.ancestor->Update(*transform.root,
TransformPaintPropertyNode::State{
TransformationMatrix().Translate(1, 2)});
// Test descendant->Changed(ancestor).
EXPECT_TRUE(transform.ancestor->Changed(*transform.root));
EXPECT_FALSE(transform.ancestor->Changed(*transform.ancestor));
EXPECT_TRUE(transform.child1->Changed(*transform.root));
EXPECT_FALSE(transform.child1->Changed(*transform.ancestor));
EXPECT_TRUE(transform.grandchild1->Changed(*transform.root));
EXPECT_FALSE(transform.grandchild1->Changed(*transform.ancestor));
// Test property->Changed(non-ancestor-property). Should combine the changed
// flags of the two paths to the root.
EXPECT_TRUE(transform.grandchild1->Changed(*transform.child2));
EXPECT_TRUE(transform.grandchild1->Changed(*transform.grandchild2));
ResetAllChanged();
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, ClipChangeAncestor) {
ResetAllChanged();
ExpectUnchangedState();
clip.ancestor->Update(
*clip.root, ClipPaintPropertyNode::State{transform.ancestor.get(),
FloatRoundedRect(1, 2, 3, 4)});
// Test descendant->Changed(ancestor).
EXPECT_TRUE(clip.ancestor->Changed(STATE(root), nullptr));
EXPECT_FALSE(clip.ancestor->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(clip.child1->Changed(STATE(root), nullptr));
EXPECT_FALSE(clip.child1->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(clip.grandchild1->Changed(STATE(root), nullptr));
EXPECT_FALSE(clip.grandchild1->Changed(STATE(ancestor), nullptr));
// Test property->Changed(non-ancestor-property).
// Simply walk to the root.
EXPECT_TRUE(clip.grandchild1->Changed(STATE(child2), nullptr));
EXPECT_TRUE(clip.grandchild1->Changed(STATE(grandchild2), nullptr));
ResetAllChanged();
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, EffectChangeAncestor) {
ResetAllChanged();
ExpectUnchangedState();
EffectPaintPropertyNode::State state{transform.ancestor.get(),
clip.ancestor.get()};
state.opacity = 0.9;
effect.ancestor->Update(*effect.root, std::move(state));
// Test descendant->Changed(ancestor).
EXPECT_TRUE(effect.ancestor->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.ancestor->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(effect.child1->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.child1->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(effect.grandchild1->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.grandchild1->Changed(STATE(ancestor), nullptr));
// Test property->Changed(non-ancestor-property).
// Simply walk to the root.
EXPECT_TRUE(effect.grandchild1->Changed(STATE(child2), nullptr));
EXPECT_TRUE(effect.grandchild1->Changed(STATE(grandchild2), nullptr));
ResetAllChanged();
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, ChangeNode) {
TEST_F(PaintPropertyNodeTest, TransformChangeOneChild) {
ResetAllChanged();
Update(*node, *root, FloatRoundedRect(1, 2, 3, 4));
EXPECT_TRUE(node->Changed(*root));
EXPECT_FALSE(node->Changed(*node));
EXPECT_TRUE(child1->Changed(*root));
EXPECT_FALSE(child1->Changed(*node));
EXPECT_TRUE(grandchild1->Changed(*root));
EXPECT_FALSE(grandchild1->Changed(*node));
ExpectUnchangedState();
transform.child1->Update(*transform.ancestor,
TransformPaintPropertyNode::State{
TransformationMatrix().Translate(1, 2)});
// Test descendant->Changed(ancestor).
EXPECT_FALSE(transform.ancestor->Changed(*transform.root));
EXPECT_FALSE(transform.ancestor->Changed(*transform.ancestor));
EXPECT_TRUE(transform.child1->Changed(*transform.root));
EXPECT_TRUE(transform.child1->Changed(*transform.ancestor));
EXPECT_TRUE(transform.grandchild1->Changed(*transform.ancestor));
EXPECT_FALSE(transform.grandchild1->Changed(*transform.child1));
EXPECT_FALSE(transform.child2->Changed(*transform.ancestor));
EXPECT_FALSE(transform.grandchild2->Changed(*transform.ancestor));
EXPECT_FALSE(grandchild1->Changed(*child2));
EXPECT_FALSE(grandchild1->Changed(*grandchild2));
// Test property->Changed(non-ancestor-property). Need to combine the changed
// flags of the two paths to the root.
EXPECT_TRUE(transform.child2->Changed(*transform.child1));
EXPECT_TRUE(transform.child1->Changed(*transform.child2));
EXPECT_TRUE(transform.child2->Changed(*transform.grandchild1));
EXPECT_TRUE(transform.child1->Changed(*transform.grandchild2));
EXPECT_TRUE(transform.grandchild1->Changed(*transform.child2));
EXPECT_TRUE(transform.grandchild1->Changed(*transform.grandchild2));
EXPECT_TRUE(transform.grandchild2->Changed(*transform.child1));
EXPECT_TRUE(transform.grandchild2->Changed(*transform.grandchild1));
ResetAllChanged();
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, ChangeOneChild) {
TEST_F(PaintPropertyNodeTest, ClipChangeOneChild) {
ResetAllChanged();
Update(*child1, *node, FloatRoundedRect(1, 2, 3, 4));
EXPECT_FALSE(node->Changed(*root));
EXPECT_FALSE(node->Changed(*node));
EXPECT_TRUE(child1->Changed(*root));
EXPECT_TRUE(child1->Changed(*node));
EXPECT_TRUE(grandchild1->Changed(*node));
EXPECT_FALSE(grandchild1->Changed(*child1));
EXPECT_FALSE(child2->Changed(*node));
EXPECT_FALSE(grandchild2->Changed(*node));
ExpectUnchangedState();
clip.child1->Update(
*clip.root, ClipPaintPropertyNode::State{transform.ancestor.get(),
FloatRoundedRect(1, 2, 3, 4)});
EXPECT_TRUE(child2->Changed(*child1));
EXPECT_TRUE(child1->Changed(*child2));
EXPECT_TRUE(child2->Changed(*grandchild1));
EXPECT_TRUE(child1->Changed(*grandchild2));
EXPECT_TRUE(grandchild1->Changed(*child2));
EXPECT_TRUE(grandchild1->Changed(*grandchild2));
EXPECT_TRUE(grandchild2->Changed(*child1));
EXPECT_TRUE(grandchild2->Changed(*grandchild1));
// Test descendant->Changed(ancestor).
EXPECT_FALSE(clip.ancestor->Changed(STATE(root), nullptr));
EXPECT_FALSE(clip.ancestor->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(clip.child1->Changed(STATE(root), nullptr));
EXPECT_TRUE(clip.child1->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(clip.grandchild1->Changed(STATE(ancestor), nullptr));
EXPECT_FALSE(clip.grandchild1->Changed(STATE(child1), nullptr));
EXPECT_FALSE(clip.child2->Changed(STATE(ancestor), nullptr));
EXPECT_FALSE(clip.grandchild2->Changed(STATE(ancestor), nullptr));
// Test property->Changed(non-ancestor-property).
// Simply walk to the root, regardless of relative_to_state's path.
EXPECT_FALSE(clip.child2->Changed(STATE(child1), nullptr));
EXPECT_TRUE(clip.child1->Changed(STATE(child2), nullptr));
EXPECT_FALSE(clip.child2->Changed(STATE(grandchild1), nullptr));
EXPECT_TRUE(clip.child1->Changed(STATE(grandchild2), nullptr));
EXPECT_TRUE(clip.grandchild1->Changed(STATE(child2), nullptr));
EXPECT_TRUE(clip.grandchild1->Changed(STATE(grandchild2), nullptr));
EXPECT_FALSE(clip.grandchild2->Changed(STATE(child1), nullptr));
EXPECT_FALSE(clip.grandchild2->Changed(STATE(grandchild1), nullptr));
ResetAllChanged();
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, Reparent) {
TEST_F(PaintPropertyNodeTest, EffectChangeOneChild) {
ResetAllChanged();
ExpectUnchangedState();
EffectPaintPropertyNode::State state{transform.ancestor.get(),
clip.ancestor.get()};
state.opacity = 0.9;
effect.child1->Update(*effect.root, std::move(state));
// Test descendant->Changed(ancestor).
EXPECT_FALSE(effect.ancestor->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.ancestor->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(effect.child1->Changed(STATE(root), nullptr));
EXPECT_TRUE(effect.child1->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(effect.grandchild1->Changed(STATE(ancestor), nullptr));
EXPECT_FALSE(effect.grandchild1->Changed(STATE(child1), nullptr));
EXPECT_FALSE(effect.child2->Changed(STATE(ancestor), nullptr));
EXPECT_FALSE(effect.grandchild2->Changed(STATE(ancestor), nullptr));
// Test property->Changed(non-ancestor-property).
// Simply walk to the root, regardless of relative_to_state's path.
EXPECT_FALSE(effect.child2->Changed(STATE(child1), nullptr));
EXPECT_TRUE(effect.child1->Changed(STATE(child2), nullptr));
EXPECT_FALSE(effect.child2->Changed(STATE(grandchild1), nullptr));
EXPECT_TRUE(effect.child1->Changed(STATE(grandchild2), nullptr));
EXPECT_TRUE(effect.grandchild1->Changed(STATE(child2), nullptr));
EXPECT_TRUE(effect.grandchild1->Changed(STATE(grandchild2), nullptr));
EXPECT_FALSE(effect.grandchild2->Changed(STATE(child1), nullptr));
EXPECT_FALSE(effect.grandchild2->Changed(STATE(grandchild1), nullptr));
ResetAllChanged();
Update(*child1, *child2, FloatRoundedRect(1, 2, 3, 4));
EXPECT_FALSE(node->Changed(*root));
EXPECT_TRUE(child1->Changed(*node));
EXPECT_TRUE(child1->Changed(*child2));
EXPECT_FALSE(child2->Changed(*node));
EXPECT_TRUE(grandchild1->Changed(*node));
EXPECT_FALSE(grandchild1->Changed(*child1));
EXPECT_TRUE(grandchild1->Changed(*child2));
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, TransformReparent) {
ResetAllChanged();
ExpectUnchangedState();
transform.child1->Update(*transform.child2,
TransformPaintPropertyNode::State{
TransformationMatrix().Translate(1, 2)});
EXPECT_FALSE(transform.ancestor->Changed(*transform.root));
EXPECT_TRUE(transform.child1->Changed(*transform.ancestor));
EXPECT_TRUE(transform.child1->Changed(*transform.child2));
EXPECT_FALSE(transform.child2->Changed(*transform.ancestor));
EXPECT_TRUE(transform.grandchild1->Changed(*transform.ancestor));
EXPECT_FALSE(transform.grandchild1->Changed(*transform.child1));
EXPECT_TRUE(transform.grandchild1->Changed(*transform.child2));
ResetAllChanged();
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, ClipLocalTransformSpaceChange) {
ResetAllChanged();
ExpectUnchangedState();
transform.child1->Update(*transform.ancestor,
TransformPaintPropertyNode::State{
TransformationMatrix().Translate(1, 2)});
EXPECT_FALSE(clip.ancestor->Changed(STATE(root), nullptr));
EXPECT_FALSE(clip.ancestor->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(clip.child1->Changed(STATE(root), nullptr));
EXPECT_TRUE(clip.child1->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(clip.grandchild1->Changed(STATE(ancestor), nullptr));
EXPECT_FALSE(clip.grandchild1->Changed(STATE(child1), nullptr));
// Test with transform_not_to_check.
EXPECT_FALSE(clip.child1->Changed(STATE(root), transform.child1.get()));
EXPECT_FALSE(clip.child1->Changed(STATE(ancestor), transform.child1.get()));
EXPECT_TRUE(
clip.grandchild1->Changed(STATE(ancestor), transform.child1.get()));
EXPECT_TRUE(clip.child1->Changed(STATE(root), transform.ancestor.get()));
EXPECT_TRUE(clip.child1->Changed(STATE(ancestor), transform.ancestor.get()));
EXPECT_TRUE(
clip.grandchild1->Changed(STATE(ancestor), transform.ancestor.get()));
ResetAllChanged();
ExpectUnchangedState();
}
TEST_F(PaintPropertyNodeTest, EffectLocalTransformSpaceChange) {
// Let effect.child1 have pixel-moving filter.
EffectPaintPropertyNode::State state{transform.child1.get(),
clip.child1.get()};
state.filter.AppendBlurFilter(20);
effect.child1->Update(*effect.ancestor, std::move(state));
ResetAllChanged();
ExpectUnchangedState();
transform.ancestor->Update(*transform.root,
TransformPaintPropertyNode::State{
TransformationMatrix().Translate(1, 2)});
EXPECT_FALSE(effect.ancestor->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.ancestor->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(effect.child1->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.child1->Changed(STATE(ancestor), nullptr));
EXPECT_TRUE(effect.grandchild1->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.grandchild1->Changed(STATE(ancestor), nullptr));
EXPECT_FALSE(effect.grandchild1->Changed(STATE(child1), nullptr));
// Effects without self or ancestor pixel-moving filter are not affected by
// change of LocalTransformSpace.
EXPECT_FALSE(effect.child2->Changed(STATE(root), nullptr));
EXPECT_FALSE(effect.grandchild2->Changed(STATE(root), nullptr));
// Test with transform_not_to_check.
EXPECT_FALSE(effect.child1->Changed(STATE(root), transform.child1.get()));
EXPECT_TRUE(effect.child1->Changed(STATE(root), transform.ancestor.get()));
ResetAllChanged();
ExpectUnchangedState();
......
......@@ -75,7 +75,8 @@ PaintInvalidationReason RasterInvalidator::ChunkPropertiesChanged(
// different, or the effect node's value changed between the layer state and
// the chunk state.
if (new_chunk_state.Effect() != old_chunk.effect_state ||
new_chunk_state.Effect()->Changed(*layer_state.Effect()))
new_chunk_state.Effect()->Changed(layer_state,
new_chunk_state.Transform()))
return PaintInvalidationReason::kPaintProperty;
// Check for accumulated clip rect change, if the clip rects are tight.
......@@ -94,7 +95,7 @@ PaintInvalidationReason RasterInvalidator::ChunkPropertiesChanged(
// different, or the clip node's value changed between the layer state and the
// chunk state.
if (new_chunk_state.Clip() != old_chunk.clip_state ||
new_chunk_state.Clip()->Changed(*layer_state.Clip()))
new_chunk_state.Clip()->Changed(layer_state, new_chunk_state.Transform()))
return PaintInvalidationReason::kPaintProperty;
return PaintInvalidationReason::kNone;
......
......@@ -88,6 +88,20 @@ class RasterInvalidatorTest : public testing::Test,
return PaintArtifact(DisplayItemList(0), std::move(data_));
}
void ClearGeometryMapperCache() {
GeometryMapperTransformCache::ClearCache();
GeometryMapperClipCache::ClearCache();
}
void SetUp() override { ClearGeometryMapperCache(); }
void TearDown() override { ClearGeometryMapperCache(); }
void CleanUp(PaintArtifact& artifact) {
for (auto& chunk : artifact.PaintChunks())
chunk.properties.ClearChangedToRoot();
ClearGeometryMapperCache();
}
static const Vector<RasterInvalidationInfo> TrackedRasterInvalidations(
RasterInvalidator& invalidator) {
DCHECK(invalidator.GetTracking());
......@@ -158,6 +172,7 @@ TEST_F(RasterInvalidatorTest, LayerBounds) {
DefaultPropertyTreeState());
// No raster invalidations needed for a new layer.
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
invalidator.Generate(artifact, kDefaultLayerBounds,
DefaultPropertyTreeState());
......@@ -175,6 +190,7 @@ TEST_F(RasterInvalidatorTest, LayerBounds) {
EXPECT_CHUNK_INVALIDATION_WITH_LAYER_OFFSET(
invalidations, 1, artifact.PaintChunks()[0],
PaintInvalidationReason::kPaintProperty, -new_layer_bounds.Location());
CleanUp(artifact);
}
TEST_F(RasterInvalidatorTest, ReorderChunks) {
......@@ -184,6 +200,7 @@ TEST_F(RasterInvalidatorTest, ReorderChunks) {
invalidator.Generate(artifact, kDefaultLayerBounds,
DefaultPropertyTreeState());
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Swap chunk 1 and 2. All chunks have their own local raster invalidations.
auto new_artifact = Chunk(0)
......@@ -210,6 +227,7 @@ TEST_F(RasterInvalidatorTest, ReorderChunks) {
PaintInvalidationReason::kChunkReordered);
EXPECT_CHUNK_INVALIDATION(invalidations, 7, new_artifact.PaintChunks()[2],
PaintInvalidationReason::kChunkReordered);
CleanUp(new_artifact);
}
TEST_F(RasterInvalidatorTest, ReorderChunkSubsequences) {
......@@ -219,6 +237,7 @@ TEST_F(RasterInvalidatorTest, ReorderChunkSubsequences) {
invalidator.Generate(artifact, kDefaultLayerBounds,
DefaultPropertyTreeState());
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Swap chunk (1,2) and (3,4). All chunks have their own local raster
// invalidations.
......@@ -255,6 +274,7 @@ TEST_F(RasterInvalidatorTest, ReorderChunkSubsequences) {
// it's the same as the new bounds.
EXPECT_CHUNK_INVALIDATION(invalidations, 11, new_artifact.PaintChunks()[4],
PaintInvalidationReason::kChunkReordered);
CleanUp(new_artifact);
}
TEST_F(RasterInvalidatorTest, AppearAndDisappear) {
......@@ -264,6 +284,7 @@ TEST_F(RasterInvalidatorTest, AppearAndDisappear) {
invalidator.Generate(artifact, kDefaultLayerBounds,
DefaultPropertyTreeState());
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Chunk 1 and 2 disappeared, 3 and 4 appeared. All chunks have their own
// local raster invalidations.
......@@ -287,6 +308,7 @@ TEST_F(RasterInvalidatorTest, AppearAndDisappear) {
PaintInvalidationReason::kChunkDisappeared);
EXPECT_CHUNK_INVALIDATION(invalidations, 5, artifact.PaintChunks()[2],
PaintInvalidationReason::kChunkDisappeared);
CleanUp(new_artifact);
}
TEST_F(RasterInvalidatorTest, AppearAtEnd) {
......@@ -296,6 +318,7 @@ TEST_F(RasterInvalidatorTest, AppearAtEnd) {
invalidator.Generate(artifact, kDefaultLayerBounds,
DefaultPropertyTreeState());
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
auto new_artifact = Chunk(0)
.RasterInvalidationCount(2)
......@@ -313,6 +336,7 @@ TEST_F(RasterInvalidatorTest, AppearAtEnd) {
PaintInvalidationReason::kChunkAppeared);
EXPECT_CHUNK_INVALIDATION(invalidations, 3, new_artifact.PaintChunks()[2],
PaintInvalidationReason::kChunkAppeared);
CleanUp(new_artifact);
}
TEST_F(RasterInvalidatorTest, UncacheableChunks) {
......@@ -323,6 +347,7 @@ TEST_F(RasterInvalidatorTest, UncacheableChunks) {
invalidator.Generate(artifact, kDefaultLayerBounds,
DefaultPropertyTreeState());
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
auto new_artifact = Chunk(0)
.RasterInvalidationCount(2)
......@@ -342,6 +367,7 @@ TEST_F(RasterInvalidatorTest, UncacheableChunks) {
PaintInvalidationReason::kChunkUncacheable);
EXPECT_CHUNK_INVALIDATION(invalidations, 6, artifact.PaintChunks()[1],
PaintInvalidationReason::kChunkUncacheable);
CleanUp(new_artifact);
}
// Tests the path based on ClipPaintPropertyNode::Changed().
......@@ -362,10 +388,10 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeRounded) {
.Properties(t0(), *clip2, e0())
.Build();
GeometryMapperClipCache::ClearCache();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change both clip0 and clip2.
auto new_artifact = Chunk(0)
......@@ -383,7 +409,6 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeRounded) {
ClipPaintPropertyNode::State{clip2->LocalTransformSpace(),
new_clip_rect});
GeometryMapperClipCache::ClearCache();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
const auto& invalidations = TrackedRasterInvalidations(invalidator);
ASSERT_EQ(1u, invalidations.size());
......@@ -392,7 +417,7 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeRounded) {
EXPECT_CHUNK_INVALIDATION(invalidations, 0, new_artifact.PaintChunks()[2],
PaintInvalidationReason::kPaintProperty);
invalidator.SetTracksRasterInvalidations(false);
clip2->ClearChangedToRoot();
CleanUp(new_artifact);
// Change chunk1's properties to use a different property tree state.
auto new_artifact1 = Chunk(0)
......@@ -403,7 +428,6 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeRounded) {
.Properties(artifact.PaintChunks()[2].properties)
.Build();
GeometryMapperClipCache::ClearCache();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(new_artifact1, kDefaultLayerBounds, layer_state);
const auto& invalidations1 = TrackedRasterInvalidations(invalidator);
......@@ -411,6 +435,7 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeRounded) {
EXPECT_CHUNK_INVALIDATION(invalidations1, 0, new_artifact1.PaintChunks()[1],
PaintInvalidationReason::kPaintProperty);
invalidator.SetTracksRasterInvalidations(false);
CleanUp(new_artifact1);
}
// Tests the path detecting change of PaintChunkInfo::chunk_to_layer_clip.
......@@ -429,10 +454,10 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeSimple) {
.Bounds(clip_rect.Rect())
.Build();
GeometryMapperClipCache::ClearCache();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change clip1 to bigger, which is still bound by clip0, resulting no actual
// visual change.
......@@ -448,10 +473,9 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeSimple) {
.Bounds(artifact.PaintChunks()[1].bounds)
.Build();
GeometryMapperClipCache::ClearCache();
invalidator.Generate(new_artifact1, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
clip1->ClearChangedToRoot();
CleanUp(new_artifact1);
// Change clip1 to smaller.
FloatRoundedRect new_clip_rect2(-500, -500, 1000, 1000);
......@@ -466,7 +490,6 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeSimple) {
.Bounds(new_clip_rect2.Rect())
.Build();
GeometryMapperClipCache::ClearCache();
invalidator.Generate(new_artifact2, kDefaultLayerBounds, layer_state);
const auto& invalidations = TrackedRasterInvalidations(invalidator);
ASSERT_EQ(4u, invalidations.size());
......@@ -484,7 +507,7 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeSimple) {
new_artifact2.PaintChunks()[1],
IntRect(-1000, 500, 2000, 500));
invalidator.SetTracksRasterInvalidations(false);
clip1->ClearChangedToRoot();
CleanUp(new_artifact2);
// Change clip1 bigger at one side.
FloatRoundedRect new_clip_rect3(-500, -500, 2000, 1000);
......@@ -499,7 +522,6 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeSimple) {
.Bounds(new_clip_rect3.Rect())
.Build();
GeometryMapperClipCache::ClearCache();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(new_artifact3, kDefaultLayerBounds, layer_state);
const auto& invalidations1 = TrackedRasterInvalidations(invalidator);
......@@ -509,7 +531,78 @@ TEST_F(RasterInvalidatorTest, ClipPropertyChangeSimple) {
new_artifact3.PaintChunks()[1],
IntRect(500, -500, 500, 1000));
invalidator.SetTracksRasterInvalidations(false);
clip1->ClearChangedToRoot();
CleanUp(new_artifact3);
}
TEST_F(RasterInvalidatorTest, ClipLocalTransformSpaceChange) {
RasterInvalidator invalidator(kNoopRasterInvalidation);
auto t1 = CreateTransform(t0(), TransformationMatrix());
auto t2 = CreateTransform(*t1, TransformationMatrix());
FloatRoundedRect::Radii radii(FloatSize(1, 2), FloatSize(2, 3),
FloatSize(3, 4), FloatSize(4, 5));
FloatRoundedRect clip_rect(FloatRect(-1000, -1000, 2000, 2000), radii);
auto c1 = CreateClip(c0(), t1.get(), clip_rect);
PropertyTreeState layer_state(&t0(), &c0(), &e0());
auto artifact = Chunk(0).Properties(*t2, *c1, e0()).Build();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change both t1 and t2 but keep t1*t2 unchanged, to test change of
// LocalTransformSpace of c1.
t1->Update(t0(), TransformPaintPropertyNode::State{
TransformationMatrix().Translate(-10, -20)});
t2->Update(*t1, TransformPaintPropertyNode::State{
TransformationMatrix().Translate(10, 20)});
auto new_artifact = Chunk(0).Properties(*t2, *c1, e0()).Build();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
const auto& invalidations = TrackedRasterInvalidations(invalidator);
ASSERT_EQ(1u, invalidations.size());
EXPECT_CHUNK_INVALIDATION(invalidations, 0, new_artifact.PaintChunks()[0],
PaintInvalidationReason::kPaintProperty);
invalidator.SetTracksRasterInvalidations(false);
CleanUp(new_artifact);
}
// This is based on ClipLocalTransformSpaceChange, but tests the no-invalidation
// path by letting the clip's LocalTransformSpace be the same as the chunk's
// transform.
TEST_F(RasterInvalidatorTest, ClipLocalTransformSpaceChangeNoInvalidation) {
RasterInvalidator invalidator(kNoopRasterInvalidation);
auto t1 = CreateTransform(t0(), TransformationMatrix());
auto t2 = CreateTransform(*t1, TransformationMatrix());
FloatRoundedRect::Radii radii(FloatSize(1, 2), FloatSize(2, 3),
FloatSize(3, 4), FloatSize(4, 5));
FloatRoundedRect clip_rect(FloatRect(-1000, -1000, 2000, 2000), radii);
// This set is different from ClipLocalTransformSpaceChange.
auto c1 = CreateClip(c0(), t2.get(), clip_rect);
PropertyTreeState layer_state(&t0(), &c0(), &e0());
auto artifact = Chunk(0).Properties(*t2, *c1, e0()).Build();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change both t1 and t2 but keep t1*t2 unchanged.
t1->Update(t0(), TransformPaintPropertyNode::State{
TransformationMatrix().Translate(-10, -20)});
t2->Update(*t1, TransformPaintPropertyNode::State{
TransformationMatrix().Translate(10, 20)});
auto new_artifact = Chunk(0).Properties(*t2, *c1, e0()).Build();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(new_artifact);
}
TEST_F(RasterInvalidatorTest, TransformPropertyChange) {
......@@ -528,10 +621,10 @@ TEST_F(RasterInvalidatorTest, TransformPropertyChange) {
.Properties(*transform1, c0(), e0())
.Build();
GeometryMapperTransformCache::ClearCache();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change layer_transform should not cause raster invalidation in the layer.
layer_transform->Update(
......@@ -546,6 +639,7 @@ TEST_F(RasterInvalidatorTest, TransformPropertyChange) {
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(new_artifact);
// Inserting another node between layer_transform and transform0 and letting
// the new node become the transform of the layer state should not cause
......@@ -562,9 +656,9 @@ TEST_F(RasterInvalidatorTest, TransformPropertyChange) {
.Properties(artifact.PaintChunks()[1].properties)
.Build();
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact1, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(new_artifact1);
// Removing transform nodes above the layer state should not cause raster
// invalidation in the layer.
......@@ -577,9 +671,9 @@ TEST_F(RasterInvalidatorTest, TransformPropertyChange) {
.Properties(artifact.PaintChunks()[1].properties)
.Build();
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact2, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(new_artifact2);
// Change transform0 and transform1, while keeping the combined transform0
// and transform1 unchanged for chunk 2. We should invalidate only chunk 0
......@@ -598,7 +692,6 @@ TEST_F(RasterInvalidatorTest, TransformPropertyChange) {
.Properties(artifact.PaintChunks()[1].properties)
.Build();
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact3, kDefaultLayerBounds, layer_state);
const auto& invalidations = TrackedRasterInvalidations(invalidator);
ASSERT_EQ(2u, invalidations.size());
......@@ -611,6 +704,7 @@ TEST_F(RasterInvalidatorTest, TransformPropertyChange) {
PaintInvalidationReason::kPaintProperty,
-kDefaultLayerBounds.Location() + IntSize(30, 50));
invalidator.SetTracksRasterInvalidations(false);
CleanUp(new_artifact3);
}
TEST_F(RasterInvalidatorTest, TransformPropertyTinyChange) {
......@@ -623,10 +717,10 @@ TEST_F(RasterInvalidatorTest, TransformPropertyTinyChange) {
PropertyTreeState layer_state(layer_transform.get(), &c0(), &e0());
auto artifact = Chunk(0).Properties(*chunk_transform, c0(), e0()).Build();
GeometryMapperTransformCache::ClearCache();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change chunk_transform by tiny difference, which should be ignored.
chunk_transform->Update(*layer_state.Transform(),
......@@ -637,9 +731,9 @@ TEST_F(RasterInvalidatorTest, TransformPropertyTinyChange) {
.Rotate(0.0000001)});
auto new_artifact = Chunk(0).Properties(*chunk_transform, c0(), e0()).Build();
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(new_artifact);
// Tiny differences should accumulate and cause invalidation when the
// accumulation is large enough.
......@@ -657,6 +751,7 @@ TEST_F(RasterInvalidatorTest, TransformPropertyTinyChange) {
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
invalidated = !TrackedRasterInvalidations(invalidator).IsEmpty();
CleanUp(new_artifact);
}
EXPECT_TRUE(invalidated);
}
......@@ -675,8 +770,8 @@ TEST_F(RasterInvalidatorTest, TransformPropertyTinyChangeScale) {
.Properties(*chunk_transform, c0(), e0())
.Build();
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
CleanUp(artifact);
// Scale change from 1e-6 to 2e-6 should be treated as significant.
invalidator.SetTracksRasterInvalidations(true);
......@@ -688,10 +783,10 @@ TEST_F(RasterInvalidatorTest, TransformPropertyTinyChangeScale) {
.Properties(*chunk_transform, c0(), e0())
.Build();
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
EXPECT_FALSE(TrackedRasterInvalidations(invalidator).IsEmpty());
invalidator.SetTracksRasterInvalidations(false);
CleanUp(new_artifact);
// Scale change from 2e-6 to 2e-6 + 1e-15 should be ignored.
invalidator.SetTracksRasterInvalidations(true);
......@@ -703,10 +798,82 @@ TEST_F(RasterInvalidatorTest, TransformPropertyTinyChangeScale) {
.Properties(*chunk_transform, c0(), e0())
.Build();
GeometryMapperTransformCache::ClearCache();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
invalidator.SetTracksRasterInvalidations(false);
CleanUp(new_artifact1);
}
TEST_F(RasterInvalidatorTest, EffectLocalTransformSpaceChange) {
RasterInvalidator invalidator(kNoopRasterInvalidation);
auto t1 = CreateTransform(t0(), TransformationMatrix());
auto t2 = CreateTransform(*t1, TransformationMatrix());
CompositorFilterOperations filter;
filter.AppendBlurFilter(20);
auto e1 = CreateFilterEffect(e0(), t1.get(), &c0(), filter);
PropertyTreeState layer_state(&t0(), &c0(), &e0());
auto artifact = Chunk(0).Properties(*t2, c0(), *e1).Build();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change both t1 and t2 but keep t1*t2 unchanged, to test change of
// LocalTransformSpace of e1.
t1->Update(t0(), TransformPaintPropertyNode::State{
TransformationMatrix().Translate(-10, -20)});
t2->Update(*t1, TransformPaintPropertyNode::State{
TransformationMatrix().Translate(10, 20)});
auto new_artifact = Chunk(0).Properties(*t2, c0(), *e1).Build();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
const auto& invalidations = TrackedRasterInvalidations(invalidator);
ASSERT_EQ(1u, invalidations.size());
auto expected_invalidation_rect = ChunkRectToLayer(
new_artifact.PaintChunks()[0].bounds, -kDefaultLayerBounds.Location());
expected_invalidation_rect.Inflate(60); // The filter outset.
EXPECT_EQ(expected_invalidation_rect, invalidations[0].rect);
EXPECT_EQ(&new_artifact.PaintChunks()[0].id.client, invalidations[0].client);
EXPECT_EQ(PaintInvalidationReason::kPaintProperty, invalidations[0].reason);
invalidator.SetTracksRasterInvalidations(false);
CleanUp(new_artifact);
}
// This is based on EffectLocalTransformSpaceChange, but tests the no-
// invalidation path by letting the effect's LocalTransformSpace be the same as
// the chunk's transform.
TEST_F(RasterInvalidatorTest, EffectLocalTransformSpaceChangeNoInvalidation) {
RasterInvalidator invalidator(kNoopRasterInvalidation);
auto t1 = CreateTransform(t0(), TransformationMatrix());
auto t2 = CreateTransform(*t1, TransformationMatrix());
// This setup is different from EffectLocalTransformSpaceChange.
CompositorFilterOperations filter;
filter.AppendBlurFilter(20);
auto e1 = CreateFilterEffect(e0(), t2.get(), &c0(), filter);
PropertyTreeState layer_state(&t0(), &c0(), &e0());
auto artifact = Chunk(0).Properties(*t2, c0(), *e1).Build();
invalidator.SetTracksRasterInvalidations(true);
invalidator.Generate(artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(artifact);
// Change both t1 and t2 but keep t1*t2 unchanged.
t1->Update(t0(), TransformPaintPropertyNode::State{
TransformationMatrix().Translate(-10, -20)});
t2->Update(*t1, TransformPaintPropertyNode::State{
TransformationMatrix().Translate(10, 20)});
auto new_artifact = Chunk(0).Properties(*t2, c0(), *e1).Build();
invalidator.Generate(new_artifact, kDefaultLayerBounds, layer_state);
EXPECT_TRUE(TrackedRasterInvalidations(invalidator).IsEmpty());
CleanUp(new_artifact);
}
} // namespace blink
......@@ -31,10 +31,26 @@ TransformPaintPropertyNode::NearestScrollTranslationNode() const {
return *transform;
}
bool TransformPaintPropertyNode::Changed(
const TransformPaintPropertyNode& relative_to_node) const {
for (const auto* node = this; node; node = node->Parent()) {
if (node == &relative_to_node)
return false;
if (node->NodeChanged())
return true;
}
// |this| is not a descendant of |relative_to_node|. We have seen no changed
// flag from |this| to the root. Now check |relative_to_node| to the root.
return relative_to_node.Changed(Root());
}
std::unique_ptr<JSONObject> TransformPaintPropertyNode::ToJSON() const {
auto json = JSONObject::Create();
if (Parent())
json->SetString("parent", String::Format("%p", Parent()));
if (NodeChanged())
json->SetBoolean("changed", true);
if (!state_.matrix.IsIdentity())
json->SetString("matrix", state_.matrix.ToString());
if (!state_.matrix.IsIdentityOrTranslation())
......
......@@ -83,6 +83,12 @@ class PLATFORM_EXPORT TransformPaintPropertyNode
return true;
}
// If |relative_to_node| is an ancestor of |this|, returns true if any node is
// marked changed along the path from |this| to |relative_to_node| (not
// included). Otherwise returns the combined changed status of the paths
// from |this| and |relative_to_node| to the root.
bool Changed(const TransformPaintPropertyNode& relative_to_node) const;
const TransformationMatrix& Matrix() const { return state_.matrix; }
const FloatPoint3D& Origin() const { return state_.origin; }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment