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