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