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 javafx.scene.CacheHint;
  29 import java.util.ArrayList;
  30 import java.util.List;
  31 import com.sun.glass.ui.Screen;
  32 import com.sun.javafx.geom.BaseBounds;
  33 import com.sun.javafx.geom.BoxBounds;
  34 import com.sun.javafx.geom.DirtyRegionContainer;
  35 import com.sun.javafx.geom.DirtyRegionPool;
  36 import com.sun.javafx.geom.Point2D;
  37 import com.sun.javafx.geom.RectBounds;
  38 import com.sun.javafx.geom.Rectangle;
  39 import com.sun.javafx.geom.transform.Affine3D;
  40 import com.sun.javafx.geom.transform.BaseTransform;
  41 import com.sun.javafx.geom.transform.GeneralTransform3D;
  42 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  43 import com.sun.prism.CompositeMode;
  44 import com.sun.prism.Graphics;
  45 import com.sun.prism.GraphicsPipeline;
  46 import com.sun.prism.RTTexture;
  47 import com.sun.prism.ReadbackGraphics;
  48 import com.sun.prism.impl.PrismSettings;
  49 import com.sun.scenario.effect.Blend;
  50 import com.sun.scenario.effect.Effect;
  51 import com.sun.scenario.effect.FilterContext;
  52 import com.sun.scenario.effect.ImageData;
  53 import com.sun.scenario.effect.impl.prism.PrDrawable;
  54 import com.sun.scenario.effect.impl.prism.PrEffectHelper;
  55 import com.sun.scenario.effect.impl.prism.PrFilterContext;
  56 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
  57 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
  58 
  59 /**
  60  * NGNode is the abstract base class peer of Node, forming
  61  * the basis for Prism and Scenario render graphs.
  62  * <p>
  63  * During synchronization, the FX scene graph will pass down to us
  64  * the transform which takes us from local space to parent space, the
  65  * content bounds (ie: geom bounds), and the transformed bounds
  66  * (ie: boundsInParent), and the clippedBounds. The effect bounds have
  67  * already been passed to the Effect peer (if there is one).
  68  * <p>
  69  * Whenever the transformedBounds of the NGNode are changed, we update
  70  * the dirtyBounds, so that the next time we need to accumulate dirty
  71  * regions, we will have the information we need to make sure we create
  72  * an appropriate dirty region.
  73  * <p>
  74  * NGNode maintains a single "dirty" flag, which indicates that this
  75  * node itself is dirty and must contribute to the dirty region. More
  76  * specifically, it indicates that this node is now dirty with respect
  77  * to the back buffer. Any rendering of the scene which will go on the
  78  * back buffer will cause the dirty flag to be cleared, whereas a
  79  * rendering of the scene which is for an intermediate image will not
  80  * clear this dirty flag.
  81  */
  82 public abstract class NGNode {
  83     protected static float highestPixelScale;
  84     static {
  85         // TODO: temporary until RT-27958 is fixed. Screens may be null or could be not initialized
  86         // when running unit tests
  87         try {
  88             for (Screen s : Screen.getScreens()) {
  89                 highestPixelScale = Math.max(s.getScale(), highestPixelScale);
  90             }
  91         } catch (RuntimeException ex) {
  92             System.err.println("WARNING: unable to get max pixel scale for screens");
  93             highestPixelScale = 1.0f;
  94         }
  95     }
  96 
  97     private final static GraphicsPipeline pipeline =
  98         GraphicsPipeline.getPipeline();
  99 
 100     private final static Boolean effectsSupported =
 101         (pipeline == null ? false : pipeline.isEffectSupported());
 102 
 103     public static enum DirtyFlag {
 104         CLEAN,
 105         // Means that the node is dirty, but only because of translation
 106         DIRTY_BY_TRANSLATION,
 107         DIRTY
 108     }
 109 
 110     /**
 111      * Used for debug purposes. Set during sync.
 112      */
 113     private String name;
 114 
 115     /**
 116      * Temporary bounds for use by this class or subclasses, designed to
 117      * reduce the amount of garbage we generate. If we get to the point
 118      * where we have multi-threaded rasterization, we might need to make
 119      * this per-instance instead of static.
 120      */
 121     private static final BoxBounds TEMP_BOUNDS = new BoxBounds();
 122     private static final RectBounds TEMP_RECT_BOUNDS = new RectBounds();
 123     protected static final Affine3D TEMP_TRANSFORM = new Affine3D();
 124 
 125     /**
 126      * Statics for defining what the culling bits are. We use 2 bits to
 127      * determine culling status
 128      */
 129     static final int DIRTY_REGION_INTERSECTS_NODE_BOUNDS = 0x1;
 130     static final int DIRTY_REGION_CONTAINS_NODE_BOUNDS = 0x2;
 131     static final int DIRTY_REGION_CONTAINS_OR_INTERSECTS_NODE_BOUNDS =
 132             DIRTY_REGION_INTERSECTS_NODE_BOUNDS | DIRTY_REGION_CONTAINS_NODE_BOUNDS;
 133 
 134     /**
 135      * The transform for this node. Although we are handed all the bounds
 136      * during synchronization (including the transformed bounds), we still
 137      * need the transform so that we can apply it to the clip and so forth
 138      * while accumulating dirty regions and rendering.
 139      */
 140     private BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
 141 
 142     /**
 143      * The cached transformed bounds. This is never null, but is frequently set
 144      * to be invalid whenever the bounds for the node have changed. These are
 145      * "complete" bounds, that is, with transforms and effect and clip applied.
 146      * Note that this is equivalent to boundsInParent in FX.
 147      */
 148     protected BaseBounds transformedBounds = new RectBounds();
 149 
 150     /**
 151      * The cached bounds. This is never null, but is frequently set to be
 152      * invalid whenever the bounds for the node have changed. These are the
 153      * "content" bounds, that is, without transforms or filters applied.
 154      */
 155     protected BaseBounds contentBounds = new RectBounds();
 156 
 157     /**
 158      * We keep a reference to the last transform bounds that were valid
 159      * and known. We do this to significantly speed up the rendering of the
 160      * scene by culling and clipping based on "dirty" regions, which are
 161      * essentially the rectangle formed by the union of the dirtyBounds
 162      * and the transformedBounds.
 163      */
 164     BaseBounds dirtyBounds = new RectBounds();
 165 
 166     /**
 167      * Whether the node is visible. We need to know about the visibility of
 168      * the node so that we can determine whether to cull it out, and perform
 169      * other such optimizations.
 170      */
 171     private boolean visible = true;
 172 
 173     /**
 174      * Indicates that this NGNode is itself dirty and needs its full bounds
 175      * included in the next repaint. This means it is dirty with respect to
 176      * the back buffer. We don't bother differentiating between bounds dirty
 177      * and visuals dirty because we can simply inspect the dirtyBounds to
 178      * see if it is valid. If so, then bounds must be dirty.
 179      */
 180     protected DirtyFlag dirty = DirtyFlag.DIRTY;
 181 
 182     /**
 183      * The parent of the node. In the case of a normal render graph node,
 184      * this will be an NGGroup. However, if this node is being used as
 185      * a clip node, then the parent is the node it is the clip for.
 186      */
 187     private NGNode parent;
 188 
 189     /**
 190      * True if this node is a clip. This means the parent is clipped by this node.
 191      */
 192     private boolean isClip;
 193 
 194     /**
 195      * The node used for specifying the clipping shape for this node. If null,
 196      * then there is no clip.
 197      */
 198     private NGNode clipNode;
 199 
 200     /**
 201      * The opacity of this node.
 202      */
 203     private float opacity = 1f;
 204 
 205     /**
 206      * The blend mode that controls how the pixels of this node blend into
 207      * the rest of the scene behind it.
 208      */
 209     private Blend.Mode nodeBlendMode;
 210 
 211     /**
 212      * The depth test flag for this node. It is used when rendering if the window
 213      * into which we are rendering has a depth buffer.
 214      */
 215     private boolean depthTest = true;
 216 
 217     /**
 218      * A filter used when the node is cached. If null, then the node is not
 219      * being cached. While in theory this could be created automatically by
 220      * the implementation due to some form of heuristic, currently we
 221      * only set this if the application has requested that the node be cached.
 222      */
 223     private CacheFilter cacheFilter;
 224 
 225     /**
 226      * A filter used whenever an effect is placed on the node. Of course
 227      * effects can form a kind of tree, such that this one effect might be
 228      * an accumulation of several different effects. This will be null if
 229      * there are no effects on the FX scene graph node.
 230      */
 231     private EffectFilter effectFilter;
 232 
 233     /**
 234      * If this node is an NGGroup, then this flag will be used to indicate
 235      * whether one or more of its children is dirty. While it would seem this
 236      * flag should be on NGGroup, the code turns out to be a bit cleaner with
 237      * this flag in the NGNode class.
 238      */
 239     protected boolean childDirty = false;
 240 
 241     /**
 242      * How many children are going to be accumulated
 243      */
 244     protected int dirtyChildrenAccumulated = 0;
 245 
 246     /**
 247      * Do not iterate over all children in group. Mark group as dirty
 248      * when threshold was reached.
 249      */
 250     protected final static int DIRTY_CHILDREN_ACCUMULATED_THRESHOLD = 12;
 251 
 252     /**
 253      * Marks position of this node in dirty regions.
 254      */
 255     protected int cullingBits = 0x0;
 256     private DirtyHint hint;
 257 
 258     /**
 259      * A cached representation of the opaque region for this node. This
 260      * cached version needs to be recomputed whenever the opaque region becomes
 261      * invalid, which includes local transform changes (translations included!).
 262      */
 263     private RectBounds opaqueRegion = null;
 264 
 265     /**
 266      * To avoid object churn we keep opaqueRegion around, and just toggle this
 267      * boolean to indicate whether we need to recompute the opaqueRegion.
 268      */
 269     private boolean opaqueRegionInvalid = true;
 270 
 271     /**
 272      * Used for debug purposes. This field will keep track of which nodes were
 273      * rendered as a result of different dirty regions. These correspond to the
 274      * same positions as the cullingBits. So for example, if a node was rendered
 275      * by dirty region 0, then painted will have the lowest bit set. If it
 276      * was rendered by dirty region 3, then it would have the 3rd bit from the
 277      * right set ( that is, 1 << 2)
 278      */
 279     private int painted = 0;
 280 
 281     protected NGNode() { }
 282 
 283     /***************************************************************************
 284      *                                                                         *
 285      *                Methods invoked during synchronization                   *
 286      *                                                                         *
 287      **************************************************************************/
 288 
 289     /**
 290      * Called by the FX scene graph to tell us whether we should be visible or not.
 291      * @param value whether it is visible
 292      */
 293     public void setVisible(boolean value) {
 294         // If the visibility changes, we need to mark this node as being dirty.
 295         // If this node is being cached, changing visibility should have no
 296         // effect, since it doesn't affect the rendering of the content in
 297         // any way. If we were to release the cached image, that might thwart
 298         // the developer's attempt to improve performance for things that
 299         // rapidly appear and disappear but which are expensive to render.
 300         // Ancestors, of course, must still have their caches invalidated.
 301         if (visible != value) {
 302             this.visible = value;
 303             markDirty();
 304         }
 305     }
 306 
 307     /**
 308      * Called by the FX scene graph to tell us what our new content bounds are.
 309      * @param bounds must not be null
 310      */
 311     public void setContentBounds(BaseBounds bounds) {
 312         // Note, there isn't anything to do here. We're dirty if geom or
 313         // visuals or transformed bounds or effects or clip have changed.
 314         // There's no point dealing with it here.
 315         contentBounds = contentBounds.deriveWithNewBounds(bounds);
 316     }
 317 
 318     /**
 319      * Called by the FX scene graph to tell us what our transformed bounds are.
 320      * @param bounds must not be null
 321      */
 322     public void setTransformedBounds(BaseBounds bounds, boolean byTransformChangeOnly) {
 323         if (transformedBounds.equals(bounds)) {
 324             // There has been no change, so ignore. It turns out this happens
 325             // a lot, because when a leaf has dirty bounds, all parents also
 326             // assume their bounds have changed, and only when they recompute
 327             // their bounds do we discover otherwise. This check could happen
 328             // on the FX side, however, then the FX side needs to cache the
 329             // former content bounds at the time of the last sync or needs to
 330             // be able to read state back from the NG side. Yuck. Just doing
 331             // it here for now.
 332             return;
 333         }
 334         // If the transformed bounds have changed, then we need to save off the
 335         // transformed bounds into the dirty bounds, so that the resulting
 336         // dirty region will be correct. If this node is cached, we DO NOT
 337         // invalidate the cache. The cacheFilter will compare its cached
 338         // transform to the accumulated transform to determine whether the
 339         // cache needs to be regenerated. So we will not invalidate it here.
 340         if (dirtyBounds.isEmpty()) {
 341             dirtyBounds = dirtyBounds.deriveWithNewBounds(transformedBounds);
 342             dirtyBounds = dirtyBounds.deriveWithUnion(bounds);
 343         } else {
 344             // TODO I think this is vestigial from Scenario and will never
 345             // actually occur in real life... (RT-23956)
 346             dirtyBounds = dirtyBounds.deriveWithUnion(transformedBounds);
 347         }
 348         transformedBounds = transformedBounds.deriveWithNewBounds(bounds);
 349         if (hasVisuals() && !byTransformChangeOnly) {
 350             markDirty();
 351         }
 352     }
 353 
 354     /**
 355      * Called by the FX scene graph to tell us what our transform matrix is.
 356      * @param tx must not be null
 357      */
 358     public void setTransformMatrix(BaseTransform tx) {
 359         if (transform.equals(tx)) {
 360             return;
 361         }
 362         // If the transform matrix has changed, then we need to update it,
 363         // and mark this node as dirty. If this node is cached, we DO NOT
 364         // invalidate the cache. The cacheFilter will compare its cached
 365         // transform to the accumulated transform to determine whether the
 366         // cache needs to be regenerated. So we will not invalidate it here.
 367         // This approach allows the cached image to be reused in situations
 368         // where only the translation parameters of the accumulated transform
 369         // are changing. The scene will still be marked dirty and cached
 370         // images of any ancestors will be invalidated.
 371         boolean useHint = false;
 372 
 373         // If the parent is cached, try to check if the transformation is only a translation
 374         if (parent != null && parent.cacheFilter != null && PrismSettings.scrollCacheOpt) {
 375             if (hint == null) {
 376                 // If there's no hint created yet, this is the first setTransformMatrix
 377                 // call and we have nothing to compare to yet.
 378                 hint = new DirtyHint();
 379             } else {
 380                 if (transform.getMxx() == tx.getMxx()
 381                         && transform.getMxy() == tx.getMxy()
 382                         && transform.getMyy() == tx.getMyy()
 383                         && transform.getMyx() == tx.getMyx()
 384                         && transform.getMxz() == tx.getMxz()
 385                         && transform.getMyz() == tx.getMyz()
 386                         && transform.getMzx() == tx.getMzx()
 387                         && transform.getMzy() == tx.getMzy()
 388                         && transform.getMzz() == tx.getMzz()
 389                         && transform.getMzt() == tx.getMzt()) {
 390                     useHint = true;
 391                     hint.translateXDelta = tx.getMxt() - transform.getMxt();
 392                     hint.translateYDelta = tx.getMyt() - transform.getMyt();
 393                 }
 394             }
 395         }
 396 
 397         transform = transform.deriveWithNewTransform(tx);
 398         if (useHint) {
 399             markDirtyByTranslation();
 400         } else {
 401             markDirty();
 402         }
 403         invalidateOpaqueRegion();
 404     }
 405 
 406     /**
 407      * Called by the FX scene graph whenever the clip node for this node changes.
 408      * @param clipNode can be null if the clip node is being cleared
 409      */
 410     public void setClipNode(NGNode clipNode) {
 411         // Whenever the clipNode itself has changed (that is, the reference to
 412         // the clipNode), we need to be sure to mark this node dirty and to
 413         // invalidate the cache of this node (if there is one) and all parents.
 414         if (clipNode != this.clipNode) {
 415             // Clear the "parent" property of the clip node, if there was one
 416             if (this.clipNode != null) this.clipNode.setParent(null);
 417             // Make the "parent" property of the clip node point to this
 418             if (clipNode != null) clipNode.setParent(this, true);
 419             // Keep the reference to the new clip node
 420             this.clipNode = clipNode;
 421             // Mark this node dirty, invalidate its cache, and all parents.
 422             visualsChanged();
 423             invalidateOpaqueRegion();
 424         }
 425     }
 426 
 427     /**
 428      * Called by the FX scene graph whenever the opacity for the node changes.
 429      * We create a special filter when the opacity is < 1.
 430      * @param opacity A value between 0 and 1.
 431      */
 432     public void setOpacity(float opacity) {
 433         // Check the argument to make sure it is valid.
 434         if (opacity < 0 || opacity > 1) {
 435             throw new IllegalArgumentException("Internal Error: The opacity must be between 0 and 1");
 436         }
 437         // If the opacity has changed, react. If this node is being cached,
 438         // then we do not want to invalidate the cache due to an opacity
 439         // change. However, as usual, all parent caches must be invalidated.
 440         if (opacity != this.opacity) {
 441             final float old = this.opacity;
 442             this.opacity = opacity;
 443             markDirty();
 444             // Even though the opacity has changed, for example from .5 to .6,
 445             // we don't need to invalidate the opaque region unless it has toggled
 446             // from 1 to !1, or from !1 to 1.
 447             if (old < 1 && (opacity == 1 || opacity == 0) || opacity < 1 && (old == 1 || old == 0)) {
 448                 invalidateOpaqueRegion();
 449             }
 450         }
 451     }
 452 
 453     /**
 454      * Set by the FX scene graph.
 455      * @param blendMode may be null to indicate "default"
 456      */
 457     public void setNodeBlendMode(Blend.Mode blendMode) {
 458         // The following code was a broken optimization that made an
 459         // incorrect assumption about null meaning the same thing as
 460         // SRC_OVER.  In reality, null means "pass through blending
 461         // from children" and SRC_OVER means "intercept blending of
 462         // children, allow them to blend with each other, but pass
 463         // their result on in a single SRC_OVER operation into the bg".
 464         // For leaf nodes, those are mostly the same thing, but Regions
 465         // and Groups might behave differently for the two modes.
 466 //        if (blendMode == Blend.Mode.SRC_OVER) {
 467 //            blendMode = null;
 468 //        }
 469 
 470         // If the blend mode has changed, react. If this node is being cached,
 471         // then we do not want to invalidate the cache due to a compositing
 472         // change. However, as usual, all parent caches must be invalidated.
 473 
 474         if (this.nodeBlendMode != blendMode) {
 475             this.nodeBlendMode = blendMode;
 476             markDirty();
 477             invalidateOpaqueRegion();
 478         }
 479     }
 480 
 481     /**
 482      * Called by the FX scene graph whenever the derived depth test flag for
 483      * the node changes.
 484      * @param depthTest indicates whether to perform a depth test operation
 485      * (if the window has a depth buffer).
 486      */
 487     public void setDepthTest(boolean depthTest) {
 488         // If the depth test flag has changed, react.
 489         if (depthTest != this.depthTest) {
 490             this.depthTest = depthTest;
 491             // Mark this node dirty, invalidate its cache, and all parents.
 492             visualsChanged();
 493         }
 494     }
 495 
 496     /**
 497      * Called by the FX scene graph whenever "cached" or "cacheHint" changes.
 498      * These hints provide a way for the developer to indicate whether they
 499      * want this node to be cached as a raster, which can be quite a performance
 500      * optimization in some cases (and lethal in others).
 501      * @param cached specifies whether or not this node should be cached
 502      * @param cacheHint never null, indicates some hint as to how to cache
 503      */
 504     public void setCachedAsBitmap(boolean cached, CacheHint cacheHint) {
 505         // Validate the arguments
 506         if (cacheHint == null) {
 507             throw new IllegalArgumentException("Internal Error: cacheHint must not be null");
 508         }
 509 
 510         if (cached) {
 511             if (cacheFilter == null) {
 512                 cacheFilter = new CacheFilter(this, cacheHint);
 513                 // We do not technically need to do a render pass here, but if
 514                 // we wait for the next render pass to cache it, then we will
 515                 // cache not the current visuals, but the visuals as defined
 516                 // by any transform changes that happen between now and then.
 517                 // Repainting now encourages the cached version to be as close
 518                 // as possible to the state of the node when the cache hint
 519                 // was set...
 520                 markDirty();
 521             } else {
 522                 if (!cacheFilter.matchesHint(cacheHint)) {
 523                     cacheFilter.setHint(cacheHint);
 524                     // Different hints may have different requirements of
 525                     // whether the cache is stale.  We do not have enough info
 526                     // right here to evaluate that, but it will be determined
 527                     // naturally during a repaint cycle.
 528                     // If the new hint is more relaxed (QUALITY => SPEED for
 529                     // instance) then rendering should be quick.
 530                     // If the new hint is more restricted (SPEED => QUALITY)
 531                     // then we need to render to improve the results anyway.
 532                     markDirty();
 533                 }
 534             }
 535         } else {
 536             if (cacheFilter != null) {
 537                 cacheFilter.dispose();
 538                 cacheFilter = null;
 539                 // A cache will often look worse than uncached rendering.  It
 540                 // may look the same in some circumstances, and this may then
 541                 // be an unnecessary rendering pass, but we do not have enough
 542                 // information here to be able to optimize that when possible.
 543                 markDirty();
 544             }
 545         }
 546     }
 547 
 548     /**
 549      * Called by the FX scene graph to set the effect.
 550      * @param effect the effect (can be null to clear it)
 551      */
 552     public void setEffect(Effect effect) {
 553         final Effect old = getEffect();
 554         // When effects are disabled, be sure to reset the effect filter
 555         if (PrismSettings.disableEffects) {
 556             effect = null;
 557         }
 558 
 559         // We only need to take action if the effect is different than what was
 560         // set previously. There are four possibilities. Of these, #1 and #3 matter:
 561         // 0. effectFilter == null, effect == null
 562         // 1. effectFilter == null, effect != null
 563         // 2. effectFilter != null, effectFilter.effect == effect
 564         // 3. effectFilter != null, effectFilter.effect != effect
 565         // In any case where the effect is changed, we must both invalidate
 566         // the cache for this node (if there is one) and all parents, and mark
 567         // this node as dirty.
 568         if (effectFilter == null && effect != null) {
 569             effectFilter = new EffectFilter(effect, this);
 570             visualsChanged();
 571         } else if (effectFilter != null && effectFilter.getEffect() != effect) {
 572             effectFilter.dispose();
 573             effectFilter = null;
 574             if (effect != null) {
 575                 effectFilter = new EffectFilter(effect, this);
 576             }
 577             visualsChanged();
 578         }
 579 
 580         // The only thing we do with the effect in #computeOpaqueRegion is to check
 581         // whether the effect is null / not null. If the answer to these question has
 582         // not changed from last time, then there is no need to recompute the opaque region.
 583         if (old != effect) {
 584             if (old == null || effect == null) {
 585                 invalidateOpaqueRegion();
 586             }
 587         }
 588     }
 589 
 590     /**
 591      * Called by the FX scene graph when an effect in the effect chain on the node
 592      * changes internally.
 593      */
 594     public void effectChanged() {
 595         visualsChanged();
 596     }
 597 
 598     /**
 599      * Return true if contentBounds is purely a 2D bounds, ie. it is a
 600      * RectBounds or its Z dimension is almost zero.
 601      */
 602     public boolean isContentBounds2D() {
 603         return (contentBounds.is2D()
 604                 || (Affine3D.almostZero(contentBounds.getMaxZ())
 605                 && Affine3D.almostZero(contentBounds.getMinZ())));
 606     }
 607 
 608     /***************************************************************************
 609      *                                                                         *
 610      * Hierarchy, visibility, and other such miscellaneous NGNode properties   *
 611      *                                                                         *
 612      **************************************************************************/
 613 
 614     /**
 615      * Gets the parent of this node. The parent might be an NGGroup. However,
 616      * if this node is a clip node on some other node, then the node on which
 617      * it is set as the clip will be returned. That is, suppose some node A
 618      * has a clip node B. The method B.getParent() will return A.
 619      */
 620     public NGNode getParent() { return parent; }
 621 
 622     /**
 623      * Only called by this class, or by the NGGroup class.
 624      */
 625     public void setParent(NGNode parent) {
 626         setParent(parent, false);
 627     }
 628 
 629     private void setParent(NGNode parent, boolean isClip) {
 630         this.parent = parent;
 631         this.isClip = isClip;
 632     }
 633 
 634     /**
 635      * Used for debug purposes.
 636      */
 637     public final void setName(String value) {
 638         this.name = value;
 639     }
 640 
 641     /**
 642      * Used for debug purposes.
 643      */
 644     public final String getName() {
 645         return name;
 646     }
 647 
 648     protected final Effect getEffect() { return effectFilter == null ? null : effectFilter.getEffect(); }
 649 
 650     /**
 651      * Gets whether this node's visible property is set
 652      */
 653     public boolean isVisible() { return visible; }
 654 
 655     public final BaseTransform getTransform() { return transform; }
 656     public final float getOpacity() { return opacity; }
 657     public final Blend.Mode getNodeBlendMode() { return nodeBlendMode; }
 658     public final boolean isDepthTest() { return depthTest; }
 659     public final CacheFilter getCacheFilter() { return cacheFilter; }
 660     public final EffectFilter getEffectFilter() { return effectFilter; }
 661     public final NGNode getClipNode() { return clipNode; }
 662 
 663     public BaseBounds getContentBounds(BaseBounds bounds, BaseTransform tx) {
 664         if (tx.isTranslateOrIdentity()) {
 665             bounds = bounds.deriveWithNewBounds(contentBounds);
 666             if (!tx.isIdentity()) {
 667                 float translateX = (float) tx.getMxt();
 668                 float translateY = (float) tx.getMyt();
 669                 float translateZ = (float) tx.getMzt();
 670                 bounds = bounds.deriveWithNewBounds(
 671                     bounds.getMinX() + translateX,
 672                     bounds.getMinY() + translateY,
 673                     bounds.getMinZ() + translateZ,
 674                     bounds.getMaxX() + translateX,
 675                     bounds.getMaxY() + translateY,
 676                     bounds.getMaxZ() + translateZ);
 677             }
 678             return bounds;
 679         } else {
 680             // This is a scale / rotate / skew transform.
 681             // We have contentBounds cached throughout the entire tree.
 682             // just walk down the tree and add everything up
 683             return computeBounds(bounds, tx);
 684         }
 685     }
 686 
 687     private BaseBounds computeBounds(BaseBounds bounds, BaseTransform tx) {
 688         // TODO: This code almost worked, but it ignored the local to
 689         // parent transforms on the nodes.  The short fix is to disable
 690         // this block and use the more general form below, but we need
 691         // to revisit this and see if we can make it work more optimally.
 692         // @see RT-12105 http://javafx-jira.kenai.com/browse/RT-12105
 693         if (false && this instanceof NGGroup) {
 694             List<NGNode> children = ((NGGroup)this).getChildren();
 695             BaseBounds tmp = TEMP_BOUNDS;
 696             for (int i=0; i<children.size(); i++) {
 697                 float minX = bounds.getMinX();
 698                 float minY = bounds.getMinY();
 699                 float minZ = bounds.getMinZ();
 700                 float maxX = bounds.getMaxX();
 701                 float maxY = bounds.getMaxY();
 702                 float maxZ = bounds.getMaxZ();
 703                 NGNode child = children.get(i);
 704                 bounds = child.computeBounds(bounds, tx);
 705                 tmp = tmp.deriveWithNewBounds(minX, minY, minZ, maxX, maxY, maxZ);
 706                 bounds = bounds.deriveWithUnion(tmp);
 707             }
 708             return bounds;
 709         } else {
 710             bounds = bounds.deriveWithNewBounds(contentBounds);
 711             return tx.transform(contentBounds, bounds);
 712         }
 713     }
 714 
 715     /**
 716      */
 717     public final BaseBounds getClippedBounds(BaseBounds bounds, BaseTransform tx) {
 718         BaseBounds effectBounds = getEffectBounds(bounds, tx);
 719         if (clipNode != null) {
 720             // there is a clip in place, so we will save off the effect/content
 721             // bounds (so as not to generate garbage) and will then get the
 722             // bounds of the clip node and do an intersection of the two
 723             float ex1 = effectBounds.getMinX();
 724             float ey1 = effectBounds.getMinY();
 725             float ez1 = effectBounds.getMinZ();
 726             float ex2 = effectBounds.getMaxX();
 727             float ey2 = effectBounds.getMaxY();
 728             float ez2 = effectBounds.getMaxZ();
 729             effectBounds = clipNode.getCompleteBounds(effectBounds, tx);
 730             effectBounds.intersectWith(ex1, ey1, ez1, ex2, ey2, ez2);
 731         }
 732         return effectBounds;
 733     }
 734 
 735     public final BaseBounds getEffectBounds(BaseBounds bounds, BaseTransform tx) {
 736         if (effectFilter != null) {
 737             return effectFilter.getBounds(bounds, tx);
 738         } else {
 739             return getContentBounds(bounds, tx);
 740         }
 741     }
 742 
 743     public final BaseBounds getCompleteBounds(BaseBounds bounds, BaseTransform tx) {
 744         if (tx.isIdentity()) {
 745             bounds = bounds.deriveWithNewBounds(transformedBounds);
 746             return bounds;
 747         } else if (transform.isIdentity()) {
 748             return getClippedBounds(bounds, tx);
 749         } else {
 750             double mxx = tx.getMxx();
 751             double mxy = tx.getMxy();
 752             double mxz = tx.getMxz();
 753             double mxt = tx.getMxt();
 754             double myx = tx.getMyx();
 755             double myy = tx.getMyy();
 756             double myz = tx.getMyz();
 757             double myt = tx.getMyt();
 758             double mzx = tx.getMzx();
 759             double mzy = tx.getMzy();
 760             double mzz = tx.getMzz();
 761             double mzt = tx.getMzt();
 762             BaseTransform boundsTx = tx.deriveWithConcatenation(this.transform);
 763             bounds = getClippedBounds(bounds, tx);
 764             if (boundsTx == tx) {
 765                 tx.restoreTransform(mxx, mxy, mxz, mxt,
 766                                     myx, myy, myz, myt,
 767                                     mzx, mzy, mzz, mzt);
 768             }
 769             return bounds;
 770         }
 771     }
 772 
 773     /***************************************************************************
 774      *                                                                         *
 775      * Dirty States                                                            *
 776      *                                                                         *
 777      **************************************************************************/
 778 
 779     /**
 780      * Invoked by subclasses whenever some change to the geometry or visuals
 781      * has occurred. This will mark the node as dirty and invalidate the cache.
 782      */
 783     protected void visualsChanged() {
 784         invalidateCache();
 785         markDirty();
 786     }
 787 
 788     protected void geometryChanged() {
 789         invalidateCache();
 790         invalidateOpaqueRegion();
 791         if (hasVisuals()) {
 792             markDirty();
 793         }
 794     }
 795 
 796     /**
 797      * Makes this node dirty, meaning that it needs to be included in the
 798      * next repaint to the back buffer, and its bounds should be included
 799      * in the dirty region. This flag means that this node itself is dirty.
 800      * In contrast, the childDirty flag indicates that a child of the node
 801      * (maybe a distant child) is dirty. This method does not invalidate the
 802      * cache of this node. However, it ends up walking up the tree marking
 803      * all parents as having a dirty child and also invalidating their caches.
 804      * This method has no effect if the node is already dirty.
 805      */
 806     public final void markDirty() {
 807         if (dirty != DirtyFlag.DIRTY) {
 808             dirty = DirtyFlag.DIRTY;
 809             markTreeDirty();
 810         }
 811     }
 812 
 813     /**
 814      * Mark the node as DIRTY_BY_TRANSLATION. This will call special cache invalidation
 815      */
 816     private void markDirtyByTranslation() {
 817         if (dirty == DirtyFlag.CLEAN) {
 818             if (parent != null && parent.dirty == DirtyFlag.CLEAN && !parent.childDirty) {
 819                 dirty = DirtyFlag.DIRTY_BY_TRANSLATION;
 820                 parent.childDirty = true;
 821                 parent.dirtyChildrenAccumulated++;
 822                 parent.invalidateCacheByTranslation(hint);
 823                 parent.markTreeDirty();
 824             } else {
 825                 markDirty();
 826             }
 827         }
 828     }
 829 
 830     //Mark tree dirty, but make sure this node's
 831     // dirtyChildrenAccumulated has not been incremented.
 832     // Useful when a markTree is called on a node that's not
 833     // the dirty source of change, e.g. group knows it has new child
 834     // or one of it's child has been removed
 835     protected final void markTreeDirtyNoIncrement() {
 836         if (parent != null && (!parent.childDirty || dirty == DirtyFlag.DIRTY_BY_TRANSLATION)) {
 837             markTreeDirty();
 838         }
 839     }
 840 
 841     /**
 842      * Notifies the parent (whether an NGGroup or just a NGNode) that
 843      * a child has become dirty. This walk will continue all the way up
 844      * to the root of the tree. If a node is encountered which is already
 845      * dirty, or which already has childDirty set, then this loop will
 846      * terminate (ie: there is no point going further so we might as well
 847      * just bail). This method ends up invalidating the cache of each
 848      * parent up the tree. Since it is possible for a node to already
 849      * have its dirty bit set, but not have its cache invalidated, this
 850      * method is careful to make sure the first parent it encounters
 851      * which is already marked dirty still has its cache invalidated. If
 852      * this turns out to be expensive due to high occurrence, we can add
 853      * a quick "invalidated" flag to every node (at the cost of yet
 854      * another bit).
 855      */
 856     protected final void markTreeDirty() {
 857         NGNode p = parent;
 858         boolean atClip = isClip;
 859         boolean byTranslation = dirty == DirtyFlag.DIRTY_BY_TRANSLATION;
 860         while (p != null && p.dirty != DirtyFlag.DIRTY && (!p.childDirty || atClip || byTranslation)) {
 861             if (atClip) {
 862                 p.dirty = DirtyFlag.DIRTY;
 863             } else if (!byTranslation) {
 864                 p.childDirty = true;
 865                 p.dirtyChildrenAccumulated++;
 866             }
 867             p.invalidateCache();
 868             atClip = p.isClip;
 869             byTranslation = p.dirty == DirtyFlag.DIRTY_BY_TRANSLATION;
 870             p = p.parent;
 871         }
 872         // if we stopped on a parent that already has dirty children, increase it's
 873         // dirty children count.
 874         // Note that when incrementDirty is false, we don't increment in this case.
 875         if (p != null && p.dirty == DirtyFlag.CLEAN && !atClip && !byTranslation) {
 876             p.dirtyChildrenAccumulated++;
 877         }
 878         // Must make sure this happens. In some cases, a parent might
 879         // already be marked dirty (for example, its opacity may have
 880         // changed) but its cache has not been made invalid. This call
 881         // will make sure it is invalidated in that case
 882         if (p != null) p.invalidateCache();
 883     }
 884 
 885     /**
 886      * Gets whether this SGNode is clean. This will return true only if
 887      * this node and any / all child nodes are clean.
 888      */
 889     public final boolean isClean() {
 890         return dirty == DirtyFlag.CLEAN && !childDirty;
 891     }
 892 
 893     /**
 894      * Clears the dirty flag. This should only happen during rendering.
 895      */
 896     protected void clearDirty() {
 897         dirty = DirtyFlag.CLEAN;
 898         childDirty = false;
 899         dirtyBounds.makeEmpty();
 900         dirtyChildrenAccumulated = 0;
 901     }
 902 
 903     /**
 904      * Walks down the tree clearing the "painted" bits for each node. This is only
 905      * called if we're drawing dirty rectangles or overdraw rectangles.
 906      */
 907     public void clearPainted() {
 908         painted = 0;
 909         if (this instanceof NGGroup) {
 910             List<NGNode> children = ((NGGroup)this).getChildren();
 911             for (int i=0; i<children.size(); i++) {
 912                 children.get(i).clearPainted();
 913             }
 914         }
 915     }
 916 
 917     public void clearDirtyTree() {
 918         clearDirty();
 919         if (getClipNode() != null) {
 920             getClipNode().clearDirtyTree();
 921         }
 922         if (this instanceof NGGroup) {
 923             List<NGNode> children = ((NGGroup) this).getChildren();
 924             for (int i = 0; i < children.size(); ++i) {
 925                 NGNode child = children.get(i);
 926                 if (child.dirty != DirtyFlag.CLEAN || child.childDirty) {
 927                     child.clearDirtyTree();
 928                 }
 929             }
 930         }
 931     }
 932 
 933     /**
 934      * Invalidates the cache, if it is in use. There are several operations
 935      * which need to cause the cached raster to become invalid so that a
 936      * subsequent render operation will result in the cached image being
 937      * reconstructed.
 938      */
 939     protected final void invalidateCache() {
 940         if (cacheFilter != null) {
 941             cacheFilter.invalidate();
 942         }
 943     }
 944 
 945     /**
 946      * Mark the cache as invalid due to a translation of a child. The cache filter
 947      * might use this information for optimizations.
 948      */
 949     protected final void invalidateCacheByTranslation(DirtyHint hint) {
 950         if (cacheFilter != null) {
 951             cacheFilter.invalidateByTranslation(hint.translateXDelta, hint.translateYDelta);
 952         }
 953     }
 954 
 955     /***************************************************************************
 956      *                                                                         *
 957      * Dirty Regions                                                           *
 958      *                                                                         *
 959      * Need to add documentation about dirty regions and how they work. One    *
 960      * thing to be aware of is that during the dirty region accumulation phase *
 961      * we use precise floating point values, but during                        *
 962      *                                                                         *
 963      **************************************************************************/
 964 
 965     /**
 966      * Accumulates and returns the dirty regions in transformed coordinates for
 967      * this node. This method is designed such that a single downward traversal
 968      * of the tree is sufficient to update the dirty regions.
 969      * <p>
 970      * This method only accumulates dirty regions for parts of the tree which lie
 971      * inside the clip since there is no point in accumulating dirty regions which
 972      * lie outside the clip. The returned dirty regions bounds  the same object
 973      * as that passed into the function. The returned dirty regions bounds will
 974      * always be adjusted such that they do not extend beyond the clip.
 975      * <p>
 976      * The given transform is the accumulated transform up to but not including the
 977      * transform of this node.
 978      *
 979      * @param clip must not be null, the clip in scene coordinates, supplied by the
 980      *        rendering system. At most, this is usually the bounds of the window's
 981      *        content area, however it might be smaller.
 982      * @param dirtyRegionTemp must not be null, the dirty region in scene coordinates.
 983      *        When this method is initially invoked by the rendering system, the
 984      *        dirtyRegion should be marked as invalid.
 985      * @param dirtyRegionContainer must not be null, the container of dirty regions in scene
 986      *        coordinates.
 987      * @param tx must not be null, the accumulated transform up to but not
 988      *        including this node's transform. When this method concludes, it must
 989      *        restore this transform if it was changed within the function.
 990      * @param pvTx must not be null, it's the perspective transform of the current
 991      *        perspective camera or identity transform if parallel camera is used.
 992      * @return The dirty region container. If the returned value is null, then that means
 993      *         the clip should be used as the dirty region. This is a special
 994      *         case indicating that there is no more need to walk the tree but
 995      *         we can take a shortcut. Note that returning null is *always*
 996      *         safe. Returning something other than null is simply an
 997      *         optimization for cases where the dirty region is substantially
 998      *         smaller than the clip.
 999      * TODO: Only made non-final for the sake of testing (see javafx-sg-prism tests) (RT-23957)
1000      */
1001     public /*final*/ int accumulateDirtyRegions(final RectBounds clip,
1002                                                 final RectBounds dirtyRegionTemp,
1003                                                 DirtyRegionPool regionPool,
1004                                                 final DirtyRegionContainer dirtyRegionContainer,
1005                                                 final BaseTransform tx,
1006                                                 final GeneralTransform3D pvTx)
1007     {
1008         // This is the main entry point, make sure to check these inputs for validity
1009         if (clip == null || dirtyRegionTemp == null || regionPool == null || dirtyRegionContainer == null ||
1010                 tx == null || pvTx == null) throw new NullPointerException();
1011 
1012         // Even though a node with 0 visibility or 0 opacity doesn't get
1013         // rendered, it may contribute to the dirty bounds, for example, if it
1014         // WAS visible or if it HAD an opacity > 0 last time we rendered then
1015         // we must honor its dirty region. We have front-loaded this work so
1016         // that we don't mark nodes as having dirty flags or dirtyBounds if
1017         // they shouldn't contribute to the dirty region. So we can simply
1018         // treat all nodes, regardless of their opacity or visibility, as
1019         // though their dirty regions matter. They do.
1020 
1021         // If this node is clean then we can simply return the dirty region as
1022         // there is no need to walk any further down this branch of the tree.
1023         // The node is "clean" if neither it, nor its children, are dirty.
1024          if (dirty == DirtyFlag.CLEAN && !childDirty) {
1025              return DirtyRegionContainer.DTR_OK;
1026          }
1027 
1028         // We simply collect this nodes dirty region if it has its dirty flag
1029         // set, regardless of whether it is a group or not. However, if this
1030         // node is not dirty, then we can ask the accumulateGroupDirtyRegion
1031         // method to collect the dirty regions of the children.
1032         if (dirty != DirtyFlag.CLEAN) {
1033             return accumulateNodeDirtyRegion(clip, dirtyRegionTemp, dirtyRegionContainer, tx, pvTx);
1034         } else {
1035             assert childDirty; // this must be true by this point
1036             return accumulateGroupDirtyRegion(clip, dirtyRegionTemp, regionPool,
1037                                               dirtyRegionContainer, tx, pvTx);
1038         }
1039     }
1040 
1041     /**
1042      * Accumulates the dirty region of a node.
1043      * TODO: Only made non-final for the sake of testing (see javafx-sg-prism tests) (RT-23957)
1044      */
1045     int accumulateNodeDirtyRegion(final RectBounds clip,
1046                                   final RectBounds dirtyRegionTemp,
1047                                   final DirtyRegionContainer dirtyRegionContainer,
1048                                   final BaseTransform tx,
1049                                   final GeneralTransform3D pvTx) {
1050 
1051         // Get the dirty bounds of this specific node in scene coordinates
1052         final BaseBounds bb = computeDirtyRegion(dirtyRegionTemp, tx, pvTx);
1053 
1054         // Note: dirtyRegion is strictly a 2D operation. We simply need the largest
1055         // rectangular bounds of bb. Hence the Z-axis projection of bb; taking
1056         // minX, minY, maxX and maxY values from this point on. Also, in many cases
1057         // bb == dirtyRegionTemp. In fact, the only time this won't be true is if
1058         // there is (or was) a perspective transform involved on this node.
1059         if (bb != dirtyRegionTemp) {
1060             bb.flattenInto(dirtyRegionTemp);
1061         }
1062 
1063         // If my dirty region is empty, or if it doesn't intersect with the
1064         // clip, then we can simply return since this node's dirty region is
1065         // not helpful
1066         if (dirtyRegionTemp.isEmpty() || clip.disjoint(dirtyRegionTemp)) {
1067             return DirtyRegionContainer.DTR_OK;
1068         }
1069 
1070         // If the clip is completely contained within the dirty region (including
1071         // if they are equal) then we return DTR_CONTAINS_CLIP
1072         if (dirtyRegionTemp.contains(clip)) {
1073             return DirtyRegionContainer.DTR_CONTAINS_CLIP;
1074         }
1075 
1076         // The only overhead in calling intersectWith, and contains (above) is the repeated checking
1077         // if the isEmpty state. But the code is cleaner and less error prone.
1078         dirtyRegionTemp.intersectWith(clip);
1079 
1080         // Add the dirty region to the container
1081         dirtyRegionContainer.addDirtyRegion(dirtyRegionTemp);
1082 
1083         return DirtyRegionContainer.DTR_OK;
1084     }
1085 
1086     /**
1087      * Accumulates the dirty region of an NGGroup. This is implemented here as opposed to
1088      * using polymorphism because we wanted to centralize all of the dirty region
1089      * management code in one place, rather than having it spread between Prism,
1090      * Scenario, and any other future toolkits.
1091      * TODO: Only made non-final for the sake of testing (see javafx-sg-prism tests) (RT-23957)
1092      */
1093     int accumulateGroupDirtyRegion(final RectBounds clip,
1094                                    final RectBounds dirtyRegionTemp,
1095                                    final DirtyRegionPool regionPool,
1096                                    DirtyRegionContainer dirtyRegionContainer,
1097                                    final BaseTransform tx,
1098                                    final GeneralTransform3D pvTx) {
1099         // We should have only made it to this point if this node has a dirty
1100         // child. If this node itself is dirty, this method never would get called.
1101         // If this node was not dirty and had no dirty children, then this
1102         // method never should have been called. So at this point, the following
1103         // assertions should be correct.
1104         assert childDirty;
1105         assert dirty == DirtyFlag.CLEAN;
1106 
1107         int status = DirtyRegionContainer.DTR_OK;
1108 
1109         if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) {
1110             status = accumulateNodeDirtyRegion(clip, dirtyRegionTemp, dirtyRegionContainer, tx, pvTx);
1111             return status;
1112         }
1113 
1114         // If we got here, then we are following a "bread crumb" trail down to
1115         // some child (perhaps distant) which is dirty. So we need to iterate
1116         // over all the children and accumulate their dirty regions. Before doing
1117         // so we, will save off the transform state and restore it after having
1118         // called all the children.
1119         double mxx = tx.getMxx();
1120         double mxy = tx.getMxy();
1121         double mxz = tx.getMxz();
1122         double mxt = tx.getMxt();
1123 
1124         double myx = tx.getMyx();
1125         double myy = tx.getMyy();
1126         double myz = tx.getMyz();
1127         double myt = tx.getMyt();
1128 
1129         double mzx = tx.getMzx();
1130         double mzy = tx.getMzy();
1131         double mzz = tx.getMzz();
1132         double mzt = tx.getMzt();
1133         BaseTransform renderTx = tx;
1134         if (this.transform != null) renderTx = renderTx.deriveWithConcatenation(this.transform);
1135 
1136         // If this group node has a clip, then we will perform some special
1137         // logic which will cause the dirty region accumulation loops to run
1138         // faster. We already have a system whereby if a node determines that
1139         // its dirty region exceeds that of the clip, it simply returns null,
1140         // short circuiting the accumulation process. We extend that logic
1141         // here by also taking into account the clipNode on the group. If
1142         // there is a clip node, then we will union the bounds of the clip
1143         // node (in boundsInScene space) with the current clip and pass this
1144         // new clip down to the children. If they determine that their dirty
1145         // regions exceed the bounds of this new clip, then they will return
1146         // null. We'll catch that here, and use that information to know that
1147         // we ought to simply accumulate the bounds of this group as if it
1148         // were dirty. This process will do all the other optimizations we
1149         // already have in place for getting the normal dirty region.
1150         RectBounds myClip = clip;
1151         //Save current dirty region so we can fast-reset to (something like) the last state
1152         //and possibly save a few intersects() calls
1153 
1154         DirtyRegionContainer originalDirtyRegion = null;
1155         BaseTransform originalRenderTx = null;
1156         if (effectFilter != null) {
1157             try {
1158                 myClip = new RectBounds();
1159                 BaseBounds myClipBaseBounds = renderTx.inverseTransform(clip, TEMP_BOUNDS);
1160                 myClipBaseBounds.flattenInto(myClip);
1161             } catch (NoninvertibleTransformException ex) {
1162                 return DirtyRegionContainer.DTR_OK;
1163             }
1164 
1165             originalRenderTx = renderTx;
1166             renderTx = BaseTransform.IDENTITY_TRANSFORM;
1167             originalDirtyRegion = dirtyRegionContainer;
1168             dirtyRegionContainer = regionPool.checkOut();
1169         } else if (clipNode != null) {
1170             originalDirtyRegion = dirtyRegionContainer;
1171             myClip = new RectBounds();
1172             BaseBounds clipBounds = clipNode.getCompleteBounds(myClip, renderTx);
1173             pvTx.transform(clipBounds, clipBounds);
1174             clipBounds.flattenInto(myClip);
1175             myClip.intersectWith(clip);
1176             dirtyRegionContainer = regionPool.checkOut();
1177         }
1178 
1179 
1180         //Accumulate also removed children to dirty region.
1181         List<NGNode> removed = ((NGGroup) this).getRemovedChildren();
1182         if (removed != null) {
1183             NGNode removedChild;
1184             for (int i = removed.size() - 1; i >= 0; --i) {
1185                 removedChild = removed.get(i);
1186                 removedChild.dirty = DirtyFlag.DIRTY;
1187                     status = removedChild.accumulateDirtyRegions(myClip,
1188                             dirtyRegionTemp,regionPool, dirtyRegionContainer, renderTx, pvTx);
1189                     if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
1190                         break;
1191                     }
1192             }
1193         }
1194 
1195         List<NGNode> children = ((NGGroup) this).getChildren();
1196         int num = children.size();
1197         for (int i=0; i<num && status == DirtyRegionContainer.DTR_OK; i++) {
1198             NGNode child = children.get(i);
1199             // The child will check the dirty bits itself. If we tested it here
1200             // (as we used to), we are just doing the check twice. True, it might
1201             // mean fewer method calls, but hotspot will probably inline this all
1202             // anyway, and doing the check in one place is less error prone.
1203             status = child.accumulateDirtyRegions(myClip, dirtyRegionTemp, regionPool,
1204                                                   dirtyRegionContainer, renderTx, pvTx);
1205             if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
1206                 break;
1207             }
1208         }
1209 
1210         if (effectFilter != null && status == DirtyRegionContainer.DTR_OK) {
1211             //apply effect on effect dirty regions
1212             applyEffect(effectFilter, dirtyRegionContainer, regionPool);
1213 
1214             if (clipNode != null) {
1215                 myClip = new RectBounds();
1216                 BaseBounds clipBounds = clipNode.getCompleteBounds(myClip, renderTx);
1217                 applyClip(clipBounds, dirtyRegionContainer);
1218             }
1219 
1220             //apply transform on effect dirty regions
1221             applyTransform(originalRenderTx, dirtyRegionContainer);
1222             renderTx = originalRenderTx;
1223 
1224             originalDirtyRegion.merge(dirtyRegionContainer);
1225             regionPool.checkIn(dirtyRegionContainer);
1226         }
1227 
1228         // If the process of applying the transform caused renderTx to not equal
1229         // tx, then there is no point restoring it since it will be a different
1230         // reference and will therefore be gc'd.
1231         if (renderTx == tx) {
1232             tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt);
1233         }
1234 
1235         // If the dirty region is null and there is a clip node specified, then what
1236         // happened is that the dirty region of content within this group exceeded
1237         // the clip of this group, and thus, we should accumulate the bounds of
1238         // this group into the dirty region. If the bounds of the group exceeds
1239         // the bounds of the dirty region, then we end up returning null in the
1240         // end. But the implementation of accumulateNodeDirtyRegion handles this.
1241         if (clipNode != null && effectFilter == null) {
1242             if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
1243                 status = accumulateNodeDirtyRegion(clip, dirtyRegionTemp, originalDirtyRegion, tx, pvTx);
1244             } else {
1245                 originalDirtyRegion.merge(dirtyRegionContainer);
1246             }
1247             regionPool.checkIn(dirtyRegionContainer);
1248         }
1249         return status;
1250     }
1251 
1252     /**
1253      * Computes the dirty region for this Node. The specified region is in
1254      * scene coordinates. The specified tx can be used to convert local bounds
1255      * to scene bounds (it includes everything up to but not including my own
1256      * transform).
1257      *
1258      * @param dirtyRegionTemp A temporary RectBounds that this method can use for scratch.
1259      *                        In the case that no perspective transform occurs, it is best if
1260      *                        the returned BaseBounds is this instance.
1261      * @param tx Any transform that needs to be applied
1262      * @param pvTx must not be null, it's the perspective transform of the current
1263      *        perspective camera or identity transform if parallel camera is used.
1264      */
1265     private BaseBounds computeDirtyRegion(final RectBounds dirtyRegionTemp,
1266                                           final BaseTransform tx,
1267                                           final GeneralTransform3D pvTx)
1268     {
1269         if (cacheFilter != null) {
1270             return cacheFilter.computeDirtyBounds(dirtyRegionTemp, tx, pvTx);
1271         }
1272         // The passed in region is a scratch object that exists for me to use,
1273         // such that I don't have to create a temporary object. So I just
1274         // hijack it right here to start with. Note that any of the calls
1275         // in computeDirtyRegion might end up changing the region instance
1276         // from dirtyRegionTemp (which is a RectBounds) to a BoxBounds if any
1277         // of the other bounds / transforms involve a perspective transformation.
1278         BaseBounds region = dirtyRegionTemp;
1279         if (!dirtyBounds.isEmpty()) {
1280             region = region.deriveWithNewBounds(dirtyBounds);
1281         } else {
1282             // If dirtyBounds is empty, then we will simply set the bounds to
1283             // be the same as the transformedBounds (since that means the bounds
1284             // haven't changed and right now we don't support dirty sub regions
1285             // for generic nodes). This can happen if, for example, this is
1286             // a group with a clip and the dirty area of child nodes within
1287             // the group exceeds the bounds of the clip on the group. Just trust me.
1288             region = region.deriveWithNewBounds(transformedBounds);
1289         }
1290         
1291         // We shouldn't do anything with empty region, as we may accidentally make
1292         // it non empty or turn it into some nonsense (like (-1,-1,0,0) )
1293         if (!region.isEmpty()) {
1294                 // Now that we have the dirty region, we will simply apply the tx
1295                 // to it (after slightly padding it for good luck) to get the scene
1296                 // coordinates for this.
1297                 region = computePadding(region);
1298                 region = tx.transform(region, region);
1299                 region = pvTx.transform(region, region);
1300         }
1301         return region;
1302     }
1303 
1304     /**
1305      * LCD Text creates some painful situations where, due to the LCD text
1306      * algorithm, we end up with some pixels touched that are normally outside
1307      * the bounds. To compensate, we need a hook for NGText to add padding.
1308      */
1309     protected BaseBounds computePadding(BaseBounds region) {
1310         return region;
1311     }
1312 
1313     /**
1314      * Marks if the node has some visuals and that the bounds change
1315      * should be taken into account when using the dirty region.
1316      * This will be false for NGGroup (but not for NGRegion)
1317      * @return true if the node has some visuals
1318      */
1319     protected boolean hasVisuals() {
1320         return true;
1321     }
1322 
1323     /***************************************************************************
1324      *                                                                         *
1325      * Culling                                                                 *
1326      *                                                                         *
1327      **************************************************************************/
1328 
1329     /**
1330      * Culling support for multiple dirty regions.
1331      * Set culling bits for the whole graph.
1332      * @param drc Array of dirty regions. Cannot be null.
1333      * @param tx The transform for this render operation. Cannot be null.
1334      * @param pvTx Perspective camera transformation. Cannot be null.
1335      */
1336     public final void doPreCulling(DirtyRegionContainer drc, BaseTransform tx, GeneralTransform3D pvTx) {
1337         if (drc == null || tx == null || pvTx == null) throw new NullPointerException();
1338         markCullRegions(drc, -1, tx, pvTx);
1339     }
1340 
1341     /**
1342      * Marks placement of the node in dirty region encoded into 2 bit flag:
1343      * 00 - node outside dirty region
1344      * 01 - node intersecting dirty region
1345      * 11 - node completely within dirty region
1346      *
1347      * 32 bits = 15 regions max. * 2 bit each. The first two bits are not used
1348      * because we have a special use case for -1, so they should only be set if
1349      * in that case.
1350      *
1351      * @param drc The array of dirty regions.
1352      * @param cullingRegionsBitsOfParent culling bits of parent. -1 if there's no parent.
1353      * @param tx The transform for this render operation. Cannot be null.
1354      * @param pvTx Perspective camera transform. Cannot be null.
1355      */
1356     void markCullRegions(
1357             DirtyRegionContainer drc,
1358             int cullingRegionsBitsOfParent,
1359             BaseTransform tx,
1360             GeneralTransform3D pvTx) {
1361 
1362         // Spent a long time tracking down how cullingRegionsBitsOfParent works. Note that it is
1363         // not just the parent's bits, but also -1 in the case of the "root", where the root is
1364         // either the actual root, or the root of a sub-render operation such as occurs with
1365         // render-to-texture for effects!
1366 
1367         if (tx.isIdentity()) {
1368             TEMP_BOUNDS.deriveWithNewBounds(transformedBounds);
1369         } else {
1370             tx.transform(transformedBounds, TEMP_BOUNDS);
1371         }
1372 
1373         if (!pvTx.isIdentity()) {
1374             pvTx.transform(TEMP_BOUNDS, TEMP_BOUNDS);
1375         }
1376 
1377         TEMP_BOUNDS.flattenInto(TEMP_RECT_BOUNDS);
1378 
1379         cullingBits = 0;
1380         RectBounds region;
1381         int mask = 0x1; // Check only for intersections
1382         for(int i = 0; i < drc.size(); i++) {
1383             region = drc.getDirtyRegion(i);
1384             if (region == null || region.isEmpty()) {
1385                 break;
1386             }
1387             // For each dirty region, we will check to see if this child
1388             // intersects with the dirty region and whether it contains the
1389             // dirty region. Note however, that we only care to mark those
1390             // child nodes which are inside a group that intersects. We don't
1391             // care about marking child nodes which are within a parent which
1392             // is wholly contained within the dirty region.
1393             if ((cullingRegionsBitsOfParent == -1 || (cullingRegionsBitsOfParent & mask) != 0) &&
1394                     region.intersects(TEMP_RECT_BOUNDS)) {
1395                 int b = DIRTY_REGION_INTERSECTS_NODE_BOUNDS;
1396                 if (region.contains(TEMP_RECT_BOUNDS)) {
1397                     b = DIRTY_REGION_CONTAINS_NODE_BOUNDS;
1398                 }
1399                 cullingBits = cullingBits | (b << (2 * i));
1400             }
1401             mask = mask << 2;
1402         }//for
1403 
1404         // If we are going to cull a node/group that's dirty,
1405         // make sure it's dirty flags are properly cleared.
1406         if (cullingBits == 0 && (dirty != DirtyFlag.CLEAN || childDirty)) {
1407             clearDirtyTree();
1408         }
1409 
1410 //        System.out.printf("%s bits: %s bounds: %s\n",
1411 //            this, Integer.toBinaryString(cullingBits), TEMP_RECT_BOUNDS);
1412     }
1413 
1414     /**
1415      * Fills the given StringBuilder with text representing the structure of the NG graph insofar as dirty
1416      * opts is concerned. Used for debug purposes. This is typically called on the root node. The List of
1417      * roots is the list of dirty roots as determined by successive calls to getRenderRoot for each dirty
1418      * region. The output will be prefixed with a key indicating how to interpret the printout.
1419      *
1420      * @param s A StringBuilder to fill with the output.
1421      * @param roots The list of render roots (may be empty, must not be null).
1422      */
1423     public final void printDirtyOpts(StringBuilder s, List<NGNode> roots) {
1424         s.append("\n*=Render Root\n");
1425         s.append("d=Dirty\n");
1426         s.append("dt=Dirty By Translation\n");
1427         s.append("i=Dirty Region Intersects the NGNode\n");
1428         s.append("c=Dirty Region Contains the NGNode\n");
1429         s.append("ef=Effect Filter\n");
1430         s.append("cf=Cache Filter\n");
1431         s.append("cl=This node is a clip node\n");
1432         s.append("b=Blend mode is set\n");
1433         s.append("or=Opaque Region\n");
1434         printDirtyOpts(s, this, BaseTransform.IDENTITY_TRANSFORM, "", roots);
1435     }
1436 
1437     /**
1438      * Used for debug purposes. Recursively visits all NGNodes and prints those that are possibly part of
1439      * the render operation and annotates each node.
1440      *
1441      * @param s The String builder
1442      * @param node The node that we're printing out information about
1443      * @param tx The transform
1444      * @param prefix Some prefix to put in front of the node output (mostly spacing)
1445      * @param roots The different dirty roots, if any.
1446      */
1447     private final void printDirtyOpts(StringBuilder s, NGNode node, BaseTransform tx, String prefix, List<NGNode> roots) {
1448         if (!node.isVisible() || node.getOpacity() == 0) return;
1449 
1450         BaseTransform copy = tx.copy();
1451         copy = copy.deriveWithConcatenation(node.getTransform());
1452         List<String> stuff = new ArrayList<>();
1453         for (int i=0; i<roots.size(); i++) {
1454             NGNode root = roots.get(i);
1455             if (node == root) stuff.add("*" + i);
1456         }
1457 
1458         if (node.dirty != NGNode.DirtyFlag.CLEAN) {
1459             stuff.add(node.dirty == NGNode.DirtyFlag.DIRTY ? "d" : "dt");
1460         }
1461 
1462         if (node.cullingBits != 0) {
1463             int mask = 0x11;
1464             for (int i=0; i<15; i++) {
1465                 int bits = node.cullingBits & mask;
1466                 if (bits != 0) {
1467                     stuff.add(bits == 1 ? "i" + i : bits == 0 ? "c" + i : "ci" + i);
1468                 }
1469                 mask = mask << 2;
1470             }
1471         }
1472 
1473         if (node.effectFilter != null) stuff.add("ef");
1474         if (node.cacheFilter != null) stuff.add("cf");
1475         if (node.nodeBlendMode != null) stuff.add("b");
1476 
1477         RectBounds opaqueRegion = node.getOpaqueRegion();
1478         if (opaqueRegion != null) {
1479             RectBounds or = new RectBounds();
1480             copy.transform(opaqueRegion, or);
1481             stuff.add("or=" + or.getMinX() + ", " + or.getMinY() + ", " + or.getWidth() + ", " + or.getHeight());
1482         }
1483 
1484         if (stuff.isEmpty()) {
1485             s.append(prefix + node.name + "\n");
1486         } else {
1487             String postfix = " [";
1488             for (int i=0; i<stuff.size(); i++) {
1489                 postfix = postfix + stuff.get(i);
1490                 if (i < stuff.size() - 1) postfix += " ";
1491             }
1492             s.append(prefix + node.name + postfix + "]\n");
1493         }
1494 
1495         if (node.getClipNode() != null) {
1496             printDirtyOpts(s, node.getClipNode(), copy, prefix + "  cl ", roots);
1497         }
1498 
1499         if (node instanceof NGGroup) {
1500             NGGroup g = (NGGroup)node;
1501             for (int i=0; i<g.getChildren().size(); i++) {
1502                 printDirtyOpts(s, g.getChildren().get(i), copy, prefix + "  ", roots);
1503             }
1504         }
1505     }
1506 
1507     /**
1508      * Helper method draws rectangles indicating the overdraw rectangles.
1509      *
1510      * @param tx The scene->parent transform.
1511      * @param pvTx The perspective camera transform.
1512      * @param clipBounds The bounds in scene coordinates
1513      * @param colorBuffer A pixel array where each pixel contains a color indicating how many times
1514      *                    it has been "drawn"
1515      * @param dirtyRegionIndex the index of the dirty region we're gathering information for. This is
1516      *                         needed so we can shift the "painted" field to find out if this node
1517      *                         was drawn in this dirty region.
1518      */
1519     public void drawDirtyOpts(final BaseTransform tx, final GeneralTransform3D pvTx,
1520                               Rectangle clipBounds, int[] colorBuffer, int dirtyRegionIndex) {
1521         if ((painted & (1 << (dirtyRegionIndex * 2))) != 0) {
1522             // Transforming the content bounds (which includes the clip) to screen coordinates
1523             tx.copy().deriveWithConcatenation(getTransform()).transform(contentBounds, TEMP_BOUNDS);
1524             if (pvTx != null) pvTx.transform(TEMP_BOUNDS, TEMP_BOUNDS);
1525             RectBounds bounds = new RectBounds();
1526             TEMP_BOUNDS.flattenInto(bounds);
1527 
1528             // Adjust the bounds so that they are relative to the clip. The colorBuffer is sized
1529             // exactly the same as the clip, and the elements of the colorBuffer represent the
1530             // pixels inside the clip. However the bounds of this node may overlap the clip in
1531             // some manner, so we adjust them such that x, y, w, h will be the adjusted bounds.
1532             assert clipBounds.width * clipBounds.height == colorBuffer.length;
1533             bounds.intersectWith(clipBounds);
1534             int x = (int) bounds.getMinX() - clipBounds.x;
1535             int y = (int) bounds.getMinY() - clipBounds.y;
1536             int w = (int) (bounds.getWidth() + .5);
1537             int h = (int) (bounds.getHeight() + .5);
1538 
1539             if (w == 0 || h == 0) {
1540                 // I would normally say we should never reach this point, as it means something was
1541                 // marked as painted but really couldn't have been.
1542                 return;
1543             }
1544 
1545             // x, y, w, h are 0 based and will fit within the clip, so now we can simply update
1546             // all the pixels that fall within these bounds.
1547             for (int i = y; i < y+h; i++) {
1548                 for (int j = x; j < x+w; j++) {
1549                     final int index = i * clipBounds.width + j;
1550                     int color = colorBuffer[index];
1551 
1552                     // This is kind of a dirty hack. The idea is to show green if 0 or 1
1553                     // times a pixel is drawn, Yellow for 2 or 3 times, and red for more
1554                     // Than that. So I use 0x80007F00 as the first green color, and
1555                     // 0x80008000 as the second green color, but their so close to the same
1556                     // thing you probably won't be able to tell them apart, but I can tell
1557                     // numerically they're different and increment (so I use the colors
1558                     // as my counters).
1559                     if (color == 0) {
1560                         color = 0x8007F00;
1561                     } else if ((painted & (3 << (dirtyRegionIndex * 2))) == 3) {
1562                         switch (color) {
1563                             case 0x80007F00:
1564                                 color = 0x80008000;
1565                                 break;
1566                             case 0x80008000:
1567                                 color = 0x807F7F00;
1568                                 break;
1569                             case 0x807F7F00:
1570                                 color = 0x80808000;
1571                                 break;
1572                             case 0x80808000:
1573                                 color = 0x807F0000;
1574                                 break;
1575                             default:
1576                                 color = 0x80800000;
1577                         }
1578                     }
1579                     colorBuffer[index] = color;
1580                 }
1581             }
1582         }
1583     }
1584 
1585     /***************************************************************************
1586      *                                                                         *
1587      * Identifying render roots                                                *
1588      *                                                                         *
1589      **************************************************************************/
1590     protected static enum RenderRootResult {
1591         /**
1592          * A Node returns NO_RENDER_ROOT when it is not a render root because
1593          * it does not have an opaqueRegion which completely covers the area
1594          * of the clip. Maybe the node is dirty, but outside the dirty region
1595          * that we're currently processing. For an NGGroup, returning
1596          * NO_RENDER_ROOT means that there is no render root (occluder) within
1597          * this entire branch of the tree.
1598          */
1599         NO_RENDER_ROOT,
1600         /**
1601          * A Node returns HAS_RENDER_ROOT when its opaque region completely
1602          * covers the clip. An NGGroup returns HAS_RENDER_ROOT when one of
1603          * its children either returned HAS_RENDER_ROOT or HAS_RENDER_ROOT_AND_IS_CLEAN.
1604          */
1605         HAS_RENDER_ROOT,
1606         /**
1607          * A Node returns HAS_RENDER_ROOT_AND_IS_CLEAN when its opaque region
1608          * completely covers the clip and the Node is, itself, clean. An NGNode
1609          * returns HAS_RENDER_ROOT_AND_IS_CLEAN only if it had a child that
1610          * returned HAS_RENDER_ROOT_AND_IS_CLEAN and none of its children drawn
1611          * above the render root are dirty.
1612          *
1613          * This optimization allows us to recognize situations where perhaps there
1614          * were some dirty nodes, but they are completely covered by an occluder,
1615          * and therefore we don't actually have to draw anything.
1616          */
1617         HAS_RENDER_ROOT_AND_IS_CLEAN,
1618     }
1619 
1620     /**
1621      * Called <strong>after</strong> preCullingBits in order to get the node
1622      * from which we should begin drawing. This is our support for occlusion culling.
1623      * This should only be called on the root node.
1624      *
1625      * If no render root was found, we need to render everything from this root, so the path will contain this node.
1626      * If no rendering is needed (everything dirty is occluded), the path will remain empty
1627      *
1628      * @param path node path to store the node path
1629      */
1630     public final void getRenderRoot(NodePath path, RectBounds dirtyRegion, int cullingIndex,
1631                                     BaseTransform tx, GeneralTransform3D pvTx) {
1632 
1633         // This is the main entry point, make sure to check these inputs for validity
1634         if (path == null || dirtyRegion == null || tx == null || pvTx == null) {
1635             throw new NullPointerException();
1636         }
1637         if (cullingIndex < -1 || cullingIndex > 15) {
1638             throw new IllegalArgumentException("cullingIndex cannot be < -1 or > 15");
1639         }
1640 
1641         // This method must NEVER BE CALLED if the depth buffer is turned on. I don't have a good way to test
1642         // for that because NGNode doesn't have a reference to the scene it is a part of...
1643 
1644         RenderRootResult result = computeRenderRoot(path, dirtyRegion, cullingIndex, tx, pvTx);
1645         if (result == RenderRootResult.NO_RENDER_ROOT) {
1646             // We didn't find any render root, which means that no one node was large enough
1647             // to obscure the entire dirty region (or, possibly, some combination of nodes in an
1648             // NGGroup were not, together, large enough to do the job). So we need to render
1649             // from the root node, which is this node.
1650             path.add(this);
1651         } else if (result == RenderRootResult.HAS_RENDER_ROOT_AND_IS_CLEAN) {
1652             // We've found a render root, and it is clean and everything above it in painter order
1653             // is clean, so actually we have nothing to paint this time around (some stuff must
1654             // have been dirty which is completely occluded by the render root). So we can clear
1655             // the path, which indicates to the caller that nothing needs to be painted.
1656             path.clear();
1657         }
1658     }
1659 
1660     /**
1661      * Searches for the last node that covers all of the specified dirty region with an opaque region,
1662      * in this node's subtree. Such a node can serve as a rendering root as all nodes preceding the node
1663      * will be covered by it.
1664      *
1665      * @param path the NodePath to populate with the path to the render root. Cannot be null.
1666      * @param dirtyRegion the current dirty region. Cannot be null.
1667      * @param cullingIndex index of culling information
1668      * @param tx current transform. Cannot be null.
1669      * @param pvTx current perspective transform. Cannot be null.
1670      * @return The result of visiting this node.
1671      */
1672     RenderRootResult computeRenderRoot(NodePath path, RectBounds dirtyRegion,
1673                                        int cullingIndex, BaseTransform tx, GeneralTransform3D pvTx) {
1674         return computeNodeRenderRoot(path, dirtyRegion, cullingIndex, tx, pvTx);
1675     }
1676 
1677     private static Point2D[] TEMP_POINTS2D_4 =
1678             new Point2D[] { new Point2D(), new Point2D(), new Point2D(), new Point2D() };
1679 
1680     // Whether (px, py) is clockwise or counter-clockwise to a->b
1681     private static int ccw(double px, double py, Point2D a, Point2D b) {
1682         return (int)Math.signum(((b.x - a.x) * (py - a.y)) - (b.y - a.y) * (px - a.x));
1683     }
1684 
1685     private static boolean pointInConvexQuad(double x, double y, Point2D[] rect) {
1686         int ccw01 = ccw(x, y, rect[0], rect[1]);
1687         int ccw12 = ccw(x, y, rect[1], rect[2]);
1688         int ccw23 = ccw(x, y, rect[2], rect[3]);
1689         int ccw31 = ccw(x, y, rect[3], rect[0]);
1690 
1691         // Possible results after this operation:
1692         // 0 -> 0 (0x0)
1693         // 1 -> 1 (0x1)
1694         // -1 -> Integer.MIN_VALUE (0x80000000)
1695         ccw01 ^= (ccw01 >>> 1);
1696         ccw12 ^= (ccw12 >>> 1);
1697         ccw23 ^= (ccw23 >>> 1);
1698         ccw31 ^= (ccw31 >>> 1);
1699 
1700         final int union = ccw01 | ccw12 | ccw23 | ccw31;
1701         // This means all ccw* were either (-1 or 0) or (1 or 0), but not all of them were 0
1702         return union == 0x80000000 || union == 0x1;
1703         // Or alternatively...
1704 //        return (union ^ (union << 31)) < 0;
1705     }
1706 
1707     /**
1708      * Check if this node can serve as rendering root for this dirty region.
1709      *
1710      * @param path the NodePath to populate with the path to the render root. Cannot be null.
1711      * @param dirtyRegion the current dirty region. Cannot be null.
1712      * @param cullingIndex index of culling information, -1 means culling information should not be used
1713      * @param tx current transform. Cannot be null.
1714      * @param pvTx current perspective transform. Cannot be null.
1715      * @return NO_RENDER_ROOT if this node does <em>not</em> have an opaque
1716      *         region that fills the entire dirty region. Returns HAS_RENDER_ROOT
1717      *         if the opaque region fills the dirty region.
1718      */
1719     final RenderRootResult computeNodeRenderRoot(NodePath path, RectBounds dirtyRegion,
1720                                                  int cullingIndex, BaseTransform tx, GeneralTransform3D pvTx) {
1721 
1722         // Nodes outside of the dirty region can be excluded immediately.
1723         // This can be used only if the culling information is provided.
1724         if (cullingIndex != -1) {
1725             final int bits = cullingBits >> (cullingIndex * 2);
1726             if ((bits & DIRTY_REGION_CONTAINS_OR_INTERSECTS_NODE_BOUNDS) == 0x00) {
1727                 return RenderRootResult.NO_RENDER_ROOT;
1728             }
1729         }
1730 
1731         if (!isVisible()) {
1732             return RenderRootResult.NO_RENDER_ROOT;
1733         }
1734 
1735         final RectBounds opaqueRegion = getOpaqueRegion();
1736         if (opaqueRegion == null) return RenderRootResult.NO_RENDER_ROOT;
1737 
1738         final BaseTransform localToParentTx = getTransform();
1739 
1740         BaseTransform localToSceneTx = TEMP_TRANSFORM.deriveWithNewTransform(tx).deriveWithConcatenation(localToParentTx);
1741 
1742         // Now check if the dirty region is fully contained in our opaque region. Suppose the above
1743         // transform included a rotation about Z. In these cases, the transformed
1744         // opaqueRegion might be some non-axis aligned quad. So what we need to do is to check
1745         // that each corner of the dirty region lies within the (potentially rotated) quad
1746         // of the opaqueRegion.
1747         if (checkBoundsInQuad(opaqueRegion, dirtyRegion, localToSceneTx, pvTx)) {
1748             // This node is a render root.
1749             path.add(this);
1750             return isClean() ? RenderRootResult.HAS_RENDER_ROOT_AND_IS_CLEAN : RenderRootResult.HAS_RENDER_ROOT;
1751         }
1752 
1753         return RenderRootResult.NO_RENDER_ROOT;
1754     }
1755 
1756     static boolean checkBoundsInQuad(RectBounds untransformedQuad,
1757             RectBounds innerBounds, BaseTransform tx, GeneralTransform3D pvTx) {
1758 
1759         if (pvTx.isIdentity() && (tx.getType() & ~(BaseTransform.TYPE_TRANSLATION
1760                 | BaseTransform.TYPE_QUADRANT_ROTATION
1761                 | BaseTransform.TYPE_MASK_SCALE)) == 0) {
1762             // If pvTx is identity and there's simple transformation that will result in axis-aligned rectangle,
1763             // we can do a quick test by using bound.contains()
1764             if (tx.isIdentity()) {
1765                 TEMP_BOUNDS.deriveWithNewBounds(untransformedQuad);
1766             } else {
1767                 tx.transform(untransformedQuad, TEMP_BOUNDS);
1768             }
1769 
1770             TEMP_BOUNDS.flattenInto(TEMP_RECT_BOUNDS);
1771 
1772             return TEMP_RECT_BOUNDS.contains(innerBounds);
1773         } else {
1774             TEMP_POINTS2D_4[0].setLocation(untransformedQuad.getMinX(), untransformedQuad.getMinY());
1775             TEMP_POINTS2D_4[1].setLocation(untransformedQuad.getMaxX(), untransformedQuad.getMinY());
1776             TEMP_POINTS2D_4[2].setLocation(untransformedQuad.getMaxX(), untransformedQuad.getMaxY());
1777             TEMP_POINTS2D_4[3].setLocation(untransformedQuad.getMinX(), untransformedQuad.getMaxY());
1778 
1779             for (Point2D p : TEMP_POINTS2D_4) {
1780                 tx.transform(p, p);
1781                 if (!pvTx.isIdentity()) {
1782                     pvTx.transform(p, p);
1783                 }
1784             }
1785 
1786             return (pointInConvexQuad(innerBounds.getMinX(), innerBounds.getMinY(), TEMP_POINTS2D_4)
1787                     && pointInConvexQuad(innerBounds.getMaxX(), innerBounds.getMinY(), TEMP_POINTS2D_4)
1788                     && pointInConvexQuad(innerBounds.getMaxX(), innerBounds.getMaxY(), TEMP_POINTS2D_4)
1789                     && pointInConvexQuad(innerBounds.getMinX(), innerBounds.getMaxY(), TEMP_POINTS2D_4));
1790         }
1791     }
1792 
1793     /**
1794      * Invalidates any cached representation of the opaque region for this node. On the next
1795      * call to getOpaqueRegion, the opaque region will be recalculated. Any changes to state
1796      * which is used in the {@link #hasOpaqueRegion()} call must invoke this method
1797      * or the opaque region calculations will be wrong.
1798      */
1799     protected final void invalidateOpaqueRegion() {
1800         opaqueRegionInvalid = true;
1801         if (isClip) parent.invalidateOpaqueRegion();
1802     }
1803 
1804     /**
1805      * This method exists only for the sake of testing.
1806      * @return value of opaqueRegionInvalid
1807      */
1808     final boolean isOpaqueRegionInvalid() {
1809         return opaqueRegionInvalid;
1810     }
1811 
1812     /**
1813      * Gets the opaque region for this node, if there is one, or returns null.
1814      * @return The opaque region for this node, or null.
1815      */
1816     public final RectBounds getOpaqueRegion() {
1817         // Note that when we invalidate the opaqueRegion of an NGNode, we don't
1818         // walk up the tree or communicate with the parents (unlike dirty flags).
1819         // An NGGroup does not compute an opaqueRegion based on the union of opaque
1820         // regions of its children (although this is a fine idea to consider!). See RT-32441
1821         // If we ever fix RT-32441, we must be sure to handle the case of a Group being used
1822         // as a clip node (such that invalidating a child on the group invalidates the
1823         // opaque region of every node up to the root).
1824 
1825         // Because the Effect classes have no reference to NGNode, they cannot tell the
1826         // NGNode to invalidate the opaque region whenever properties on the Effect that
1827         // would impact the opaqueRegion change. As a result, when an Effect is specified
1828         // on the NGNode, we will always treat it as if it were invalid. A more invasive
1829         // (but better) change would be to give Effect the ability to invalidate the
1830         // NGNode's opaque region when needed.
1831         if (opaqueRegionInvalid || getEffect() != null) {
1832             opaqueRegionInvalid = false;
1833             if (supportsOpaqueRegions() && hasOpaqueRegion()) {
1834                 opaqueRegion = computeOpaqueRegion(opaqueRegion == null ? new RectBounds() : opaqueRegion);
1835                 // If we got a null result then we encountered an error condition where somebody
1836                 // claimed supportsOpaqueRegions and hasOpaqueRegion, but then they
1837                 // returned null! This should never happen, so we have an assert here. However since
1838                 // assertions are disabled at runtime and we want to avoid the NPE, we also perform
1839                 // a null check.
1840                 assert opaqueRegion != null;
1841                 if (opaqueRegion == null) {
1842                     return null;
1843                 }
1844                 // If there is a clip, then we need to determine the opaque region of the clip, and
1845                 // intersect that with our existing opaque region. For example, if I had a rectangle
1846                 // with a circle for its clip (centered over the rectangle), then the result needs to
1847                 // be the circle's opaque region.
1848                 final NGNode clip = getClipNode();
1849                 if (clip != null) {
1850                     final RectBounds clipOpaqueRegion = clip.getOpaqueRegion();
1851                     // Technically a flip/quadrant rotation is allowed as well, but we don't have a convenient
1852                     // way to do that yet.
1853                     if (clipOpaqueRegion == null || (clip.getTransform().getType() & ~(BaseTransform.TYPE_TRANSLATION | BaseTransform.TYPE_MASK_SCALE)) != 0) {
1854                         // RT-25095: If this node has a clip who's opaque region cannot be determined, then
1855                         // we cannot determine any opaque region for this node (in fact, it might not have one).
1856                         // Also, if the transform is something other than identity, scale, or translate then
1857                         // we're just going to bail (sorry, rotate, maybe next time!)
1858                         return opaqueRegion = null;
1859                     }
1860                     // We have to take into account any transform specified on the clip to put
1861                     // it into the same coordinate system as this node
1862                     final BaseBounds b = clip.getTransform().transform(clipOpaqueRegion, TEMP_BOUNDS);
1863                     b.flattenInto(TEMP_RECT_BOUNDS);
1864                     opaqueRegion.intersectWith(TEMP_RECT_BOUNDS);
1865 
1866                 }
1867             } else {
1868                 // The opaqueRegion may have been non-null in the past, but there isn't an opaque region now,
1869                 // so we will nuke it to save some memory
1870                 opaqueRegion = null;
1871             }
1872         }
1873 
1874         return opaqueRegion;
1875     }
1876 
1877     /**
1878      * Gets whether this NGNode supports opaque regions at all. Most node types do not,
1879      * but some do. If an NGNode subclass is written to support opaque regions, it must override
1880      * this method to return true. The subclass must then also override the computeDirtyRegion method
1881      * to return the dirty region, or null if the node in its current state doesn't have one.
1882      * This method is intended to be immutable.
1883      *
1884      * @return Whether this NGNode implementation supports opaque regions. This could also have been
1885      *         implemented via an interface that some NGNodes implemented, but then we'd have instanceof
1886      *         checks which I'd rather avoid.
1887      */
1888     protected boolean supportsOpaqueRegions() { return false; }
1889 
1890     /**
1891      * Called only on NGNode subclasses which override {@link #supportsOpaqueRegions()} to return
1892      * true, this method will return whether or not this NGNode is in a state where it has
1893      * an opaque region to actually return. If this method returns true, a subsequent call to
1894      * {@link #computeOpaqueRegion(com.sun.javafx.geom.RectBounds)} <strong>must</strong> return
1895      * a non-null result. Any state used in the computation of this method, when it changes, must
1896      * result in a call to {@link #invalidateOpaqueRegion()}.
1897      *
1898      * @return Whether this NGNode currently has an opaque region.
1899      */
1900     protected boolean hasOpaqueRegion() {
1901         final NGNode clip = getClipNode();
1902         final Effect effect = getEffect();
1903         return (effect == null || !effect.reducesOpaquePixels()) &&
1904                getOpacity() == 1f &&
1905                (nodeBlendMode == null || nodeBlendMode == Blend.Mode.SRC_OVER) &&
1906                (clip == null ||
1907                (clip.supportsOpaqueRegions() && clip.hasOpaqueRegion()));
1908     }
1909 
1910     /**
1911      * Computes and returns the opaque region for this node. This method
1912      * @param opaqueRegion
1913      * @return
1914      */
1915     protected RectBounds computeOpaqueRegion(RectBounds opaqueRegion) {
1916         return null;
1917     }
1918 
1919     /**
1920      * Returns whether a clip represented by this node can be rendered using
1921      * axis aligned rect clip. The default implementation returns false,
1922      * specific subclasses should override to return true when appropriate.
1923      *
1924      * @return whether this rectangle is axis aligned when rendered given node's
1925      * and rendering transform
1926      */
1927     protected boolean isRectClip(BaseTransform xform, boolean permitRoundedRectangle) {
1928         return false;
1929     }
1930 
1931     /***************************************************************************
1932      *                                                                         *
1933      * Rendering                                                               *
1934      *                                                                         *
1935      **************************************************************************/
1936 
1937     /**
1938      * Render the tree of nodes to the specified G (graphics) object
1939      * descending from this node as the root. This method is designed to avoid
1940      * generated trash as much as possible while descending through the
1941      * render graph while rendering. This is the appropriate method both to
1942      * initiate painting of an entire scene, and for a branch. The NGGroup
1943      * implementation must call this method on each child, not doRender directly.
1944      *
1945      * @param g The graphics object we're rendering to. This must never be null.
1946      */
1947     public final void render(Graphics g) {
1948         if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Nodes visited during render");
1949         // Clear the visuals changed flag
1950         clearDirty();
1951         // If it isn't visible, then punt
1952         if (!visible || opacity == 0f) return;
1953 
1954         // We know that we are going to render this node, so we call the
1955         // doRender method, which subclasses implement to do the actual
1956         // rendering work.
1957         doRender(g);
1958     }
1959 
1960     // This node requires 2D graphics state for rendering
1961     boolean isShape3D() {
1962         return false;
1963     }
1964 
1965     /**
1966      * Invoked only by the final render method. Implementations
1967      * of this method should make sure to save & restore the transform state.
1968      */
1969     protected void doRender(Graphics g) {
1970 
1971         g.setState3D(isShape3D());
1972 
1973         boolean preCullingTurnedOff = false;
1974         if (PrismSettings.dirtyOptsEnabled) {
1975             if (g.hasPreCullingBits()) {
1976                 //preculling bits available
1977                 final int bits = cullingBits >> (g.getClipRectIndex() * 2);
1978                 if ((bits & DIRTY_REGION_CONTAINS_OR_INTERSECTS_NODE_BOUNDS) == 0) {
1979                     // If no culling bits are set for this region, this group
1980                     // does not intersect (nor is covered by) the region
1981                     return;
1982                 } else if ((bits & DIRTY_REGION_CONTAINS_NODE_BOUNDS) != 0) {
1983                     // When this group is fully covered by the region,
1984                     // turn off the culling checks in the subtree, as everything
1985                     // gets rendered
1986                     g.setHasPreCullingBits(false);
1987                     preCullingTurnedOff = true;
1988                 }
1989             }
1990         }
1991 
1992         // save current depth test state
1993         boolean prevDepthTest = g.isDepthTest();
1994 
1995         // Apply Depth test for this node
1996         // (note that this will only be used if we have a depth buffer for the
1997         // surface to which we are rendering)
1998         g.setDepthTest(isDepthTest());
1999 
2000         // save current transform state
2001         BaseTransform prevXform = g.getTransformNoClone();
2002 
2003         double mxx = prevXform.getMxx();
2004         double mxy = prevXform.getMxy();
2005         double mxz = prevXform.getMxz();
2006         double mxt = prevXform.getMxt();
2007 
2008         double myx = prevXform.getMyx();
2009         double myy = prevXform.getMyy();
2010         double myz = prevXform.getMyz();
2011         double myt = prevXform.getMyt();
2012 
2013         double mzx = prevXform.getMzx();
2014         double mzy = prevXform.getMzy();
2015         double mzz = prevXform.getMzz();
2016         double mzt = prevXform.getMzt();
2017 
2018         // filters are applied in the following order:
2019         //   transform
2020         //   blend mode
2021         //   opacity
2022         //   cache
2023         //   clip
2024         //   effect
2025         // The clip must be below the cache filter, as this is expected in the
2026         // CacheFilter in order to apply scrolling optimization
2027         g.transform(getTransform());
2028         // Try to keep track of whether this node was *really* painted. Still an
2029         // approximation, but somewhat more accurate (at least it doesn't include
2030         // groups which don't paint anything themselves).
2031         boolean p = false;
2032         // NOTE: Opt out 2D operations on 3D Shapes, which are not yet handled by Prism
2033         if (!isShape3D() && g instanceof ReadbackGraphics && needsBlending()) {
2034             renderNodeBlendMode(g);
2035             p = true;
2036         } else if (!isShape3D() && getOpacity() < 1f) {
2037             renderOpacity(g);
2038             p = true;
2039         } else if (!isShape3D() && getCacheFilter() != null) {
2040             renderCached(g);
2041             p = true;
2042         } else if (!isShape3D() && getClipNode() != null) {
2043             renderClip(g);
2044             p = true;
2045         } else if (!isShape3D() && getEffectFilter() != null && effectsSupported) {
2046             renderEffect(g);
2047             p = true;
2048         } else {
2049             renderContent(g);
2050             if (PrismSettings.showOverdraw) {
2051                 p = this instanceof NGRegion || !(this instanceof NGGroup);
2052             }
2053         }
2054 
2055         if (preCullingTurnedOff) {
2056             g.setHasPreCullingBits(true);
2057         }
2058 
2059         // restore previous transform state
2060         g.setTransform3D(mxx, mxy, mxz, mxt,
2061                          myx, myy, myz, myt,
2062                          mzx, mzy, mzz, mzt);
2063 
2064         // restore previous depth test state
2065         g.setDepthTest(prevDepthTest);
2066 
2067         if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Nodes rendered");
2068 
2069         // Used for debug purposes. This is not entirely accurate, as it doesn't measure the
2070         // number of times this node drew to the pixels, and in some cases reports a node as
2071         // having been drawn even when it didn't lay down any pixels. We'd need to integrate
2072         // with our shaders or do something much more invasive to get better data here.
2073         if (PrismSettings.showOverdraw) {
2074             if (p) {
2075                 painted |= 3 << (g.getClipRectIndex() * 2);
2076             } else {
2077                 painted |= 1 << (g.getClipRectIndex() * 2);
2078             }
2079         }
2080     }
2081 
2082     /**
2083      * Return true if this node has a blend mode that requires special
2084      * processing.
2085      * Regular nodes can handle null or SRC_OVER just by rendering into
2086      * the existing buffer.
2087      * Groups override this since they must collect their children into
2088      * a single rendering pass if their mode is explicitly SRC_OVER.
2089      * @return true if this node needs special blending support
2090      */
2091     protected boolean needsBlending() {
2092         Blend.Mode mode = getNodeBlendMode();
2093         return (mode != null && mode != Blend.Mode.SRC_OVER);
2094     }
2095 
2096     private void renderNodeBlendMode(Graphics g) {
2097         // The following is safe; curXform will not be mutated below
2098         BaseTransform curXform = g.getTransformNoClone();
2099 
2100         BaseBounds clipBounds = getClippedBounds(new RectBounds(), curXform);
2101         if (clipBounds.isEmpty()) {
2102             clearDirtyTree();
2103             return;
2104         }
2105 
2106         if (!isReadbackSupported(g)) {
2107             if (getOpacity() < 1f) {
2108                 renderOpacity(g);
2109             } else if (getClipNode() != null) {
2110                 renderClip(g);
2111             } else {
2112                 renderContent(g);
2113             }
2114             return;
2115         }
2116 
2117         // TODO: optimize this (RT-26936)
2118         // Extract clip bounds
2119         Rectangle clipRect = new Rectangle(clipBounds);
2120         clipRect.intersectWith(PrEffectHelper.getGraphicsClipNoClone(g));
2121 
2122         // render the node content into the first offscreen image
2123         FilterContext fctx = getFilterContext(g);
2124         PrDrawable contentImg = (PrDrawable)
2125             Effect.getCompatibleImage(fctx, clipRect.width, clipRect.height);
2126         if (contentImg == null) {
2127             clearDirtyTree();
2128             return;
2129         }
2130         Graphics gContentImg = contentImg.createGraphics();
2131         gContentImg.setHasPreCullingBits(g.hasPreCullingBits());
2132         gContentImg.setClipRectIndex(g.getClipRectIndex());
2133         gContentImg.translate(-clipRect.x, -clipRect.y);
2134         gContentImg.transform(curXform);
2135         if (getOpacity() < 1f) {
2136             renderOpacity(gContentImg);
2137         } else if (getCacheFilter() != null) {
2138             renderCached(gContentImg);
2139         } else if (getClipNode() != null) {
2140             renderClip(g);
2141         } else if (getEffectFilter() != null) {
2142             renderEffect(gContentImg);
2143         } else {
2144             renderContent(gContentImg);
2145         }
2146 
2147         // the above image has already been rendered in device space, so
2148         // just translate to the node origin in device space here...
2149         RTTexture bgRTT = ((ReadbackGraphics) g).readBack(clipRect);
2150         PrDrawable bgPrD = PrDrawable.create(fctx, bgRTT);
2151         Blend blend = new Blend(getNodeBlendMode(),
2152                                 new PassThrough(bgPrD, clipRect),
2153                                 new PassThrough(contentImg, clipRect));
2154         CompositeMode oldmode = g.getCompositeMode();
2155         g.setTransform(null);
2156         g.setCompositeMode(CompositeMode.SRC);
2157         PrEffectHelper.render(blend, g, 0, 0, null);
2158         g.setCompositeMode(oldmode);
2159         // transform state will be restored in render() method above...
2160 
2161         Effect.releaseCompatibleImage(fctx, contentImg);
2162         ((ReadbackGraphics) g).releaseReadBackBuffer(bgRTT);
2163     }
2164 
2165     private void renderRectClip(Graphics g, NGRectangle clipNode) {
2166         BaseBounds newClip = clipNode.getShape().getBounds();
2167         if (!clipNode.getTransform().isIdentity()) {
2168             newClip = clipNode.getTransform().transform(newClip, newClip);
2169         }
2170         final BaseTransform curXform = g.getTransformNoClone();
2171         final Rectangle curClip = g.getClipRectNoClone();
2172         newClip = curXform.transform(newClip, newClip);
2173         newClip.intersectWith(PrEffectHelper.getGraphicsClipNoClone(g));
2174         if (newClip.isEmpty() ||
2175             newClip.getWidth() == 0 ||
2176             newClip.getHeight() == 0) {
2177             clearDirtyTree();
2178             return;
2179         }
2180         // REMIND: avoid garbage by changing setClipRect to accept xywh
2181         g.setClipRect(new Rectangle(newClip));
2182         renderForClip(g);
2183         g.setClipRect(curClip);
2184         clipNode.clearDirty(); // as render() is not called on the clipNode,
2185                                // make sure the dirty flags are cleared
2186     }
2187 
2188     void renderClip(Graphics g) {
2189         //  if clip's opacity is 0 there's nothing to render
2190         if (getClipNode().getOpacity() == 0.0) {
2191             clearDirtyTree();
2192             return;
2193         }
2194 
2195         // The following is safe; curXform will not be mutated below
2196         BaseTransform curXform = g.getTransformNoClone();
2197 
2198         BaseBounds clipBounds = getClippedBounds(new RectBounds(), curXform);
2199         if (clipBounds.isEmpty()) {
2200             clearDirtyTree();
2201             return;
2202         }
2203 
2204         if (getClipNode() instanceof NGRectangle) {
2205             // optimized case for rectangular clip
2206             NGRectangle rectNode = (NGRectangle)getClipNode();
2207             if (rectNode.isRectClip(curXform, false)) {
2208                 renderRectClip(g, rectNode);
2209                 return;
2210             }
2211         }
2212 
2213         // TODO: optimize this (RT-26936)
2214         // Extract clip bounds
2215         Rectangle clipRect = new Rectangle(clipBounds);
2216         clipRect.intersectWith(PrEffectHelper.getGraphicsClipNoClone(g));
2217 
2218         if (!curXform.is2D()) {
2219             Rectangle savedClip = g.getClipRect();
2220             g.setClipRect(clipRect);
2221             NodeEffectInput clipInput =
2222                 new NodeEffectInput(getClipNode(),
2223                                     NodeEffectInput.RenderType.FULL_CONTENT);
2224             NodeEffectInput nodeInput =
2225                 new NodeEffectInput(this,
2226                                     NodeEffectInput.RenderType.CLIPPED_CONTENT);
2227             Blend blend = new Blend(Blend.Mode.SRC_IN, clipInput, nodeInput);
2228             PrEffectHelper.render(blend, g, 0, 0, null);
2229             clipInput.flush();
2230             nodeInput.flush();
2231             g.setClipRect(savedClip);
2232             // There may have been some errors in the application of the
2233             // effect and we would not know to what extent the nodes were
2234             // rendered and cleared or left dirty.  clearDirtyTree() will
2235             // clear both this node its clip node, and it will not recurse
2236             // to the children unless they are still marked dirty.  It should
2237             // be cheap if there was no problem and thorough if there was...
2238             clearDirtyTree();
2239             return;
2240         }
2241 
2242         // render the node content into the first offscreen image
2243         FilterContext fctx = getFilterContext(g);
2244         PrDrawable contentImg = (PrDrawable)
2245             Effect.getCompatibleImage(fctx, clipRect.width, clipRect.height);
2246         if (contentImg == null) {
2247             clearDirtyTree();
2248             return;
2249         }
2250         Graphics gContentImg = contentImg.createGraphics();
2251         gContentImg.setExtraAlpha(g.getExtraAlpha());
2252         gContentImg.setHasPreCullingBits(g.hasPreCullingBits());
2253         gContentImg.setClipRectIndex(g.getClipRectIndex());
2254         gContentImg.translate(-clipRect.x, -clipRect.y);
2255         gContentImg.transform(curXform);
2256         renderForClip(gContentImg);
2257 
2258         // render the mask (clipNode) into the second offscreen image
2259         PrDrawable clipImg = (PrDrawable)
2260             Effect.getCompatibleImage(fctx, clipRect.width, clipRect.height);
2261         if (clipImg == null) {
2262             getClipNode().clearDirtyTree();
2263             Effect.releaseCompatibleImage(fctx, contentImg);
2264             return;
2265         }
2266         Graphics gClipImg = clipImg.createGraphics();
2267         gClipImg.translate(-clipRect.x, -clipRect.y);
2268         gClipImg.transform(curXform);
2269         getClipNode().render(gClipImg);
2270 
2271         // the above images have already been rendered in device space, so
2272         // just translate to the node origin in device space here...
2273         g.setTransform(null);
2274         Blend blend = new Blend(Blend.Mode.SRC_IN,
2275                                 new PassThrough(clipImg, clipRect),
2276                                 new PassThrough(contentImg, clipRect));
2277         PrEffectHelper.render(blend, g, 0, 0, null);
2278         // transform state will be restored in render() method above...
2279 
2280         Effect.releaseCompatibleImage(fctx, contentImg);
2281         Effect.releaseCompatibleImage(fctx, clipImg);
2282     }
2283 
2284     void renderForClip(Graphics g) {
2285         if (getEffectFilter() != null) {
2286             renderEffect(g);
2287         } else {
2288             renderContent(g);
2289         }
2290     }
2291 
2292     private void renderOpacity(Graphics g) {
2293         if (getEffectFilter() != null ||
2294             getCacheFilter() != null ||
2295             getClipNode() != null ||
2296             !hasOverlappingContents())
2297         {
2298             // if the node has a non-null effect or cached==true, we don't
2299             // need to bother rendering to an offscreen here because the
2300             // contents will be flattened as part of rendering the effect
2301             // (or creating the cached image)
2302             float ea = g.getExtraAlpha();
2303             g.setExtraAlpha(ea*getOpacity());
2304             if (getCacheFilter() != null) {
2305                 renderCached(g);
2306             } else if (getClipNode() != null) {
2307                 renderClip(g);
2308             } else if (getEffectFilter() != null) {
2309                 renderEffect(g);
2310             } else {
2311                 renderContent(g);
2312             }
2313             g.setExtraAlpha(ea);
2314             return;
2315         }
2316 
2317         FilterContext fctx = getFilterContext(g);
2318         BaseTransform curXform = g.getTransformNoClone();
2319         BaseBounds bounds = getContentBounds(new RectBounds(), curXform);
2320         Rectangle r = new Rectangle(bounds);
2321         r.intersectWith(PrEffectHelper.getGraphicsClipNoClone(g));
2322         PrDrawable img = (PrDrawable)
2323             Effect.getCompatibleImage(fctx, r.width, r.height);
2324         if (img == null) {
2325             return;
2326         }
2327         Graphics gImg = img.createGraphics();
2328         gImg.setHasPreCullingBits(g.hasPreCullingBits());
2329         gImg.setClipRectIndex(g.getClipRectIndex());
2330         gImg.translate(-r.x, -r.y);
2331         gImg.transform(curXform);
2332         renderContent(gImg);
2333         // img contents have already been rendered in device space, so
2334         // just translate to the node origin in device space here...
2335         g.setTransform(null);
2336         float ea = g.getExtraAlpha();
2337         g.setExtraAlpha(getOpacity()*ea);
2338         g.drawTexture(img.getTextureObject(), r.x, r.y, r.width, r.height);
2339         g.setExtraAlpha(ea);
2340         // transform state will be restored in render() method above...
2341         Effect.releaseCompatibleImage(fctx, img);
2342     }
2343 
2344     private void renderCached(Graphics g) {
2345         // We will punt on 3D completely for caching.
2346         // The first check is for any of its children contains a 3D Transform.
2347         // The second check is for any of its parents and itself has a 3D Transform
2348         // The third check is for the printing case, which doesn't use cached
2349         // bitmaps for the screen and for which there is no cacheFilter.
2350         if (isContentBounds2D() && g.getTransformNoClone().is2D() &&
2351                 !(g instanceof com.sun.prism.PrinterGraphics)) {
2352             getCacheFilter().render(g);
2353         } else {
2354             renderContent(g);
2355         }
2356     }
2357 
2358     protected void renderEffect(Graphics g) {
2359         getEffectFilter().render(g);
2360     }
2361 
2362     protected abstract void renderContent(Graphics g);
2363 
2364     protected abstract boolean hasOverlappingContents();
2365 
2366     /***************************************************************************
2367      *                                                                         *
2368      *                       Static Helper Methods.                            *
2369      *                                                                         *
2370      **************************************************************************/
2371 
2372     boolean isReadbackSupported(Graphics g) {
2373         return ((g instanceof ReadbackGraphics) &&
2374                 ((ReadbackGraphics) g).canReadBack());
2375     }
2376 
2377     /***************************************************************************
2378      *                                                                         *
2379      *                      Filters (Cache, Effect, etc).                      *
2380      *                                                                         *
2381      **************************************************************************/
2382 
2383     static FilterContext getFilterContext(Graphics g) {
2384         Screen s = g.getAssociatedScreen();
2385         if (s == null) {
2386             return PrFilterContext.getPrinterContext(g.getResourceFactory());
2387         } else {
2388             return PrFilterContext.getInstance(s);
2389         }
2390     }
2391 
2392     /**
2393      * A custom effect implementation that has a filter() method that
2394      * simply wraps the given pre-rendered PrDrawable in an ImageData
2395      * and returns that result.  This is only used by the renderClip()
2396      * implementation so we cut some corners here (for example, we assume
2397      * that the given PrDrawable image is already in device space).
2398      */
2399     private static class PassThrough extends Effect {
2400         private PrDrawable img;
2401         private Rectangle bounds;
2402 
2403         PassThrough(PrDrawable img, Rectangle bounds) {
2404             this.img = img;
2405             this.bounds = bounds;
2406         }
2407 
2408         @Override
2409         public ImageData filter(FilterContext fctx,
2410                                 BaseTransform transform,
2411                                 Rectangle outputClip,
2412                                 Object renderHelper,
2413                                 Effect defaultInput)
2414         {
2415             return new ImageData(fctx, img, new Rectangle(bounds));
2416         }
2417 
2418         @Override
2419         public RectBounds getBounds(BaseTransform transform,
2420                                   Effect defaultInput)
2421         {
2422             return new RectBounds(bounds);
2423         }
2424 
2425         @Override
2426         public AccelType getAccelType(FilterContext fctx) {
2427             return AccelType.INTRINSIC;
2428         }
2429 
2430         @Override
2431         public boolean reducesOpaquePixels() {
2432             return false;
2433         }
2434 
2435         @Override
2436         public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
2437             return null; //Never called
2438         }
2439     }
2440 
2441     /***************************************************************************
2442      *                                                                         *
2443      * Stuff                                                                   *
2444      *                                                                         *
2445      **************************************************************************/
2446 
2447     public void release() {
2448     }
2449 
2450     @Override public String toString() {
2451         return name == null ? super.toString() : name;
2452     }
2453 
2454     public void applyTransform(final BaseTransform tx, DirtyRegionContainer drc) {
2455         for (int i = 0; i < drc.size(); i++) {
2456             drc.setDirtyRegion(i, (RectBounds) tx.transform(drc.getDirtyRegion(i), drc.getDirtyRegion(i)));
2457             if (drc.checkAndClearRegion(i)) {
2458                 --i;
2459             }
2460         }
2461     }
2462 
2463     public void applyClip(final BaseBounds clipBounds, DirtyRegionContainer drc) {
2464         for (int i = 0; i < drc.size(); i++) {
2465             drc.getDirtyRegion(i).intersectWith(clipBounds);
2466             if (drc.checkAndClearRegion(i)) {
2467                 --i;
2468             }
2469         }
2470     }
2471     
2472     public void applyEffect(final EffectFilter effectFilter, DirtyRegionContainer drc, DirtyRegionPool regionPool) {
2473         Effect effect = effectFilter.getEffect();
2474         EffectDirtyBoundsHelper helper = EffectDirtyBoundsHelper.getInstance();
2475         helper.setInputBounds(contentBounds);
2476         helper.setDirtyRegions(drc);
2477         final DirtyRegionContainer effectDrc = effect.getDirtyRegions(helper, regionPool);
2478         drc.deriveWithNewContainer(effectDrc);
2479         regionPool.checkIn(effectDrc);
2480     }
2481 
2482     private static class EffectDirtyBoundsHelper extends Effect {
2483         private BaseBounds bounds;
2484         private static EffectDirtyBoundsHelper instance = null;
2485         private DirtyRegionContainer drc;
2486 
2487         public void setInputBounds(BaseBounds inputBounds) {
2488             bounds = inputBounds;
2489         }
2490 
2491         @Override
2492         public ImageData filter(FilterContext fctx,
2493                 BaseTransform transform,
2494                 Rectangle outputClip,
2495                 Object renderHelper,
2496                 Effect defaultInput) {
2497             throw new UnsupportedOperationException();
2498         }
2499 
2500         @Override
2501         public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
2502             if (bounds.getBoundsType() == BaseBounds.BoundsType.RECTANGLE) {
2503                 return bounds;
2504             } else {
2505                 //RT-29453 - CCE: in case we get 3D bounds we need to "flatten" them
2506                 return new RectBounds(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY());
2507             }
2508         }
2509 
2510         @Override
2511         public Effect.AccelType getAccelType(FilterContext fctx) {
2512             return null;
2513         }
2514 
2515         public static EffectDirtyBoundsHelper getInstance() {
2516             if (instance == null) {
2517                 instance = new EffectDirtyBoundsHelper();
2518             }
2519             return instance;
2520         }
2521 
2522         @Override
2523         public boolean reducesOpaquePixels() {
2524             return true;
2525         }
2526 
2527         private void setDirtyRegions(DirtyRegionContainer drc) {
2528             this.drc = drc;
2529         }
2530 
2531         @Override
2532         public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
2533             DirtyRegionContainer ret = regionPool.checkOut();
2534             ret.deriveWithNewContainer(drc);
2535 
2536             return ret;
2537         }
2538 
2539     }
2540 }