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.Collections; 30 import java.util.List; 31 import com.sun.javafx.geom.DirtyRegionContainer; 32 import com.sun.javafx.geom.RectBounds; 33 import com.sun.javafx.geom.Rectangle; 34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.javafx.geom.transform.GeneralTransform3D; 36 import com.sun.prism.Graphics; 37 import com.sun.scenario.effect.Blend; 38 import com.sun.scenario.effect.Blend.Mode; 39 import com.sun.scenario.effect.FilterContext; 40 import com.sun.scenario.effect.ImageData; 41 import com.sun.scenario.effect.impl.prism.PrDrawable; 42 import com.sun.scenario.effect.impl.prism.PrEffectHelper; 43 44 /** 45 */ 46 public class NGGroup extends NGNode { 47 /** 48 * The blend mode to use with this group. 49 */ 50 private Blend.Mode blendMode = Blend.Mode.SRC_OVER; 51 // NOTE I need a special array list here where all nodes added can have 52 // their parent set correctly, and all nodes removed have it cleared correctly. 53 // Actually, if a node is removed, I probably don't have to worry about 54 // clearing it because as soon as it is added to another parent it will be set 55 // and there is no magic listener foo going on here. 56 private List<NGNode> children = new ArrayList<>(1); 57 private List<NGNode> unmod = Collections.unmodifiableList(children); 58 private List<NGNode> removed; 59 60 /** 61 * This mask has all bits that mark that a region intersects this group. 62 * Which means it looks like this: 00010101010101010101010101010101 (first bit for sign) 63 */ 64 private static final int REGION_INTERSECTS_MASK = 0x15555555; 65 66 /*************************************************************************** 67 * * 68 * Implementation of the PGGroup interface * 69 * * 70 **************************************************************************/ 71 72 /** 73 * Gets an unmodifiable list of the current children on this group 74 */ 75 public List<NGNode> getChildren() { return unmod; } 76 77 /** 78 * Adds a node to the given index. An index of -1 means "append", for legacy 79 * reasons (it was easier than asking for the number of children, iirc). 80 * @param index -1, or <= node.size() 81 * @param node 82 */ 83 public void add(int index, NGNode node) { 84 // Validate the arguments 85 if ((index < -1) || (index > children.size())) { 86 throw new IndexOutOfBoundsException("invalid index"); 87 } 88 89 // NOTE: We used to do checks here to make sure that a node 90 // being added didn't already have another parent listed as 91 // its parent. Now we just silently accept them. The FX side 92 // is already doing this check, and implementing this check 93 // properly would require that the "clear" implementation visit 94 // all nodes and clear this flag, which is really just wasted work. 95 NGNode child = node; 96 97 // When a new node is added, we need to make sure the new node has this 98 // group registered as its parent. We also need to make sure I invalidate 99 // this group's cache and mark it dirty. Note that we don't have to worry 100 // about notifying the other parent that it has lost a node: the FX 101 // scene graph will be sure to send a "remove" notification to the other 102 // parent, so we don't have to be concerned with the other parent 103 // having to be marked dirty or whatnot. 104 child.setParent(this); 105 childDirty = true; 106 if (index == -1) { 107 children.add(node); 108 } else { 109 children.add(index, node); 110 } 111 child.markDirty(); 112 markTreeDirtyNoIncrement(); 113 geometryChanged(); 114 } 115 116 public void clearFrom(int fromIndex) { 117 if (fromIndex < children.size()) { 118 children.subList(fromIndex, children.size()).clear(); 119 geometryChanged(); 120 childDirty = true; 121 markTreeDirtyNoIncrement(); 122 } 123 } 124 125 public List<NGNode> getRemovedChildren() { 126 return removed; 127 } 128 129 public void addToRemoved(NGNode n) { 130 if (removed == null) removed = new ArrayList<>(); 131 if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) { 132 return; 133 } 134 135 removed.add(n); 136 dirtyChildrenAccumulated++; 137 138 if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) { 139 removed.clear(); //no need to store anything in this case 140 } 141 } 142 143 @Override 144 protected void clearDirty() { 145 super.clearDirty(); 146 if (removed != null) removed.clear(); 147 } 148 149 public void remove(NGNode node) { 150 // We just remove the node and mark this group as being dirty. Really, if we 151 // supported sub-regions within the group, we'd only have to mark the 152 // sub-region that had been occupied by the node as dirty, but we do not 153 // as yet have this optimization (mostly because we didn't have it in 154 // Scenario, mostly because it was hard to optimize correctly). 155 children.remove(node); 156 geometryChanged(); 157 childDirty = true; 158 markTreeDirtyNoIncrement(); 159 } 160 161 public void remove(int index) { 162 children.remove(index); 163 geometryChanged(); 164 childDirty = true; 165 markTreeDirtyNoIncrement(); 166 } 167 168 public void clear() { 169 children.clear(); 170 childDirty = false; 171 geometryChanged(); 172 markTreeDirtyNoIncrement(); 173 } 174 175 /** 176 * Set by the FX scene graph. 177 * @param blendMode cannot be null 178 */ 179 public void setBlendMode(Object blendMode) { 180 // Verify the arguments 181 if (blendMode == null) { 182 throw new IllegalArgumentException("Mode must be non-null"); 183 } 184 // If the blend mode has changed, mark this node as dirty and 185 // invalidate its cache 186 if (this.blendMode != blendMode) { 187 this.blendMode = (Blend.Mode)blendMode; 188 visualsChanged(); 189 } 190 } 191 192 @Override 193 public void renderForcedContent(Graphics gOptional) { 194 if (children == null) { 195 return; 196 } 197 for (int i = 0; i < children.size(); i++) { 198 children.get(i).renderForcedContent(gOptional); 199 } 200 } 201 202 @Override 203 protected void renderContent(Graphics g) { 204 if (children == null) { 205 return; 206 } 207 208 NodePath renderRoot = g.getRenderRoot(); 209 int startPos = 0; 210 if (renderRoot != null) { 211 if (renderRoot.hasNext()) { 212 renderRoot.next(); 213 startPos = children.indexOf(renderRoot.getCurrentNode()); 214 215 for (int i = 0; i < startPos; ++i) { 216 children.get(i).clearDirtyTree(); 217 } 218 } else { 219 g.setRenderRoot(null); 220 } 221 } 222 223 if (blendMode == Blend.Mode.SRC_OVER || 224 children.size() < 2) { // Blend modes only work "between" siblings 225 226 for (int i = startPos; i < children.size(); i++) { 227 NGNode child; 228 try { 229 child = children.get(i); 230 } catch (Exception e) { 231 child = null; 232 } 233 // minimal protection against concurrent update of the list. 234 if (child != null) { 235 child.render(g); 236 } 237 } 238 return; 239 } 240 241 Blend b = new Blend(blendMode, null, null); 242 FilterContext fctx = getFilterContext(g); 243 244 ImageData bot = null; 245 boolean idValid = true; 246 do { 247 // TODO: probably don't need to wrap the transform here... (RT-26981) 248 BaseTransform transform = g.getTransformNoClone().copy(); 249 if (bot != null) { 250 bot.unref(); 251 bot = null; 252 } 253 Rectangle rclip = PrEffectHelper.getGraphicsClipNoClone(g); 254 for (int i = startPos; i < children.size(); i++) { 255 NGNode child = children.get(i); 256 ImageData top = NodeEffectInput. 257 getImageDataForNode(fctx, child, false, transform, rclip); 258 if (bot == null) { 259 bot = top; 260 } else { 261 ImageData newbot = 262 b.filterImageDatas(fctx, transform, rclip, null, bot, top); 263 bot.unref(); 264 top.unref(); 265 bot = newbot; 266 } 267 } 268 if (bot != null && (idValid = bot.validate(fctx))) { 269 Rectangle r = bot.getUntransformedBounds(); 270 PrDrawable botimg = (PrDrawable)bot.getUntransformedImage(); 271 g.setTransform(bot.getTransform()); 272 g.drawTexture(botimg.getTextureObject(), 273 r.x, r.y, r.width, r.height); 274 } 275 } while (bot == null || !idValid); 276 277 if (bot != null) { 278 bot.unref(); 279 } 280 } 281 282 @Override 283 protected boolean hasOverlappingContents() { 284 if (blendMode != Mode.SRC_OVER) { 285 // All other modes are flattened so there are no overlapping issues 286 return false; 287 } 288 int n = (children == null ? 0 : children.size()); 289 if (n == 1) { 290 return children.get(0).hasOverlappingContents(); 291 } 292 return (n != 0); 293 } 294 295 public boolean isEmpty() { 296 return children == null || children.isEmpty(); 297 } 298 299 @Override 300 protected boolean hasVisuals() { 301 return false; 302 } 303 304 305 @Override 306 protected boolean needsBlending() { 307 Blend.Mode mode = getNodeBlendMode(); 308 // TODO: If children are all SRC_OVER then we can pass on SRC_OVER too 309 // (RT-26981) 310 return (mode != null); 311 } 312 313 /*************************************************************************** 314 * * 315 * Culling Related Methods * 316 * * 317 **************************************************************************/ 318 @Override 319 protected RenderRootResult computeRenderRoot(NodePath path, RectBounds dirtyRegion, int cullingIndex, BaseTransform tx, 320 GeneralTransform3D pvTx) { 321 322 // If the NGGroup is completely outside the culling area, then we don't have to traverse down 323 // to the children yo. 324 if (cullingIndex != -1) { 325 final int bits = cullingBits >> (cullingIndex*2); 326 if ((bits & DIRTY_REGION_CONTAINS_OR_INTERSECTS_NODE_BOUNDS) == 0) { 327 return RenderRootResult.NO_RENDER_ROOT; 328 } 329 if ((bits & DIRTY_REGION_CONTAINS_NODE_BOUNDS) != 0) { 330 cullingIndex = -1; // Do not check culling in children, 331 // as culling bits are not set for fully interior groups 332 } 333 } 334 335 if (!isVisible()) { 336 return RenderRootResult.NO_RENDER_ROOT; 337 } 338 339 if (getOpacity() != 1.0 || (getEffect() != null && getEffect().reducesOpaquePixels()) || needsBlending()) { 340 return RenderRootResult.NO_RENDER_ROOT; 341 } 342 343 if (getClipNode() != null) { 344 final NGNode clip = getClipNode(); 345 RectBounds clipBounds = clip.getOpaqueRegion(); 346 if (clipBounds == null) { 347 return RenderRootResult.NO_RENDER_ROOT; 348 } 349 TEMP_TRANSFORM.deriveWithNewTransform(tx).deriveWithConcatenation(getTransform()).deriveWithConcatenation(clip.getTransform()); 350 if (!checkBoundsInQuad(clipBounds, dirtyRegion, TEMP_TRANSFORM, pvTx)) { 351 return RenderRootResult.NO_RENDER_ROOT; 352 } 353 } 354 355 // An NGGroup itself never draws pixels, so we don't have to call super. Just visit 356 // each child, starting with the top-most. 357 double mxx = tx.getMxx(); 358 double mxy = tx.getMxy(); 359 double mxz = tx.getMxz(); 360 double mxt = tx.getMxt(); 361 362 double myx = tx.getMyx(); 363 double myy = tx.getMyy(); 364 double myz = tx.getMyz(); 365 double myt = tx.getMyt(); 366 367 double mzx = tx.getMzx(); 368 double mzy = tx.getMzy(); 369 double mzz = tx.getMzz(); 370 double mzt = tx.getMzt(); 371 final BaseTransform chTx = tx.deriveWithConcatenation(getTransform()); 372 373 // We need to keep a reference to the result of calling computeRenderRoot on each child 374 RenderRootResult result = RenderRootResult.NO_RENDER_ROOT; 375 // True if every child _after_ the the found render root is clean 376 boolean followingChildrenClean = true; 377 // Iterate over all children, looking for a render root. 378 for (int resultIdx=children.size()-1; resultIdx>=0; resultIdx--) { 379 // Get the render root result from the child 380 final NGNode child = children.get(resultIdx); 381 result = child.computeRenderRoot(path, dirtyRegion, cullingIndex, chTx, pvTx); 382 // Update this flag, which if true means that this child and all subsequent children 383 // of this group are all clean. 384 followingChildrenClean &= child.isClean(); 385 386 if (result == RenderRootResult.HAS_RENDER_ROOT) { 387 // If we have a render root and it is dirty, then we don't really care whether 388 // followingChildrenClean is true or false, we just add this group to the 389 // path and we're done. 390 path.add(this); 391 break; 392 } else if (result == RenderRootResult.HAS_RENDER_ROOT_AND_IS_CLEAN) { 393 path.add(this); 394 // If we have a result which is itself reporting that it is clean, but 395 // we have some following children which are dirty, then we need to 396 // switch the result for this Group to be HAS_RENDER_ROOT. 397 if (!followingChildrenClean) { 398 result = RenderRootResult.HAS_RENDER_ROOT; 399 } 400 break; 401 } 402 } 403 // restore previous transform state 404 tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt); 405 return result; 406 } 407 408 @Override 409 protected void markCullRegions( 410 DirtyRegionContainer drc, 411 int cullingRegionsBitsOfParent, 412 BaseTransform tx, 413 GeneralTransform3D pvTx) { 414 415 //set culling bits for this group first. 416 super.markCullRegions(drc, cullingRegionsBitsOfParent, tx, pvTx); 417 418 //cullingRegionsBits == 0 group is outside all dirty regions 419 // we can cull all children otherwise check children. 420 // If none of the regions intersect this group, skip pre-culling 421 if (cullingBits == -1 || (cullingBits != 0 && (cullingBits & REGION_INTERSECTS_MASK) != 0)) { 422 //save current transform 423 double mxx = tx.getMxx(); 424 double mxy = tx.getMxy(); 425 double mxz = tx.getMxz(); 426 double mxt = tx.getMxt(); 427 428 double myx = tx.getMyx(); 429 double myy = tx.getMyy(); 430 double myz = tx.getMyz(); 431 double myt = tx.getMyt(); 432 433 double mzx = tx.getMzx(); 434 double mzy = tx.getMzy(); 435 double mzz = tx.getMzz(); 436 double mzt = tx.getMzt(); 437 BaseTransform chTx = tx.deriveWithConcatenation(getTransform()); 438 439 NGNode child; 440 for (int chldIdx = 0; chldIdx < children.size(); chldIdx++) { 441 child = children.get(chldIdx); 442 child.markCullRegions( 443 drc, 444 cullingBits, 445 chTx, 446 pvTx); 447 } 448 // restore previous transform state 449 tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt); 450 } 451 } 452 453 @Override 454 public void drawDirtyOpts(final BaseTransform tx, final GeneralTransform3D pvTx, 455 Rectangle clipBounds, int[] countBuffer, int dirtyRegionIndex) { 456 super.drawDirtyOpts(tx, pvTx, clipBounds, countBuffer, dirtyRegionIndex); 457 // Not really efficient but this code is only executed during debug. This makes sure 458 // that the source transform (tx) is not modified. 459 BaseTransform clone = tx.copy(); 460 clone = clone.deriveWithConcatenation(getTransform()); 461 for (int childIndex = 0; childIndex < children.size(); childIndex++) { 462 final NGNode child = children.get(childIndex); 463 child.drawDirtyOpts(clone, pvTx, clipBounds, countBuffer, dirtyRegionIndex); 464 } 465 } 466 467 }