1 /* 2 * Copyright (c) 2013, 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 test.com.sun.javafx.sg.prism; 27 28 import com.sun.javafx.geom.DirtyRegionContainer; 29 import com.sun.javafx.geom.RectBounds; 30 import com.sun.javafx.geom.transform.BaseTransform; 31 import com.sun.javafx.geom.transform.GeneralTransform3D; 32 import com.sun.javafx.sg.prism.NGCircle; 33 import com.sun.javafx.sg.prism.NGGroup; 34 import com.sun.javafx.sg.prism.NGNode; 35 import com.sun.javafx.sg.prism.NGNodeShim; 36 import com.sun.javafx.sg.prism.NGRectangle; 37 import com.sun.javafx.sg.prism.NodePath; 38 import org.junit.Before; 39 import org.junit.Ignore; 40 import org.junit.Test; 41 import static org.junit.Assert.assertSame; 42 import static org.junit.Assert.assertTrue; 43 44 /** 45 * Tests for the computation of the render root of a graph 46 */ 47 public class RenderRootTest extends NGTestBase { 48 // NGNodes to test: NGRectangle, NGImageView, NGRegion, NGCircle, NGEllipse 49 // Also thrown in 3D transforms and 2D transforms other than BASE_TRANSFORM 50 // Structures to test: root, background, foreground 51 // - Foreground completely covers background. 52 // - Foreground partially overlaps background 53 // Test each node has exactly the expected opaque region, given: 54 // - stroke 55 // - effect 56 // - clip (dirty region should only include the intersection of the clip & bounds) 57 // - transforms (transform onto a pixel boundary, or transform into a pixel crack) 58 // - fill 59 // - opaque image / transparent image 60 // - opacity 61 // - blend mode 62 // - x / y position on partial pixel boundaries 63 // - others? 64 65 private NGRectangle rect; 66 private NGGroup root; 67 68 @Before 69 public void setup() { 70 rect = createRectangle(10, 10, 90, 90); 71 root = createGroup(rect); 72 } 73 74 /** 75 * Helper method to get the render root. We have to run both the markCullRegions and getRenderRoot methods 76 * in order for getRenderRoot to return the correctly computed results. 77 * 78 * @param root The root node 79 * @param dirtyX The x coordinate of the dirty region 80 * @param dirtyY The y coordinate of the dirty region 81 * @param dirtyWidth The width of the dirty region 82 * @param dirtyHeight The height of the dirty region 83 * @return The NodePath, or null if there are no path elements 84 */ 85 private NodePath getRenderRoot(NGGroup root, int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { 86 final DirtyRegionContainer drc = new DirtyRegionContainer(1); 87 final RectBounds dirtyRegion = new RectBounds(dirtyX, dirtyY, dirtyX+dirtyWidth, dirtyY+dirtyHeight); 88 drc.addDirtyRegion(dirtyRegion); 89 final BaseTransform tx = BaseTransform.IDENTITY_TRANSFORM; 90 final GeneralTransform3D pvTx = new GeneralTransform3D(); 91 NGNodeShim.markCullRegions(root, drc, -1, tx, pvTx); 92 NodePath path = new NodePath(); 93 root.getRenderRoot(path, dirtyRegion, 0, tx, pvTx); 94 return path; 95 } 96 97 /** 98 * A quick note about how the NodePath works. 99 * 100 * If it is empty, then it means that nothing 101 * needs to be painted (maybe there were some dirty nodes, but they were completely 102 * occluded, so we don't need to paint anything). 103 * 104 * If it contains *only* the root node, 105 * then either the root node itself is completely occluding the dirty region, or there was 106 * no other render root to be found, so we have to paint the whole scene. 107 * 108 * If it contains something more than just the root node, then it will be the path from 109 * the root node down to the render root child. 110 * 111 * This method takes the expected root (which may be null) and the rootPath (which can 112 * never be null). If expectedRoot is null, rootPath must be empty. Otherwise, 113 * expectedRoot must be the last item in rootPath. 114 */ 115 private void assertRenderRoot(NGNode expectedRoot, NodePath rootPath) { 116 if (expectedRoot == null) { 117 assertTrue(rootPath.isEmpty()); 118 } else { 119 // Get to the end 120 while (rootPath.hasNext()) rootPath.next(); 121 assertSame(expectedRoot, rootPath.getCurrentNode()); 122 } 123 } 124 125 /** 126 * Tests the case where the dirty region is completely within the opaque region. 127 * The rect in this case is dirty. 128 */ 129 @Test 130 public void dirtyRegionWithinOpaqueRegion() { 131 NodePath rootPath = getRenderRoot(root, 20, 20, 70, 70); 132 assertRenderRoot(rect, rootPath); 133 } 134 135 /** 136 * Tests the case where the dirty region is completely within the opaque region. 137 * The rect in this case is clean. 138 */ 139 @Test 140 public void dirtyRegionWithinOpaqueRegion_Clean() { 141 root.clearDirtyTree(); 142 NodePath rootPath = getRenderRoot(root, 20, 20, 70, 70); 143 assertRenderRoot(null, rootPath); 144 } 145 146 /** 147 * Tests the case where the dirty region exactly matches the opaque region. 148 * The rect is dirty. 149 */ 150 @Test 151 public void dirtyRegionMatchesOpaqueRegion() { 152 NodePath rootPath = getRenderRoot(root, 10, 10, 90, 90); 153 assertRenderRoot(rect, rootPath); 154 } 155 156 /** 157 * Tests the case where the dirty region exactly matches the opaque region. 158 * The rect is clean. 159 */ 160 @Test 161 public void dirtyRegionMatchesOpaqueRegion_Clean() { 162 root.clearDirtyTree(); 163 NodePath rootPath = getRenderRoot(root, 10, 10, 90, 90); 164 assertRenderRoot(null, rootPath); 165 } 166 167 /** 168 * Tests the case where the dirty region is within the opaque region, but shares the 169 * same top edge. The rect is dirty. 170 */ 171 @Test 172 public void dirtyRegionWithinOpaqueRegionTouchesTop() { 173 NodePath rootPath = getRenderRoot(root, 20, 10, 70, 70); 174 assertRenderRoot(rect, rootPath); 175 } 176 177 /** 178 * Tests the case where the dirty region is within the opaque region, but shares the 179 * same top edge. The rect is clean. 180 */ 181 @Test 182 public void dirtyRegionWithinOpaqueRegionTouchesTop_Clean() { 183 root.clearDirtyTree(); 184 NodePath rootPath = getRenderRoot(root, 20, 10, 70, 70); 185 assertRenderRoot(null, rootPath); 186 } 187 188 /** 189 * Tests the case where the dirty region is within the opaque region, but shares the 190 * same right edge. The rect is dirty. 191 */ 192 @Test 193 public void dirtyRegionWithinOpaqueRegionTouchesRight() { 194 NodePath rootPath = getRenderRoot(root, 20, 20, 80, 70); 195 assertRenderRoot(rect, rootPath); 196 } 197 /** 198 * Tests the case where the dirty region is within the opaque region, but shares the 199 * same right edge. The rect is clean. 200 */ 201 @Test 202 public void dirtyRegionWithinOpaqueRegionTouchesRight_Clean() { 203 root.clearDirtyTree(); 204 NodePath rootPath = getRenderRoot(root, 20, 20, 80, 70); 205 assertRenderRoot(null, rootPath); 206 } 207 208 /** 209 * Tests the case where the dirty region is within the opaque region, but shares the 210 * same bottom edge. The rect is dirty. 211 */ 212 @Test 213 public void dirtyRegionWithinOpaqueRegionTouchesBottom() { 214 NodePath rootPath = getRenderRoot(root, 20, 20, 70, 80); 215 assertRenderRoot(rect, rootPath); 216 } 217 218 /** 219 * Tests the case where the dirty region is within the opaque region, but shares the 220 * same bottom edge. The rect is clean. 221 */ 222 @Test 223 public void dirtyRegionWithinOpaqueRegionTouchesBottom_Clean() { 224 root.clearDirtyTree(); 225 NodePath rootPath = getRenderRoot(root, 20, 20, 70, 80); 226 assertRenderRoot(null, rootPath); 227 } 228 229 /** 230 * Tests the case where the dirty region is within the opaque region, but shares the 231 * same left edge. The rect is dirty. 232 */ 233 @Test 234 public void dirtyRegionWithinOpaqueRegionTouchesLeft() { 235 NodePath rootPath = getRenderRoot(root, 10, 20, 70, 70); 236 assertRenderRoot(rect, rootPath); 237 } 238 239 /** 240 * Tests the case where the dirty region is within the opaque region, but shares the 241 * same left edge. The rect is clean. 242 */ 243 @Test 244 public void dirtyRegionWithinOpaqueRegionTouchesLeft_Clean() { 245 root.clearDirtyTree(); 246 NodePath rootPath = getRenderRoot(root, 10, 20, 70, 70); 247 assertRenderRoot(null, rootPath); 248 } 249 250 @Test 251 public void opaqueRegionWithinDirtyRegion() { 252 NodePath rootPath = getRenderRoot(root, 0, 0, 110, 110); 253 assertRenderRoot(root, rootPath); 254 } 255 256 @Test 257 public void opaqueRegionWithinDirtyRegion_Clean() { 258 root.clearDirtyTree(); 259 NodePath rootPath = getRenderRoot(root, 0, 0, 110, 110); 260 assertRenderRoot(root, rootPath); 261 } 262 263 @Test 264 public void dirtyRegionIntersectsOpaqueRegionTop() { 265 NodePath rootPath = getRenderRoot(root, 20, 0, 70, 30); 266 assertRenderRoot(root, rootPath); 267 } 268 269 @Test 270 public void dirtyRegionIntersectsOpaqueRegionTop_Clean() { 271 root.clearDirtyTree(); 272 NodePath rootPath = getRenderRoot(root, 20, 0, 70, 30); 273 assertRenderRoot(root, rootPath); 274 } 275 276 @Test 277 public void dirtyRegionIntersectsOpaqueRegionRight() { 278 NodePath rootPath = getRenderRoot(root, 90, 20, 30, 70); 279 assertRenderRoot(root, rootPath); 280 } 281 282 @Test 283 public void dirtyRegionIntersectsOpaqueRegionRight_Clean() { 284 root.clearDirtyTree(); 285 NodePath rootPath = getRenderRoot(root, 90, 20, 30, 70); 286 assertRenderRoot(root, rootPath); 287 } 288 289 @Test 290 public void dirtyRegionIntersectsOpaqueRegionBottom() { 291 NodePath rootPath = getRenderRoot(root, 20, 90, 70, 30); 292 assertRenderRoot(root, rootPath); 293 } 294 295 @Test 296 public void dirtyRegionIntersectsOpaqueRegionBottom_Clean() { 297 root.clearDirtyTree(); 298 NodePath rootPath = getRenderRoot(root, 20, 90, 70, 30); 299 assertRenderRoot(root, rootPath); 300 } 301 302 @Test 303 public void dirtyRegionIntersectsOpaqueRegionLeft() { 304 NodePath rootPath = getRenderRoot(root, 0, 20, 30, 70); 305 assertRenderRoot(root, rootPath); 306 } 307 308 @Test 309 public void dirtyRegionIntersectsOpaqueRegionLeft_Clean() { 310 root.clearDirtyTree(); 311 NodePath rootPath = getRenderRoot(root, 0, 20, 30, 70); 312 assertRenderRoot(root, rootPath); 313 } 314 315 @Test 316 public void dirtyRegionCompletelyOutsideOfOpaqueRegion() { 317 NodePath rootPath = getRenderRoot(root, 0, 0, 5, 5); 318 assertRenderRoot(root, rootPath); 319 } 320 321 @Test 322 public void dirtyRegionCompletelyOutsideOfOpaqueRegion_Clean() { 323 root.clearDirtyTree(); 324 NodePath rootPath = getRenderRoot(root, 0, 0, 5, 5); 325 assertRenderRoot(root, rootPath); 326 } 327 328 @Ignore("What is the right thing here? It seems that an empty dirty region should result in no rendering?") 329 @Test 330 public void emptyDirtyRegion() { 331 NodePath rootPath = getRenderRoot(root, 0, 0, -1, -1); 332 assertRenderRoot(root, rootPath); 333 } 334 335 @Ignore("Currently fails because isEmpty doesn't take into account width == 0, height == 0") 336 @Test 337 public void zeroSizeDirtyRegionWithinOpaqueRegion() { 338 NodePath rootPath = getRenderRoot(root, 20, 20, 0, 0); 339 assertRenderRoot(root, rootPath); 340 } 341 342 /** 343 * Tests that a clip works. Note that I send the dirty region to be the same 344 * size as what I expect the clip to be, so that the test will fail if the 345 * dirty region ends up being larger than the computed clip. 346 */ 347 @Test 348 public void withRectangularClip() { 349 NGRectangle clip = createRectangle(20, 20, 70, 70); 350 rect.setClipNode(clip); 351 NodePath rootPath = getRenderRoot(root, 20, 20, 70, 70); 352 assertRenderRoot(rect, rootPath); 353 } 354 355 /** 356 * The negative test, where the clip is smaller than the dirty region 357 */ 358 @Test 359 public void withRectangularClip_negative() { 360 NGRectangle clip = createRectangle(20, 20, 70, 70); 361 rect.setClipNode(clip); 362 NodePath rootPath = getRenderRoot(root, 19, 20, 70, 70); 363 assertRenderRoot(root, rootPath); 364 } 365 366 /** 367 * Tests that a clip works when translated. 368 */ 369 @Test 370 public void withRectangularClipTranslated() { 371 NGRectangle clip = createRectangle(20, 20, 70, 70); 372 clip.setTransformMatrix(BaseTransform.getTranslateInstance(10, 10)); 373 rect.setClipNode(clip); 374 NodePath rootPath = getRenderRoot(root, 30, 30, 70, 70); 375 assertRenderRoot(rect, rootPath); 376 } 377 378 /** 379 * Tests that a clip works when translated. 380 */ 381 @Test 382 public void withRectangularClipTranslated_negative() { 383 NGRectangle clip = createRectangle(20, 20, 70, 70); 384 clip.setTransformMatrix(BaseTransform.getTranslateInstance(10, 10)); 385 rect.setClipNode(clip); 386 NodePath rootPath = getRenderRoot(root, 29, 30, 70, 70); 387 assertRenderRoot(root, rootPath); 388 } 389 390 /** 391 * Note, scales about origin, not center 392 */ 393 @Test 394 public void withRectangularClipScaled() { 395 NGRectangle clip = createRectangle(20, 20, 70, 70); 396 clip.setTransformMatrix(BaseTransform.getScaleInstance(.5, .5)); 397 rect.setClipNode(clip); 398 NodePath rootPath = getRenderRoot(root, 10, 10, 35, 35); 399 assertRenderRoot(rect, rootPath); 400 } 401 402 /** 403 * Note, scales about origin, not center 404 */ 405 @Test 406 public void withRectangularClipScaled_negative() { 407 NGRectangle clip = createRectangle(20, 20, 70, 70); 408 clip.setTransformMatrix(BaseTransform.getScaleInstance(.5, .5)); 409 rect.setClipNode(clip); 410 NodePath rootPath = getRenderRoot(root, 9, 10, 35, 35); 411 assertRenderRoot(root, rootPath); 412 } 413 414 /** 415 * We can now easily use ellipse and ellipse and images etc as clips 416 * in addition to rect clips. Here I choose a dirty region that is 417 * clearly in the center of the ellipse's area so as to pass the test. 418 */ 419 @Test 420 public void withCircleClip() { 421 NGCircle clip = createCircle(50, 50, 45); 422 rect.setClipNode(clip); 423 NodePath rootPath = getRenderRoot(root, 40, 40, 20, 20); 424 assertRenderRoot(rect, rootPath); 425 } 426 427 /** 428 * Make the dirty area larger than the clip so as to fail. 429 */ 430 @Test 431 public void withCircleClip_negative() { 432 NGCircle clip = createCircle(50, 50, 45); 433 rect.setClipNode(clip); 434 NodePath rootPath = getRenderRoot(root, 10, 10, 90, 90); 435 assertRenderRoot(root, rootPath); 436 } 437 }