1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.sg.prism; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 import com.sun.javafx.geom.BaseBounds; 31 import com.sun.javafx.geom.RectBounds; 32 import com.sun.javafx.geom.transform.BaseTransform; 33 import com.sun.scenario.effect.DropShadow; 34 import org.junit.Before; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 import org.junit.runners.Parameterized; 38 import static org.junit.Assert.assertEquals; 39 import static org.junit.Assert.assertFalse; 40 41 /** 42 * A series of tests where we are checking for the dirty region on a grid 43 * of nodes. This is a parameterized test (based on its base class). 44 * Each time the test is run, it will be given a way to create nodes, 45 * and a way to make them dirty. Each test then only has to invoke the 46 * creator to make nodes and the polluter to make them dirty, and then 47 * check that the right methods were called and the right dirty regions 48 * computed. There is some magic here -- every node created by the creator 49 * must implement TestNGNode; the base class must set the "root" in the 50 * parent class. 51 */ 52 @RunWith(Parameterized.class) 53 public class GridDirtyRegionTest extends DirtyRegionTestBase { 54 /** 55 * Specified to avoid magic numbers. There are several places where we 56 * translate the root in order to make it dirty. 57 */ 58 private static final float TRANSLATE_DELTA = 50; 59 60 /** 61 * Creates a new instance of the GridDirtyRegionTest. Since this is a 62 * parameterized test, we are passed the node creator and polluter. 63 */ 64 public GridDirtyRegionTest(Creator creator, Polluter polluter) { 65 super(creator, polluter); 66 } 67 68 /** 69 * Constructs a non-overlapping grid of nodes. Each node is a direct child 70 * of the root node. They may end up overlapping when they become dirty, 71 * but they don't start out that way! Each node is placed where it belongs 72 * in the grid by translating them into place. 73 */ 74 @Before public void setUp() { 75 // create the grid 76 NGNode[] content = new NGNode[9]; 77 for (int row=0; row<3; row++) { 78 for (int col=0; col<3; col++) { 79 NGNode node = creator.create(); 80 BaseTransform tx = BaseTransform.IDENTITY_TRANSFORM; 81 tx = tx.deriveWithTranslation((col * 110), (row * 110)); 82 transform(node, tx); 83 content[(row * 3) + col] = node; 84 } 85 } 86 root = createGroup(content); 87 88 // The grid is created & populated. We'll now go through and manually 89 // clean them all up so that when we perform the test, it is from the 90 // starting point of a completely cleaned tree 91 root.render(TestGraphics.TEST_GRAPHICS); 92 } 93 94 @Test public void sanityCheck() { 95 NGNode node = root.getChildren().get(0); 96 assertEquals(new RectBounds(0, 0, 100, 100), node.getContentBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM)); 97 assertEquals(new RectBounds(0, 0, 100, 100), node.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM)); 98 99 node = root.getChildren().get(1); 100 assertEquals(new RectBounds(0, 0, 100, 100), node.getContentBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM)); 101 assertEquals(new RectBounds(110, 0, 210, 100), node.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM)); 102 103 node = root.getChildren().get(3); 104 assertEquals(new RectBounds(0, 0, 100, 100), node.getContentBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM)); 105 assertEquals(new RectBounds(0, 110, 100, 210), node.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM)); 106 } 107 108 @Test public void cleanNodesShouldNotContributeToDirtyRegion() { 109 // By default the scene should be clean 110 assertDirtyRegionEquals(root, new RectBounds()); 111 } 112 113 @Test public void cleanChildNodesOnADirtyParentShouldNotContributeToDirtyRegion() { 114 // Now if I translate the root, none of the child nodes 115 // should contribute to the dirty region 116 translate(root, TRANSLATE_DELTA, TRANSLATE_DELTA); 117 for (NGNode child : root.getChildren()) { 118 assertDirtyRegionEquals(child, new RectBounds()); 119 } 120 } 121 122 @Test public void whenOnlyTheRootIsDirtyOnlyTheRootShouldBeAskedToAccumulateDirtyRegions() { 123 translate(root, TRANSLATE_DELTA, TRANSLATE_DELTA); 124 assertOnlyTheseNodesAreAskedToAccumulateDirtyRegions(root); 125 } 126 127 @Test public void cleanChildNodesOnACleanParentShouldNotContributeToDirtyRegion() { 128 // If I make one of the children dirty, then the child should contribute 129 // to the dirty region, but none of the other nodes should. This test just 130 // checks this second part -- that none of the other child nodes contribute. 131 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 132 polluter.pollute(middleChild); 133 for (NGNode child : root.getChildren()) { 134 if (child != middleChild) { // skip the dirty node 135 assertDirtyRegionEquals(child, new RectBounds()); 136 } 137 } 138 } 139 140 @Test public void whenOnlyASingleChildIsDirtyThenParentAndAllChildrenAreAskedToAccumulateDirtyRegions() { 141 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 142 polluter.pollute(middleChild); 143 List<NGNode> nodes = new ArrayList<>(root.getChildren()); 144 nodes.add(root); 145 NGNode[] arr = new NGNode[nodes.size()]; 146 for (int i=0; i<nodes.size(); i++) arr[i] = nodes.get(i); 147 // The middle child should be changed 148 assertOnlyTheseNodesAreAskedToAccumulateDirtyRegions(arr); 149 } 150 151 @Test public void whenOnlyASingleChildIsDirtyThenOnlyParentAndThatChildShouldComputeDirtyRegions() { 152 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 153 polluter.pollute(middleChild); 154 /* 155 Testing discovered a really great thing that happens -- if the dirty node is 156 a group, but it has no dirty child nodes, then the group itself will not end 157 up with accumulateGroupDirtyRegion getting called -- only the node variant. 158 Which makes perfect sense and is something of an optimization. 159 */ 160 assertOnlyTheseNodesAreAskedToComputeDirtyRegions(root, middleChild); 161 } 162 163 @Test public void aDirtyChildNodeShouldFormTheDirtyRegionWhenItIsTheOnlyDirtyNode() { 164 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 165 assertDirtyRegionEquals(root, polluter.polluteAndGetExpectedBounds(middleChild)); 166 } 167 168 @Test public void theUnionOfTwoDirtyChildNodesDirtyRegionsShouldFormTheDirtyRegionWhenTheyAreTheOnlyDirtyNodes() { 169 NGNode firstChild = root.getChildren().get(0); 170 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 171 RectBounds firstChildArea = polluter.polluteAndGetExpectedBounds(firstChild); 172 RectBounds middleChildArea = polluter.polluteAndGetExpectedBounds(middleChild); 173 RectBounds expected = (RectBounds)firstChildArea.deriveWithUnion(middleChildArea); 174 assertDirtyRegionEquals(root, expected); 175 } 176 177 @Test public void whenTheParentIsDirtyAndSomeChildrenAreDirtyTheParentBoundsShouldFormTheDirtyRegion() { 178 BaseBounds original = root.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 179 translate(root, TRANSLATE_DELTA, TRANSLATE_DELTA); 180 BaseBounds transformed = root.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 181 polluter.pollute(root.getChildren().get(0)); 182 polluter.pollute(root.getChildren().get(root.getChildren().size()/2)); 183 polluter.pollute(root.getChildren().get(root.getChildren().size()-1)); 184 RectBounds expected = (RectBounds)original.deriveWithUnion(transformed); 185 assertDirtyRegionEquals(root, expected); 186 } 187 188 @Test public void anEffectShouldChangeTheTransformedBoundsOfAChild() { 189 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 190 BaseBounds oldTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 191 DropShadow shadow = new DropShadow(); 192 shadow.setGaussianWidth(21); 193 shadow.setGaussianHeight(21); 194 shadow.setOffsetX(2); 195 shadow.setOffsetY(2); 196 setEffect(middleChild, shadow); 197 BaseBounds newTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 198 assertFalse(newTransformedBounds.equals(oldTransformedBounds)); 199 } 200 201 @Test public void whenAnEffectIsSetTheChildBecomesDirtyAndTheDirtyRegionIncludesTheEffectBounds() { 202 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 203 DropShadow shadow = new DropShadow(); 204 shadow.setGaussianWidth(21); 205 shadow.setGaussianHeight(21); 206 shadow.setOffsetX(2); 207 shadow.setOffsetY(2); 208 BaseBounds oldTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 209 setEffect(middleChild, shadow); 210 BaseBounds newTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 211 RectBounds expected = (RectBounds)oldTransformedBounds.deriveWithUnion(newTransformedBounds); 212 assertDirtyRegionEquals(root, expected); 213 } 214 215 @Test public void whenAnEffectIsChangedOnTheChildTheDirtyRegionIncludesTheOldAndNewEffectBounds() { 216 NGNode middleChild = root.getChildren().get(root.getChildren().size()/2); 217 DropShadow shadow = new DropShadow(); 218 shadow.setGaussianWidth(21); 219 shadow.setGaussianHeight(21); 220 shadow.setOffsetX(2); 221 shadow.setOffsetY(2); 222 BaseBounds oldTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 223 setEffect(middleChild, shadow); 224 BaseBounds newTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 225 shadow.setOffsetX(20); 226 shadow.setOffsetY(20); 227 BaseBounds evenNewerTransformedBounds = middleChild.getCompleteBounds(new RectBounds(), BaseTransform.IDENTITY_TRANSFORM); 228 RectBounds expected = (RectBounds)oldTransformedBounds.deriveWithUnion(newTransformedBounds).deriveWithUnion(evenNewerTransformedBounds); 229 assertDirtyRegionEquals(root, expected); 230 } 231 232 // TODO be sure to test changing properties on the node clip. For example, use a rect clip 233 // and change its geometry (RT-26928) 234 235 236 // TODO be sure to write a number of tests regarding the screen clip, and make sure that 237 // I test that accumulating dirty regions is correct in the presence of a clip. (RT-26928) 238 }