changeset 4283:dea00f4f6753

RT-31581: BaseNode should be combined with NGNodeReviewed by: Steve Northover and Martin Sladecek
author rbair
date Fri, 12 Jul 2013 08:35:56 -0700
parents 691071d37f9d
children ddbfe8ed6e7b
files .idea/codeStyleSettings.xml modules/graphics/src/main/java/com/sun/javafx/sg/BaseCacheFilter.java modules/graphics/src/main/java/com/sun/javafx/sg/BaseEffectFilter.java modules/graphics/src/main/java/com/sun/javafx/sg/BaseNode.java modules/graphics/src/main/java/com/sun/javafx/sg/BaseNodeEffectInput.java modules/graphics/src/main/java/com/sun/javafx/sg/DirtyHint.java modules/graphics/src/main/java/com/sun/javafx/sg/GrowableDataBuffer.java modules/graphics/src/main/java/com/sun/javafx/sg/MediaFrameTracker.java modules/graphics/src/main/java/com/sun/javafx/sg/NodePath.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/BaseCacheFilter.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/BaseEffectFilter.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/BaseNodeEffectInput.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/DirtyHint.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/GrowableDataBuffer.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/MediaFrameTracker.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGArc.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGCanvas.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGExternalNode.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGGroup.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGLightBase.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGNode.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPath.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPhongMaterial.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGRegion.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGShape.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGShape3D.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NodeEffectInput.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/NodePath.java modules/graphics/src/main/java/com/sun/javafx/sg/prism/ShapeEvaluator.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/AbstractPainter.java modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java modules/graphics/src/main/java/javafx/scene/canvas/Canvas.java modules/graphics/src/main/java/javafx/scene/canvas/GraphicsContext.java modules/graphics/src/test/java/com/sun/javafx/sg/ContentBoundsTest.java modules/graphics/src/test/java/com/sun/javafx/sg/CullingTest.java modules/graphics/src/test/java/com/sun/javafx/sg/EffectDirtyRegionTest.java modules/graphics/src/test/java/com/sun/javafx/sg/prism/ContentBoundsTest.java modules/graphics/src/test/java/com/sun/javafx/sg/prism/CullingTest.java modules/graphics/src/test/java/com/sun/javafx/sg/prism/DirtyRegionTestBase.java modules/graphics/src/test/java/com/sun/javafx/sg/prism/EffectDirtyRegionTest.java modules/graphics/src/test/java/com/sun/javafx/sg/prism/OcclusionCullingTest.java
diffstat 41 files changed, 3598 insertions(+), 3708 deletions(-) [+]
line wrap: on
line diff
--- a/.idea/codeStyleSettings.xml	Fri Jul 12 14:51:13 2013 +0200
+++ b/.idea/codeStyleSettings.xml	Fri Jul 12 08:35:56 2013 -0700
@@ -2,7 +2,11 @@
 <project version="4">
   <component name="ProjectCodeStyleSettingsManager">
     <option name="PER_PROJECT_SETTINGS">
-      <value />
+      <value>
+        <XML>
+          <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
+        </XML>
+      </value>
     </option>
   </component>
 </project>
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/BaseCacheFilter.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,618 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import javafx.scene.CacheHint;
-import com.sun.javafx.geom.Rectangle;
-import com.sun.javafx.geom.transform.Affine2D;
-import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.scenario.effect.FilterContext;
-import com.sun.scenario.effect.Filterable;
-import com.sun.scenario.effect.ImageData;
-
-/*
- * Base implementation of the Node.cache and cacheHint APIs.
- *
- * When all or a portion of the cacheHint becomes enabled, we should try *not*
- * to re-render the cache.  This avoids a big hiccup at the beginning of the
- * "use SPEED only while animating" use case:
- *   0) Under DEFAULT, we should already have a cached image
- *   1) scale/rotate caching is enabled (no expensive re-render required)
- *   2) animation happens, using the cached image
- *   3) animation completes, caching is disable and the node is re-rendered (at
- *      full-fidelity) with the final transform.
- *
- * Certain transform combinations are not supported, notably scaling by unequal
- * amounts in the x and y directions while also rotating.  Other than simple
- * translation, animations in this case will require re-rendering every frame.
- *
- * Ideally, a simple change to a Node's translation should never regenerate the
- * cached image.
- */
-public abstract class BaseCacheFilter {
-    private double lastXDelta;
-    private double lastYDelta;
-    
-    private static final Rectangle TEMP_RECT = new Rectangle();
-
-    /**
-     * Compute the dirty region that must be re-rendered after scrolling
-     */
-    private Rectangle computeDirtyRegionForTranslate() {
-        if (lastXDelta != 0) {
-            if (lastXDelta > 0) {
-                TEMP_RECT.setBounds(0, 0, (int)lastXDelta, cacheBounds.height);
-            } else {
-                TEMP_RECT.setBounds(cacheBounds.width + (int)lastXDelta, 0, -(int)lastXDelta, cacheBounds.height);
-            }
-        } else {
-            if (lastYDelta > 0) {
-                TEMP_RECT.setBounds(0, 0, cacheBounds.width, (int)lastYDelta);
-            } else {
-                TEMP_RECT.setBounds(0, cacheBounds.height + (int)lastYDelta, cacheBounds.width, -(int)lastYDelta);
-            }
-        }
-        return TEMP_RECT;
-    }
-
-    private static enum ScrollCacheState {
-        CHECKING_PRECONDITIONS,
-        ENABLED,
-        DISABLED
-    }
-    private ScrollCacheState scrollCacheState = ScrollCacheState.CHECKING_PRECONDITIONS;
-    
-    // Note: this ImageData is always created and assumed to be untransformed.
-    protected ImageData cachedImageData;
-    private Rectangle cacheBounds = new Rectangle();
-    
-
-    // Used to draw into the cache
-    private Affine2D cachedXform = new Affine2D();
-
-    // The scale and rotate used to draw into the cache
-    private double cachedScaleX;
-    private double cachedScaleY;
-    private double cachedRotate;
-
-    protected double cachedX;
-    protected double cachedY;
-    protected BaseNode node;
-
-    // Used to draw the cached image to the screen
-    protected Affine2D screenXform = new Affine2D();
-
-    // Cache hint settings
-    private boolean scaleHint;
-    private boolean rotateHint;
-    // We keep this around for the sake of matchesHint
-    private CacheHint cacheHint;
-
-    // Was the last paint unsupported by the cache?  If so, will need to
-    // regenerate the cache next time.
-    private boolean wasUnsupported = false;
-
-    // Fun with floating point
-    private static final double EPSILON = 0.0000001;
-
-    protected BaseCacheFilter(BaseNode node, CacheHint cacheHint) {
-        this.node = node;
-        this.scrollCacheState = ScrollCacheState.CHECKING_PRECONDITIONS;
-        setHint(cacheHint);
-    }
-
-    public void setHint(CacheHint cacheHint) {
-        this.cacheHint = cacheHint;
-        this.scaleHint = (cacheHint == CacheHint.SCALE ||
-                          cacheHint == CacheHint.SCALE_AND_ROTATE);
-        this.rotateHint = (cacheHint == CacheHint.ROTATE ||
-                           cacheHint == CacheHint.SCALE_AND_ROTATE);
-    }
-
-    /**
-     * Indicates whether this BaseCacheFilter's hint matches the CacheHint
-     * passed in.
-     */
-    boolean matchesHint(CacheHint cacheHint) {
-        return this.cacheHint == cacheHint;
-    }
-
-    /**
-     * Implemented by concrete subclasses to create the ImageData for the bitmap
-     * cache.
-     */
-    protected abstract ImageData impl_createImageData(FilterContext fctx,
-                                                      Rectangle bounds);
-    
-    /**
-     * The bounds of the cache
-     */
-    protected abstract Rectangle impl_getCacheBounds(Rectangle bounds, BaseTransform xform);
-    
-    /**
-     * Render node to cache
-     */
-    protected abstract void impl_renderNodeToCache(ImageData imageData,
-                                                   Rectangle cacheBounds,
-                                                   BaseTransform xform,
-                                                   Rectangle dirtyBounds);
-
-    /**
-     * Called on concrete subclasses to render the node directly to the screen,
-     * in the case that the cached image is unexpectedly null.  See RT-6428.
-     */
-    protected abstract void impl_renderNodeToScreen(Object implGraphics,
-                                                    BaseTransform xform);
-
-    /**
-     * Called on concrete subclasses to render the cached image to the screen,
-     * translated by mxt, myt.
-     */
-    protected abstract void impl_renderCacheToScreen(Object implGraphics,
-                                                Filterable implImage,
-                                                double mxt, double myt);
-    
-    /**
-     * Is it possible to use scroll optimization?
-     */
-    protected abstract boolean impl_scrollCacheCapable();
-    
-    /**
-     * Move the subregion of the cache by specified delta
-     */
-    protected abstract void impl_moveCacheBy(ImageData cachedImageData,
-            double lastXDelta, double lastYDelta);
-
-    /**
-     * Render the cached node to the screen, updating the cached image as
-     * necessary given the current cacheHint and specified xform.
-     */
-    public void render(Object implGraphics, BaseTransform xform,
-                       FilterContext fctx) {
-
-        // Note: xform should not be modified, for the sake of Prism
-
-        double mxx = xform.getMxx();
-        double myx = xform.getMyx();
-        double mxy = xform.getMxy();
-        double myy = xform.getMyy();
-        double mxt = xform.getMxt();
-        double myt = xform.getMyt();
-
-        double[] xformInfo = unmatrix(xform);
-        boolean isUnsupported = unsupported(xformInfo);
-
-        
-        lastXDelta = lastXDelta * xformInfo[0];
-        lastYDelta = lastYDelta * xformInfo[1];
-
-        if (cachedImageData != null) {
-            Filterable implImage = cachedImageData.getUntransformedImage();
-            if (implImage != null) {
-                implImage.lock();
-                if (!cachedImageData.validate(fctx)) {
-                    implImage.unlock();
-                    invalidate();
-                }
-            }
-        }
-        if (needToRenderCache(fctx, xform, xformInfo)) {
-            if (cachedImageData != null) {
-                Filterable implImage = cachedImageData.getUntransformedImage();
-                if (implImage != null) {
-                    implImage.unlock();
-                }
-                invalidate();
-            }
-            // Update the cachedXform to the current xform (ignoring translate).
-            cachedXform.setTransform(mxx, myx, mxy, myy, 0.0, 0.0);
-            cachedScaleX = xformInfo[0];
-            cachedScaleY = xformInfo[1];
-            cachedRotate = xformInfo[2];
-            
-            cacheBounds = impl_getCacheBounds(cacheBounds, cachedXform);
-            cachedImageData = impl_createImageData(fctx, cacheBounds);
-            impl_renderNodeToCache(cachedImageData, cacheBounds, cachedXform, null);
-
-            // cachedBounds includes effects, and is in *scene* coords
-            Rectangle cachedBounds = cachedImageData.getUntransformedBounds();
-
-            // Save out the (un-transformed) x & y coordinates.  This accounts
-            // for effects and other reasons the untranslated location may not
-            // be 0,0.
-            cachedX = cachedBounds.x;
-            cachedY = cachedBounds.y;
-
-            // screenXform is always identity in this case, as we've just
-            // rendered into the cache using the render xform.
-            screenXform.setTransform(BaseTransform.IDENTITY_TRANSFORM);
-        } else {
-            if (scrollCacheState == ScrollCacheState.ENABLED && 
-                    (lastXDelta != 0 || lastYDelta != 0) ) {
-                impl_moveCacheBy(cachedImageData, lastXDelta, lastYDelta);
-                impl_renderNodeToCache(cachedImageData, cacheBounds, cachedXform, computeDirtyRegionForTranslate());
-                lastXDelta = lastYDelta = 0;
-            }
-            // Using the cached image; calculate screenXform to paint to screen.
-            if (isUnsupported) {
-                // Only way we should be using the cached image in the
-                // unsupported case is for a change in translate only.  No other
-                // xform should be needed, so use identity.
-
-                // TODO: assert cachedXform == render xform (ignoring translate)
-                //   or  assert xforminfo == cachedXform info (RT-23962)
-                screenXform.setTransform(BaseTransform.IDENTITY_TRANSFORM);
-            } else {
-                updateScreenXform(xformInfo);
-            }
-        }
-        // If this render is unsupported, remember for next time.  We'll need
-        // to regenerate the cache once we're in a supported scenario again.
-        wasUnsupported = isUnsupported;
-
-        Filterable implImage = cachedImageData.getUntransformedImage();
-        if (implImage == null) {
-            impl_renderNodeToScreen(implGraphics, xform);
-        } else {
-            impl_renderCacheToScreen(implGraphics, implImage, mxt, myt);
-            implImage.unlock();
-        }
-    }
-
-    /**
-     * Are we attempting to use cache for an unsupported transform mode?  Mostly
-     * this is for trying to rotate while scaling the object by different
-     * amounts in the x and y directions (this also includes shearing).
-     */
-    boolean unsupported(double[] xformInfo) {
-        double scaleX = xformInfo[0];
-        double scaleY = xformInfo[1];
-        double rotate = xformInfo[2];
-
-        // If we're trying to rotate...
-        if (rotate > EPSILON || rotate < -EPSILON) {
-            // ...and if scaleX != scaleY.  This can be in the render xform, or
-            // may have made it into the cached image.
-            if (scaleX > scaleY + EPSILON || scaleY > scaleX + EPSILON ||
-                scaleX < scaleY - EPSILON || scaleY < scaleX - EPSILON ||
-                cachedScaleX > cachedScaleY + EPSILON ||
-                cachedScaleY > cachedScaleX + EPSILON ||
-                cachedScaleX < cachedScaleY - EPSILON ||
-                cachedScaleY < cachedScaleX - EPSILON ) {
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean isXformScrollCacheCapable(double[] xformInfo) {
-        if (unsupported(xformInfo)) {
-            return false;
-        }
-        double rotate = xformInfo[2];
-        return rotateHint || rotate == 0;
-    }
-
-    /*
-     * Do we need to regenerate the cached image?
-     * Assumes that caller locked and validated the cachedImageData.untximage
-     * if not null...
-     */
-    private boolean needToRenderCache(FilterContext fctx, BaseTransform renderXform,
-                                      double[] xformInfo) {
-        if (cachedImageData == null) {
-            return true;
-        }
-        
-        if (lastXDelta != 0 || lastYDelta != 0) {
-            if (Math.abs(lastXDelta) >= cacheBounds.width || Math.abs(lastYDelta) >= cacheBounds.height ||
-                    Math.rint(lastXDelta) != lastXDelta || Math.rint(lastYDelta) != lastYDelta) {
-                node.clearDirtyTree(); // Need to clear dirty (by translation) flags in the children
-                lastXDelta = lastYDelta = 0;
-                return true;
-            }
-            if (scrollCacheState == ScrollCacheState.CHECKING_PRECONDITIONS) {
-                if (impl_scrollCacheCapable() && isXformScrollCacheCapable(xformInfo)) {
-                    scrollCacheState = ScrollCacheState.ENABLED;
-                } else {
-                    scrollCacheState = ScrollCacheState.DISABLED;
-                    return true;
-                }
-            }
-        }
-        
-        // TODO: is == sufficient for floating point comparison here? (RT-23963)
-        if (cachedXform.getMxx() == renderXform.getMxx() &&
-            cachedXform.getMyy() == renderXform.getMyy() &&
-            cachedXform.getMxy() == renderXform.getMxy() &&
-            cachedXform.getMyx() == renderXform.getMyx()) {
-            // It's just a translation - use cached Image
-            return false;
-        }
-        // Not just a translation - if was or is unsupported, then must rerender
-        if (wasUnsupported || unsupported(xformInfo)) {
-            return true;
-        }
-
-        double scaleX = xformInfo[0];
-        double scaleY = xformInfo[1];
-        double rotate = xformInfo[2];
-        if (scaleHint) {
-            if (rotateHint) {
-                return false;
-            } else {
-                // Not caching for rotate: regenerate cache if rotate changed
-                if (cachedRotate - EPSILON < rotate && rotate < cachedRotate + EPSILON) {
-                    return false;
-                } else {
-                    return true;
-                }
-            }
-        } else {
-            if (rotateHint) {
-                // Not caching for scale: regenerate cache if scale changed
-                if (cachedScaleX - EPSILON < scaleX && scaleX < cachedScaleX + EPSILON &&
-                    cachedScaleY - EPSILON < scaleY && scaleY < cachedScaleY + EPSILON) {
-                    return false;
-                } else {// Scale is not "equal enough" - regenerate
-                    return true;
-                }
-            }
-            else { // Not caching for anything; always regenerate
-                return true;
-            }
-        }
-    }
-
-    /*
-     * Given the new xform info, update the screenXform as needed to correctly
-     * paint the cache to the screen.
-     */
-    void updateScreenXform(double[] xformInfo) {
-        // screenXform will be the difference between the cachedXform and the
-        // render xform.
-
-        if (scaleHint) {
-            if (rotateHint) {
-                double screenScaleX = xformInfo[0] / cachedScaleX;
-                double screenScaleY = xformInfo[1] / cachedScaleY;
-                double screenRotate = xformInfo[2] - cachedRotate;
-
-                screenXform.setToScale(screenScaleX, screenScaleY);
-                screenXform.rotate(screenRotate);
-            } else {
-                double screenScaleX = xformInfo[0] / cachedScaleX;
-                double screenScaleY = xformInfo[1] / cachedScaleY;
-                screenXform.setToScale(screenScaleX, screenScaleY);
-            }
-        } else {
-            if (rotateHint) {
-                double screenRotate = xformInfo[2] - cachedRotate;
-                screenXform.setToRotation(screenRotate, 0.0, 0.0);
-            } else {
-                // No caching, cache already rendered with xform; just paint it
-                screenXform.setTransform(BaseTransform.IDENTITY_TRANSFORM);
-            }
-        }
-    }
-
-    public void invalidate() {
-        if (scrollCacheState == ScrollCacheState.ENABLED) {
-            scrollCacheState = ScrollCacheState.CHECKING_PRECONDITIONS;
-        }
-        imageDataUnref();
-        lastXDelta = lastYDelta = 0;
-    }
-
-    protected void imageDataUnref() {
-        if (cachedImageData != null) {
-            // While we hold on to this ImageData we leave the texture
-            // unlocked so it can be reclaimed, but the default unref()
-            // method assumes it was locked.
-            Filterable implImage = cachedImageData.getUntransformedImage();
-            if (implImage != null) {
-                implImage.lock();
-            }
-            cachedImageData.unref();
-            cachedImageData = null;
-        }
-    }
-    
-    void invalidateByTranslation(double translateXDelta, double translateYDelta) {
-        if (cachedImageData == null) {
-            return;
-        }
-
-        if (scrollCacheState == ScrollCacheState.DISABLED) {
-            imageDataUnref();
-        } else {
-             // When both mxt and myt change, we don't currently use scroll optimization
-            if (translateXDelta != 0 && translateYDelta != 0) {
-                imageDataUnref();
-            } else {
-                lastYDelta = translateYDelta;
-                lastXDelta = translateXDelta;
-            }
-        }
-    }
-
-    public void dispose() {
-        invalidate();
-        node = null;
-    }
-
-    /*
-     * unmatrix() and the supporting functions are based on the code from
-     * "Decomposing A Matrix Into Simple Transformations" by Spencer W. Thomas
-     * from Graphics Gems II, as found at
-     * http://tog.acm.org/resources/GraphicsGems/
-     * which states, "All code here can be used without restrictions."
-     *
-     * The code was reduced from handling a 4x4 matrix (3D w/ perspective)
-     * to handle just a 2x2 (2D scale/rotate, w/o translate, as that is handled
-     * separately).
-     */
-
-    /**
-     * Given a BaseTransform, decompose it into values for scaleX, scaleY and
-     * rotate.
-     *
-     * The return value is a double[3], the values being:
-     *   [0]: scaleX
-     *   [1]: scaleY
-     *   [2]: rotation angle, in radians, between *** and ***
-     *
-     * From unmatrix() in unmatrix.c
-     */
-    double[] unmatrix(BaseTransform xform) {
-        double[] retVal = new double[3];
-
-        double[][] row = {{xform.getMxx(), xform.getMxy()},
-            {xform.getMyx(), xform.getMyy()}};
-        final double xSignum = Math.signum(row[0][0]);
-        final double ySignum = Math.signum(row[1][1]);
-
-        // Compute X scale factor and normalize first row.
-        // tran[U_SCALEX] = V3Length(&row[0]);
-        // row[0] = *V3Scale(&row[0], 1.0);
-
-        double scaleX = xSignum * v2length(row[0]);
-        v2scale(row[0], xSignum);
-
-        // Compute XY shear factor and make 2nd row orthogonal to 1st.
-        // tran[U_SHEARXY] = V3Dot(&row[0], &row[1]);
-        // (void)V3Combine(&row[1], &row[0], &row[1], 1.0, -tran[U_SHEARXY]);
-        //
-        // "this is too large by the y scaling factor"
-        double shearXY = v2dot(row[0], row[1]);
-
-        // Combine into row[1]
-        v2combine(row[1], row[0], row[1], 1.0, -shearXY);
-
-        // Now, compute Y scale and normalize 2nd row
-        // tran[U_SCALEY] = V3Length(&row[1]);
-        // V3Scale(&row[1], 1.0);
-        // tran[U_SHEARXY] /= tran[U_SCALEY];
-
-        double scaleY = ySignum * v2length(row[1]);
-        v2scale(row[1], ySignum);
-
-        // Now extract the rotation. (This is new code, not from the Gem.)
-        //
-        // In our matrix, we now have
-        // [   cos(theta)    -sin(theta)    ]
-        // [   sin(theta)     cos(theta)    ]
-        //
-        // TODO: assert: all 4 values are sane (RT-23962)
-        //
-        double sin = row[1][0];
-        double cos = row[0][0];
-        double angleRad = 0.0;
-
-        // Recall:
-        // arcsin works for theta: -90 -> 90
-        // arccos works for theta:   0 -> 180
-        if (sin >= 0) {
-            // theta is 0 -> 180, use acos()
-            angleRad = Math.acos(cos);
-        } else {
-            if (cos > 0) {
-                // sin < 0, cos > 0, so theta is 270 -> 360, aka -90 -> 0
-                // use asin(), add 360
-                angleRad = 2.0 * Math.PI + Math.asin(sin);
-            } else {
-                // sin < 0, cos < 0, so theta 180 -> 270
-                // cos from 180 -> 270 is inverse of cos from 0->90,
-                // so take acos(-cos) and add 180
-                angleRad = Math.PI + Math.acos(-cos);
-            }
-        }
-
-        retVal[0] = scaleX;
-        retVal[1] = scaleY;
-        retVal[2] = angleRad;
-
-        return retVal;
-    }
-
-
-    /**
-     * make a linear combination of two vectors and return the result
-     * result = (v0 * scalarA) + (v1 * scalarB)
-     *
-     * From V3Combine() in GGVecLib.c
-     */
-    void v2combine(double v0[], double v1[], double result[], double scalarA, double scalarB) {
-        // make a linear combination of two vectors and return the result.
-        // result = (a * ascl) + (b * bscl)
-        /*
-        Vector3 *V3Combine (a, b, result, ascl, bscl)
-        Vector3 *a, *b, *result;
-        double ascl, bscl;
-        {
-                result->x = (ascl * a->x) + (bscl * b->x);
-                result->y = (ascl * a->y) + (bscl * b->y);
-                result->z = (ascl * a->z) + (bscl * b->z);
-                return(result);
-        */
-
-        result[0] = scalarA*v0[0] + scalarB*v1[0];
-        result[1] = scalarA*v0[1] + scalarB*v1[1];
-    }
-
-
-    /**
-     * dot product of 2 vectors of length 2
-     */
-    double v2dot(double v0[], double v1[]) {
-        return v0[0]*v1[0] + v0[1]*v1[1];
-    }
-
-    /**
-     * scale v[] to be relative to newLen
-     *
-     * From V3Scale() in GGVecLib.c
-     */
-    double[] v2scale(double v[], double newLen) {
-        double len = v2length(v);
-        double[] retVal = v;
-        if (len != 0) {
-            v[0] *= newLen / len;
-            v[1] *= newLen / len;
-        }
-        return retVal;
-    }
-
-    /**
-     * returns length of input vector
-     *
-     * Based on V3Length() in GGVecLib.c
-     */
-    double v2length(double v[]) {
-        return Math.sqrt(v[0]*v[0] + v[1]*v[1]);
-    }
-}
-
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/BaseEffectFilter.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import com.sun.javafx.geom.BaseBounds;
-import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.scenario.effect.Effect;
-
-/**
- */
-public abstract class BaseEffectFilter {
-    private Effect effect;
-    private BaseNodeEffectInput nodeInput;
-
-    protected BaseEffectFilter(Effect effect, BaseNode node) {
-        this.effect = effect;
-        this.nodeInput = createNodeEffectInput(node);
-    }
-
-    public Effect getEffect() { return effect; }
-
-    public BaseNodeEffectInput getNodeInput() { return nodeInput; }
-
-    protected void dispose() {
-        effect = null;
-        nodeInput.setNode(null);
-        nodeInput = null;
-    }
-
-    public BaseBounds getBounds(BaseBounds bounds, BaseTransform xform) {
-        BaseBounds r = getEffect().getBounds(xform, nodeInput);
-        return bounds.deriveWithNewBounds(r);
-    }
-
-    protected abstract BaseNodeEffectInput createNodeEffectInput(BaseNode node);
-}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/BaseNode.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1393 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import javafx.scene.CacheHint;
-import java.util.List;
-import com.sun.javafx.geom.BaseBounds;
-import com.sun.javafx.geom.BoxBounds;
-import com.sun.javafx.geom.DirtyRegionContainer;
-import com.sun.javafx.geom.DirtyRegionPool;
-import com.sun.javafx.geom.RectBounds;
-import com.sun.javafx.geom.Rectangle;
-import com.sun.javafx.geom.transform.Affine3D;
-import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.javafx.geom.transform.GeneralTransform3D;
-import com.sun.javafx.geom.transform.NoninvertibleTransformException;
-import com.sun.javafx.sg.prism.NGGroup;
-import com.sun.javafx.sg.prism.NGNode;
-import com.sun.scenario.effect.Blend;
-import com.sun.scenario.effect.Effect;
-import com.sun.scenario.effect.FilterContext;
-import com.sun.scenario.effect.ImageData;
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
-import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
-
-/**
- * BaseNode is the abstract base class implementing PGNode, and forming
- * the basis for Prism and Scenario render graphs. Although it is not
- * necessary to extend from BaseNode, doing so will brighten your day.
- * <p>
- * During synchronization, the FX scene graph will pass down to us
- * the transform which takes us from local space to parent space, the
- * content bounds (ie: geom bounds), and the transformed bounds
- * (ie: boundsInParent), and the clippedBounds. The effect bounds have
- * already been passed to the Effect peer (if there is one).
- * <p>
- * Whenever the transformedBounds of the BaseNode are changed, we update
- * the dirtyBounds, so that the next time we need to accumulate dirty
- * regions, we will have the information we need to make sure we create
- * an appropriate dirty region.
- * <p>
- * BaseNode maintains a single "dirty" flag, which indicates that this
- * node itself is dirty and must contribute to the dirty region. More
- * specifically, it indicates that this node is now dirty with respect
- * to the back buffer. Any rendering of the scene which will go on the
- * back buffer will cause the dirty flag to be cleared, whereas a
- * rendering of the scene which is for an intermediate image will not
- * clear this dirty flag.
- *
- */
-public abstract class BaseNode<G> {
-    public boolean debug = false;
-
-    /**
-     * Temporary bounds for use by this class or subclasses, designed to
-     * reduce the amount of garbage we generate. If we get to the point
-     * where we have multi-threaded rasterization, we might need to make
-     * this per-instance instead of static.
-     */
-    protected static final BaseBounds TEMP_BOUNDS = new BoxBounds();
-    protected static final RectBounds TEMP_RECT_BOUNDS = new RectBounds();
-    protected static final Affine3D TEMP_TRANSFORM = new Affine3D();
-
-    /**
-     * This flag is used to indicate if the next rendering loop should clear
-     * all the dirty flags. The dirty flags are meant to indicate which
-     * portions of the tree are out of sync with the back buffer, and so
-     * CLEAR_DIRTY should always be true when the render operation originated
-     * from the back buffer, and should always be false when rendering into
-     * an image or some other such operation.
-     */
-    private static boolean CLEAR_DIRTY = true;
-
-    /**
-     * The transform for this node. Although we are handed all the bounds
-     * during synchronization (including the transformed bounds), we still
-     * need the transform so that we can apply it to the clip and so forth
-     * while accumulating dirty regions and rendering.
-     */
-    private BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
-
-    /**
-     * The cached transformed bounds. This is never null, but is frequently set
-     * to be invalid whenever the bounds for the node have changed. These are
-     * "complete" bounds, that is, with transforms and effect and clip applied.
-     * Note that this is equivalent to boundsInParent in FX.
-     */
-    protected BaseBounds transformedBounds = new RectBounds();
-
-    /**
-     * The cached bounds. This is never null, but is frequently set to be
-     * invalid whenever the bounds for the node have changed. These are the
-     * "content" bounds, that is, without transforms or filters applied.
-     */
-    protected BaseBounds contentBounds = new RectBounds();
-
-    /**
-     * We keep a reference to the last transform bounds that were valid
-     * and known. We do this to significantly speed up the rendering of the
-     * scene by culling and clipping based on "dirty" regions, which are
-     * essentially the rectangle formed by the union of the dirtyBounds
-     * and the transformedBounds.
-     */
-    private BaseBounds dirtyBounds = new RectBounds();
-
-    /**
-     * Whether the node is visible. We need to know about the visibility of
-     * the node so that we can determine whether to cull it out, and perform
-     * other such optimizations.
-     */
-    private boolean visible = true;
-
-
-    protected static enum DirtyFlag {
-        CLEAN,
-        // Means that the node is dirty, but only because of translation
-        DIRTY_BY_TRANSLATION,
-        DIRTY
-    }
-
-    /**
-     * Indicates that this BaseNode is itself dirty and needs its full bounds
-     * included in the next repaint. This means it is dirty with respect to
-     * the back buffer. We don't bother differentiating between bounds dirty
-     * and visuals dirty because we can simply inspect the dirtyBounds to
-     * see if it is valid. If so, then bounds must be dirty.
-     */
-    protected DirtyFlag dirty = DirtyFlag.DIRTY;
-
-    /**
-     * The parent of the node. In the case of a normal render graph node,
-     * this will be an PGGroup. However, if this node is being used as
-     * a clip node, then the parent is the node it is the clip for.
-     */
-    private BaseNode parent;
-
-    /**
-     * True is this node is a clip. This means the parent is clipped by this node.
-     */
-    private boolean isClip;
-
-    /**
-     * The node used for specifying the clipping shape for this node. If null,
-     * then there is no clip.
-     */
-    private BaseNode clipNode;
-
-    /**
-     * The opacity of this node.
-     */
-    private float opacity = 1f;
-
-    /**
-     * The blend mode that controls how the pixels of this node blend into
-     * the rest of the scene behind it.
-     */
-    private Blend.Mode nodeBlendMode;
-
-    /**
-     * The depth test flag for this node. It is used when rendering if the window
-     * into which we are rendering has a depth buffer.
-     */
-    private boolean depthTest = true;
-
-    /**
-     * A filter used when the node is cached. If null, then the node is not
-     * being cached. While in theory this could be created automatically by
-     * the implementation due to some form of heuristic, currently the we
-     * only set this if the application has requested that the node be cached.
-     */
-    private BaseCacheFilter cacheFilter;
-
-    /**
-     * A filter used whenever an effect is placed on the node. Of course
-     * effects can form a kind of tree, such that this one effect might be
-     * an accumulation of several different effects. This will be null if
-     * there are no effects on the FX scene graph node.
-     */
-    private BaseEffectFilter effectFilter;
-
-    /**
-     * If this node is a PGGroup, then this flag will be used to indicate
-     * whether one or more of its children is dirty. While it would seem this
-     * flag should be on PGGroup, the code turns out to be a bit cleaner with
-     * this flag in the BaseNode class.
-     */
-     protected boolean childDirty = false;
-
-     /**
-      * How many children are going to be accumulated
-      */
-     protected int dirtyChildrenAccumulated = 0;
-
-     /**
-      * Do not iterate over all children in group. Mark group as dirty
-      * when threshold was reached.
-      */
-     protected final static int DIRTY_CHILDREN_ACCUMULATED_THRESHOLD = 12;
-
-     /**
-      * Marks position of this node in dirty regions.
-      */
-     protected int cullingBits = 0x0;
-
-    /***************************************************************************
-     *                                                                         *
-     * Implementation of the PGNode interface                                  *
-     *                                                                         *
-     **************************************************************************/
-
-    /**
-     * Called by the FX scene graph to tell us whether we should be visible or not.
-     * @param value
-     */
-    public void setVisible(boolean value) {
-        // If the visibility changes, we need to mark this node as being dirty.
-        // If this node is being cached, changing visibility should have no
-        // effect, since it doesn't affect the rendering of the content in
-        // any way. If we were to release the cached image, that might thwart
-        // the developer's attempt to improve performance for things that
-        // rapidly appear and disappear but which are expensive to render.
-        // Ancestors, of course, must still have their caches invalidated.
-        if (visible != value) {
-            this.visible = value;
-            markDirty();
-        }
-    }
-
-    /**
-     * Called by the FX scene graph to tell us what our new content bounds are.
-     * @param bounds must not be null
-     */
-    public void setContentBounds(BaseBounds bounds) {
-        // Note, there isn't anything to do here. We're dirty if geom or
-        // visuals or transformed bounds or effects or clip have changed.
-        // There's no point dealing with it here.
-        contentBounds = contentBounds.deriveWithNewBounds(bounds);
-    }
-
-    /**
-     * Called by the FX scene graph to tell us what our transformed bounds are.
-     * @param bounds must not be null
-     */
-    public void setTransformedBounds(BaseBounds bounds, boolean byTransformChangeOnly) {
-        if (transformedBounds.equals(bounds)) {
-            // There has been no change, so ignore. It turns out this happens
-            // a lot, because when a leaf has dirty bounds, all parents also
-            // assume their bounds have changed, and only when they recompute
-            // their bounds do we discover otherwise. This check could happen
-            // on the FX side, however, then the FX side needs to cache the
-            // former content bounds at the time of the last sync or needs to
-            // be able to read state back from the PG side. Yuck. Just doing
-            // it here for now.
-            return;
-        }
-        // If the transformed bounds have changed, then we need to save off the
-        // transformed bounds into the dirty bounds, so that the resulting
-        // dirty region will be correct. If this node is cached, we DO NOT
-        // invalidate the cache. The cacheFilter will compare its cached
-        // transform to the accumulated transform to determine whether the
-        // cache needs to be regenerated. So we will not invalidate it here.
-        if (dirtyBounds.isEmpty()) {
-            dirtyBounds = dirtyBounds.deriveWithNewBounds(transformedBounds);
-            dirtyBounds = dirtyBounds.deriveWithUnion(bounds);
-        } else {
-            // TODO I think this is vestigial from Scenario and will never
-            // actually occur in real life... (RT-23956)
-            dirtyBounds = dirtyBounds.deriveWithUnion(transformedBounds);
-        }
-        transformedBounds = transformedBounds.deriveWithNewBounds(bounds);
-        if (hasVisuals() && !byTransformChangeOnly) {
-            markDirty();
-        }
-    }
-
-    private DirtyHint hint;
-
-    /**
-     * Called by the FX scene graph to tell us what our transform matrix is.
-     * @param tx must not be null
-     */
-    public void setTransformMatrix(BaseTransform tx) {
-        // If the transform matrix has changed, then we need to update it,
-        // and mark this node as dirty. If this node is cached, we DO NOT
-        // invalidate the cache. The cacheFilter will compare its cached
-        // transform to the accumulated transform to determine whether the
-        // cache needs to be regenerated. So we will not invalidate it here.
-        // This approach allows the cached image to be reused in situations
-        // where only the translation parameters of the accumulated transform
-        // are changing. The scene will still be marked dirty and cached
-        // images of any ancestors will be invalidated.
-        boolean useHint = false;
-
-        // If the parent is cached, try to check if the transformation is only a translation
-        if (parent != null && parent.cacheFilter != null) {
-            if (hint == null) {
-                hint = new DirtyHint();
-            // If there's no hint created yet, this is the first setTransformMatrix
-            // call and we have nothing to compare to yet.
-            } else {
-                if (transform.getMxx() == tx.getMxx()
-                        && transform.getMxy() == tx.getMxy()
-                        && transform.getMyy() == tx.getMyy()
-                        && transform.getMyx() == tx.getMyx()
-                        && transform.getMxz() == tx.getMxz()
-                        && transform.getMyz() == tx.getMyz()
-                        && transform.getMzx() == tx.getMzx()
-                        && transform.getMzy() == tx.getMzy()
-                        && transform.getMzz() == tx.getMzz()
-                        && transform.getMzt() == tx.getMzt()) {
-                    useHint = true;
-                    hint.translateXDelta = tx.getMxt() - transform.getMxt();
-                    hint.translateYDelta = tx.getMyt() - transform.getMyt();
-                }
-            }
-        }
-
-        transform = transform.deriveWithNewTransform(tx);
-        if (useHint) {
-            markDirtyByTranslation(hint);
-        } else {
-            markDirty();
-        }
-    }
-
-    /**
-     * Called by the FX scene graph whenever the clip node for this node changes.
-     * Note that BaseNode assumes that the PGNode is a BaseNode subclass.
-     * @param clipNode can be null if the clip node is being cleared
-     */
-    public void setClipNode(NGNode clipNode) {
-        // Whenever the clipNode itself has changed (that is, the reference to
-        // the clipNode), we need to be sure to mark this node dirty and to
-        // invalidate the cache of this node (if there is one) and all parents.
-        BaseNode newClipNode = clipNode;
-        if (newClipNode != this.clipNode) {
-            // Clear the "parent" property of the clip node, if there was one
-            if (this.clipNode != null) this.clipNode.setParent(null);
-            // Make the "parent" property of the clip node point to this
-            if (newClipNode != null) newClipNode.setParent(this, true);
-            // Keep the reference to the new clip node
-            this.clipNode = newClipNode;
-            // Mark this node dirty, invalidate its cache, and all parents.
-            visualsChanged();
-        }
-    }
-
-    /**
-     * Called by the FX scene graph whenever the opacity for the node changes.
-     * We create a special filter when the opacity is < 1.
-     * @param opacity A value between 0 and 1.
-     */
-    public void setOpacity(float opacity) {
-        // Check the argument to make sure it is valid.
-        if (opacity < 0 || opacity > 1) {
-            throw new IllegalArgumentException("Internal Error: The opacity must be between 0 and 1");
-        }
-        // If the opacity has changed, react. If this node is being cached,
-        // then we do not want to invalidate the cache due to an opacity
-        // change. However, as usual, all parent caches must be invalidated.
-        if (opacity != this.opacity) {
-            this.opacity = opacity;
-            markDirty();
-        }
-    }
-
-    /**
-     * Set by the FX scene graph.
-     * @param blendMode may be null to indicate "default"
-     */
-    public void setNodeBlendMode(Blend.Mode blendMode) {
-        // If the blend mode has changed, react. If this node is being cached,
-        // then we do not want to invalidate the cache due to a compositing
-        // change. However, as usual, all parent caches must be invalidated.
-        if (this.nodeBlendMode != blendMode) {
-            this.nodeBlendMode = blendMode;
-            markDirty();
-        }
-    }
-
-    /**
-     * Called by the FX scene graph whenever the derived depth test flag for
-     * the node changes.
-     * @param depthTest indicates whether to perform a depth test operation
-     * (if the window has a depth buffer).
-     */
-    public void setDepthTest(boolean depthTest) {
-        // If the depth test flag has changed, react.
-        if (depthTest != this.depthTest) {
-            this.depthTest = depthTest;
-            // Mark this node dirty, invalidate its cache, and all parents.
-            visualsChanged();
-        }
-    }
-
-    /**
-     * Called by the FX scene graph whenever "cached" or "cacheHint" changes.
-     * These hints provide a way for the developer to indicate whether they
-     * want this node to be cached as a raster, which can be quite a performance
-     * optimization in some cases (and lethal in others).
-     * @param cached specifies whether or not this node should be cached
-     * @param cacheHint never null, indicates some hint as to how to cache
-     */
-    public void setCachedAsBitmap(boolean cached, CacheHint cacheHint) {
-        // Validate the arguments
-        if (cacheHint == null) {
-            throw new IllegalArgumentException("Internal Error: cacheHint must not be null");
-        }
-
-        if (cached) {
-            if (cacheFilter == null) {
-                cacheFilter = createCacheFilter(cacheHint);
-                // We do not technically need to do a render pass here, but if
-                // we wait for the next render pass to cache it, then we will
-                // cache not the current visuals, but the visuals as defined
-                // by any transform changes that happen between now and then.
-                // Repainting now encourages the cached version to be as close
-                // as possible to the state of the node when the cache hint
-                // was set...
-                markDirty();
-            } else {
-                if (!cacheFilter.matchesHint(cacheHint)) {
-                    cacheFilter.setHint(cacheHint);
-                    // Different hints may have different requirements of
-                    // whether the cache is stale.  We do not have enough info
-                    // right here to evaluate that, but it will be determined
-                    // naturally during a repaint cycle.
-                    // If the new hint is more relaxed (QUALITY => SPEED for
-                    // instance) then rendering should be quick.
-                    // If the new hint is more restricted (SPEED => QUALITY)
-                    // then we need to render to improve the results anyway.
-                    markDirty();
-                }
-            }
-        } else {
-            if (cacheFilter != null) {
-                cacheFilter.dispose();
-                cacheFilter = null;
-                // A cache will often look worse than uncached rendering.  It
-                // may look the same in some circumstances, and this may then
-                // be an unnecessary rendering pass, but we do not have enough
-                // information here to be able to optimize that when possible.
-                markDirty();
-            }
-        }
-    }
-
-    /**
-     * Called by the FX scene graph to set the effect.
-     * @param effect
-     */
-    public void setEffect(Object effect) {
-        // We only need to take action if the effect is different than what was
-        // set previously. There are four possibilities. Of these, #1 and #3 matter:
-        // 0. effectFilter == null, effect == null
-        // 1. effectFilter == null, effect != null
-        // 2. effectFilter != null, effectFilter.effect == effect
-        // 3. effectFilter != null, effectFilter.effect != effect
-        // In any case where the effect is changed, we must both invalidate
-        // the cache for this node (if there is one) and all parents, and mark
-        // this node as dirty.
-        if (effectFilter == null && effect != null) {
-            effectFilter = createEffectFilter((Effect)effect);
-            visualsChanged();
-        } else if (effectFilter != null && effectFilter.getEffect() != effect) {
-            effectFilter.dispose();
-            effectFilter = null;
-            if (effect != null) {
-                effectFilter = createEffectFilter((Effect)effect);
-            }
-            visualsChanged();
-        }
-    }
-
-    /**
-     * Called by the FX scene graph when an effect in the effect chain on the node
-     * changes internally.
-     */
-    public void effectChanged() {
-        visualsChanged();
-    }
-
-    /**
-     * Return true if contentBounds is purely a 2D bounds, ie. it is a
-     * RectBounds or its Z dimension is almost zero.
-     */
-    public boolean isContentBounds2D() {
-        return (contentBounds.is2D()
-                || (Affine3D.almostZero(contentBounds.getMaxZ())
-                && Affine3D.almostZero(contentBounds.getMinZ())));
-    }
-
-    /***************************************************************************
-     *                                                                         *
-     * Hierarchy, visibility, and other such miscellaneous BaseNode properties *
-     * not already handled by implementing PGNode interface, bounds, or dirty  *
-     * region management.                                                      *
-     *                                                                         *
-     **************************************************************************/
-
-    /**
-     * Gets the parent of this node. The parent might be an PGGroup. However,
-     * if this node is a clip node on some other node, then the node on which
-     * it is set as the clip will be returned. That is, suppose some node A
-     * has a clip node B. The method B.getParent() will return A.
-     */
-    public BaseNode getParent() { return parent; }
-
-    /**
-     * Only called by this class, or by the PGGroup class.
-     */
-    public void setParent(BaseNode parent) {
-        setParent(parent, false);
-    }
-
-    private void setParent(BaseNode parent, boolean isClip) {
-        this.parent = parent;
-        this.isClip = isClip;
-    }
-
-    protected final Effect getEffect() { return effectFilter == null ? null : effectFilter.getEffect(); }
-
-    /**
-     * Gets whether this node's visible property is set
-     */
-    public boolean isVisible() { return visible; }
-
-    public final BaseTransform getTransform() { return transform; }
-    public final float getOpacity() { return opacity; }
-    public final Blend.Mode getNodeBlendMode() { return nodeBlendMode; }
-    public final boolean isDepthTest() { return depthTest; }
-    public final BaseCacheFilter getCacheFilter() { return cacheFilter; }
-    public final BaseEffectFilter getEffectFilter() { return effectFilter; }
-    public final BaseNode getClipNode() { return clipNode; }
-
-    public BaseBounds getContentBounds(BaseBounds bounds, BaseTransform tx) {
-        if (tx.isTranslateOrIdentity()) {
-            bounds = bounds.deriveWithNewBounds(contentBounds);
-            if (!tx.isIdentity()) {
-                float translateX = (float) tx.getMxt();
-                float translateY = (float) tx.getMyt();
-                float translateZ = (float) tx.getMzt();
-                bounds = bounds.deriveWithNewBounds(
-                    bounds.getMinX() + translateX,
-                    bounds.getMinY() + translateY,
-                    bounds.getMinZ() + translateZ,
-                    bounds.getMaxX() + translateX,
-                    bounds.getMaxY() + translateY,
-                    bounds.getMaxZ() + translateZ);
-            }
-            return bounds;
-        } else {
-            // This is a scale / rotate / skew transform.
-            // We have contentBounds cached throughout the entire tree.
-            // just walk down the tree and add everything up
-            return computeBounds(bounds, tx);
-        }
-    }
-
-    private BaseBounds computeBounds(BaseBounds bounds, BaseTransform tx) {
-        // TODO: This code almost worked, but it ignored the local to
-        // parent transforms on the nodes.  The short fix is to disable
-        // this block and use the more general form below, but we need
-        // to revisit this and see if we can make it work more optimally.
-        // @see RT-12105 http://javafx-jira.kenai.com/browse/RT-12105
-        if (false && this instanceof NGGroup) {
-            List<NGNode> children = ((NGGroup)this).getChildren();
-            BaseBounds tmp = TEMP_BOUNDS;
-            for (int i=0; i<children.size(); i++) {
-                float minX = bounds.getMinX();
-                float minY = bounds.getMinY();
-                float minZ = bounds.getMinZ();
-                float maxX = bounds.getMaxX();
-                float maxY = bounds.getMaxY();
-                float maxZ = bounds.getMaxZ();
-                BaseNode child = (BaseNode)children.get(i);
-                bounds = child.computeBounds(bounds, tx);
-                tmp = tmp.deriveWithNewBounds(minX, minY, minZ, maxX, maxY, maxZ);
-                bounds = bounds.deriveWithUnion(tmp);
-            }
-            return bounds;
-        } else {
-            bounds = bounds.deriveWithNewBounds(contentBounds);
-            return tx.transform(contentBounds, bounds);
-        }
-    }
-
-    /**
-     */
-    public final BaseBounds getClippedBounds(BaseBounds bounds, BaseTransform tx) {
-        BaseBounds effectBounds = getEffectBounds(bounds, tx);
-        if (clipNode != null) {
-            // there is a clip in place, so we will save off the effect/content
-            // bounds (so as not to generate garbage) and will then get the
-            // bounds of the clip node and do an intersection of the two
-            float ex1 = effectBounds.getMinX();
-            float ey1 = effectBounds.getMinY();
-            float ez1 = effectBounds.getMinZ();
-            float ex2 = effectBounds.getMaxX();
-            float ey2 = effectBounds.getMaxY();
-            float ez2 = effectBounds.getMaxZ();
-            effectBounds = clipNode.getCompleteBounds(effectBounds, tx);
-            effectBounds.intersectWith(ex1, ey1, ez1, ex2, ey2, ez2);
-        }
-        return effectBounds;
-    }
-
-    public final BaseBounds getEffectBounds(BaseBounds bounds, BaseTransform tx) {
-        if (effectFilter != null) {
-            return effectFilter.getBounds(bounds, tx);
-        } else {
-            return getContentBounds(bounds, tx);
-        }
-    }
-
-    public final BaseBounds getCompleteBounds(BaseBounds bounds, BaseTransform tx) {
-        if (tx.isIdentity()) {
-            bounds = bounds.deriveWithNewBounds(transformedBounds);
-            return bounds;
-        } else if (transform.isIdentity()) {
-            return getClippedBounds(bounds, tx);
-        } else {
-            double mxx = tx.getMxx();
-            double mxy = tx.getMxy();
-            double mxz = tx.getMxz();
-            double mxt = tx.getMxt();
-            double myx = tx.getMyx();
-            double myy = tx.getMyy();
-            double myz = tx.getMyz();
-            double myt = tx.getMyt();
-            double mzx = tx.getMzx();
-            double mzy = tx.getMzy();
-            double mzz = tx.getMzz();
-            double mzt = tx.getMzt();
-            BaseTransform boundsTx = tx.deriveWithConcatenation(this.transform);
-            bounds = getClippedBounds(bounds, tx);
-            if (boundsTx == tx) {
-                tx.restoreTransform(mxx, mxy, mxz, mxt,
-                                    myx, myy, myz, myt,
-                                    mzx, mzy, mzz, mzt);
-            }
-            return bounds;
-        }
-    }
-
-    /***************************************************************************
-     *                                                                         *
-     * Dirty States and Dirty Regions                                          *
-     *                                                                         *
-     **************************************************************************/
-
-    /**
-     * Invoked by subclasses whenever some change to the geometry or visuals
-     * has occurred. This will mark the node as dirty and invalidate the cache.
-     */
-    protected void visualsChanged() {
-        invalidateCache();
-        markDirty();
-    }
-
-    protected void geometryChanged() {
-        invalidateCache();
-        if (hasVisuals()) {
-            markDirty();
-        }
-    }
-
-    /**
-     * Makes this node dirty, meaning that it needs to be included in the
-     * next repaint to the back buffer, and its bounds should be included
-     * in the dirty region. This flag means that this node itself is dirty.
-     * In contrast, the childDirty flag indicates that a child of the node
-     * (maybe a distant child) is dirty. This method does not invalidate the
-     * cache of this node. However, it ends up walking up the tree marking
-     * all parents as having a dirty child and also invalidating their caches.
-     * This method has no effect if the node is already dirty.
-     */
-    public final void markDirty() {
-        if (dirty != DirtyFlag.DIRTY) {
-            dirty = DirtyFlag.DIRTY;
-            markTreeDirty();
-        }
-    }
-
-    /**
-     * Mark the node as DIRTY_BY_TRANSLATION. This will call special cache invalidation
-     * @param hint
-     */
-    public void markDirtyByTranslation(DirtyHint hint) {
-        if (dirty == DirtyFlag.CLEAN) {
-            if (parent != null && parent.dirty == DirtyFlag.CLEAN && !parent.childDirty) {
-                dirty = DirtyFlag.DIRTY_BY_TRANSLATION;
-                parent.childDirty = true;
-                parent.dirtyChildrenAccumulated++;
-                parent.invalidateCacheByTranslation(hint);
-                parent.markTreeDirty();
-            } else {
-                markDirty();
-            }
-        }
-    }
-
-    //Mark tree dirty, but make sure this node's
-    // dirtyChildrenAccumulated has not been incremented.
-    // Useful when a markTree is called on a node that's not
-    // the dirty source of change, e.g. group knows it has new child
-    // or one of it's child has been removed
-    protected final void markTreeDirtyNoIncrement() {
-        if (parent != null && (!parent.childDirty || dirty == DirtyFlag.DIRTY_BY_TRANSLATION)) {
-            markTreeDirty();
-        }
-    }
-
-    /**
-     * Notifies the parent (whether a PGGroup or just a BaseNode) that
-     * a child has become dirty. This walk will continue all the way up
-     * to the root of the tree. If a node is encountered which is already
-     * dirty, or which already has childDirty set, then this loop will
-     * terminate (ie: there is no point going further so we might as well
-     * just bail). This method ends up invalidating the cache of each
-     * parent up the tree. Since it is possible for a node to already
-     * have its dirty bit set, but not have its cache invalidated, this
-     * method is careful to make sure the first parent it encounters
-     * which is already marked dirty still has its cache invalidated. If
-     * this turns out to be expensive due to high occurrence, we can add
-     * a quick "invalidated" flag to every node (at the cost of yet
-     * another bit).
-     */
-
-    protected final void markTreeDirty() {
-        BaseNode p = parent;
-        boolean atClip = isClip;
-        boolean byTranslation = dirty == DirtyFlag.DIRTY_BY_TRANSLATION;
-        while (p != null && p.dirty != DirtyFlag.DIRTY && (!p.childDirty || atClip || byTranslation)) {
-            if (atClip) {
-                p.dirty = DirtyFlag.DIRTY;
-            } else if (!byTranslation) {
-                p.childDirty = true;
-                p.dirtyChildrenAccumulated++;
-            }
-            p.invalidateCache();
-            atClip = p.isClip;
-            byTranslation = p.dirty == DirtyFlag.DIRTY_BY_TRANSLATION;
-            p = p.parent;
-        }
-        // if we stopped on a parent that already has dirty children, increase it's
-        // dirty children count.
-        // Note that when incrementDirty is false, we dont increment in this case.
-        if (p != null && p.dirty == DirtyFlag.CLEAN && !atClip && !byTranslation) {
-            p.dirtyChildrenAccumulated++;
-        }
-        // Must make sure this happens. In some cases, a parent might
-        // already be marked dirty (for example, its opacity may have
-        // changed) but its cache has not been made invalid. This call
-        // will make sure it is invalidated in that case
-        if (p != null) p.invalidateCache();
-    }
-
-    /**
-     * Gets whether this SGNode is clean. This will return true only if
-     * this node and any / all child nodes are clean.
-     */
-    public final boolean isClean() {
-        return dirty == DirtyFlag.CLEAN && !childDirty;
-    }
-
-    /**
-     * Gets whether this node itself is dirty.
-     */
-//    public final boolean isDirty() {
-//        return dirty;
-//    }
-
-    /**
-     * Clears the dirty flag. This should only happen during rendering.
-     */
-    protected void clearDirty() {
-        dirty = DirtyFlag.CLEAN;
-        childDirty = false;
-        dirtyBounds.makeEmpty();
-        dirtyChildrenAccumulated = 0;
-    }
-
-    public void clearDirtyTree() {
-        clearDirty();
-        if (getClipNode() != null) {
-            getClipNode().clearDirtyTree();
-        }
-        if (this instanceof NGGroup) {
-            List<NGNode> children = ((NGGroup) this).getChildren();
-            for (int i = 0; i < children.size(); ++i) {
-                BaseNode child = ((BaseNode)children.get(i));
-                if (child.dirty != DirtyFlag.CLEAN || child.childDirty) {
-                    child.clearDirtyTree();
-                }
-            }
-        }
-    }
-
-    /**
-     * Invalidates the cache, if it is in use. There are several operations
-     * which need to cause the cached raster to become invalid so that a
-     * subsequent render operation will result in the cached image being
-     * reconstructed.
-     */
-    protected final void invalidateCache() {
-        if (cacheFilter != null) {
-            cacheFilter.invalidate();
-        }
-    }
-
-    /**
-     * Mark the cache as invalid due to a translation of a child. The cache filter
-     * might use this information for optimizations.
-     * @param hint
-     */
-    protected final void invalidateCacheByTranslation(DirtyHint hint) {
-        if (cacheFilter != null) {
-            cacheFilter.invalidateByTranslation(hint.translateXDelta, hint.translateYDelta);
-        }
-    }
-
-    /**
-     * Accumulates and returns the dirty regions in transformed coordinates for
-     * this node. This method is designed such that a single downward traversal
-     * of the tree is sufficient to update the dirty regions.
-     * <p>
-     * This method only accumulates dirty regions for parts of the tree which lie
-     * inside the clip since there is no point in accumulating dirty regions which
-     * lie outside the clip. The returned dirty regions bounds  the same object
-     * as that passed into the function. The returned dirty regions bounds will
-     * always be adjusted such that they do not extend beyond the clip.
-     * <p>
-     * The given transform is the accumulated transform up to but not including the
-     * transform of this node.
-     *
-     * @param clip must not be null, the clip in scene coordinates, supplied by the
-     *        rendering system. At most, this is usually the bounds of the window's
-     *        content area, however it might be smaller.
-     * @param dirtyRegionTemp must not be null, the dirty region in scene coordinates.
-     *        When this method is initially invoked by the rendering system, the
-     *        dirtyRegion should be marked as invalid.
-     * @param dirtyRegionContainer must not be null, the container of dirty regions in scene
-     *        coordinates.
-     * @param tx must not be null, the accumulated transform up to but not
-     *        including this node's transform. When this method concludes, it must
-     *        restore this transform if it was changed within the function.
-     * @param pvTx must not be null, it's the perspective transform of the current
-     *        perspective camera or identity transform if parallel camera is used.
-     * @return The dirty region container. If the returned value is null, then that means
-     *         the clip should be used as the dirty region. This is a special
-     *         case indicating that there is no more need to walk the tree but
-     *         we can take a shortcut. Note that returning null is *always*
-     *         safe. Returning something other than null is simply an
-     *         optimization for cases where the dirty region is substantially
-     *         smaller than the clip.
-     * TODO: Only made non-final for the sake of testing (see javafx-sg-prism tests) (RT-23957)
-     */
-
-    public /*final*/ int accumulateDirtyRegions(final RectBounds clip,
-                                                final RectBounds dirtyRegionTemp,
-                                                DirtyRegionPool regionPool,
-                                                final DirtyRegionContainer dirtyRegionContainer,
-                                                final BaseTransform tx,
-                                                final GeneralTransform3D pvTx)
-    {
-        // Even though a node with 0 visibility or 0 opacity doesn't get
-        // rendered, it may contribute to the dirty bounds, for example, if it
-        // WAS visible or if it HAD an opacity > 0 last time we rendered then
-        // we must honor its dirty region. We have front-loaded this work so
-        // that we don't mark nodes as having dirty flags or dirtyBounds if
-        // they shouldn't contribute to the dirty region. So we can simply
-        // treat all nodes, regardless of their opacity or visibility, as
-        // though their dirty regions matter. They do.
-
-        // If this node is clean then we can simply return the dirty region as
-        // there is no need to walk any further down this branch of the tree.
-        // The node is "clean" if neither it, nor its children, are dirty.
-         if (dirty == DirtyFlag.CLEAN && !childDirty) {
-             return DirtyRegionContainer.DTR_OK;
-         }
-
-        // We simply collect this nodes dirty region if it has its dirty flag
-        // set, regardless of whether it is a group or not. However, if this
-        // node is not dirty, then we can ask the accumulateGroupDirtyRegion
-        // method to collect the dirty regions of the children.
-        if (dirty != DirtyFlag.CLEAN) {
-            return accumulateNodeDirtyRegion(clip, dirtyRegionTemp, dirtyRegionContainer, tx, pvTx);
-        } else {
-            assert childDirty == true; // this must be true by this point
-            return accumulateGroupDirtyRegion(clip, dirtyRegionTemp, regionPool,
-                                              dirtyRegionContainer, tx, pvTx);
-        }
-    }
-
-    /**
-     * Accumulates the dirty region of a node.
-     * TODO: Only made protected for the sake of testing (see javafx-sg-prism tests) (RT-23957)
-     */
-    protected int accumulateNodeDirtyRegion(final RectBounds clip,
-                                            final RectBounds dirtyRegionTemp,
-                                            final DirtyRegionContainer dirtyRegionContainer,
-                                            final BaseTransform tx,
-                                            final GeneralTransform3D pvTx) {
-
-        // Get the dirty bounds of this specific node in scene coordinates
-        BaseBounds bb = computeDirtyRegion(dirtyRegionTemp, tx, pvTx);
-
-        // Note: dirtyRegion is strictly a 2D operation. We simply need the largest
-        // rectangular bounds of bb. Hence the Z-axis projection of bb; taking
-        // minX, minY, maxX and maxY values from this point on.
-        dirtyRegionTemp.setMinX(bb.getMinX());
-        dirtyRegionTemp.setMinY(bb.getMinY());
-        dirtyRegionTemp.setMaxX(bb.getMaxX());
-        dirtyRegionTemp.setMaxY(bb.getMaxY());
-
-        // If my dirty region is empty, or if it doesn't intersect with the
-        // clip, then we can simply return the passed in dirty region since
-        // this node's dirty region is not helpful
-        if (dirtyRegionTemp.isEmpty() || clip.disjoint(dirtyRegionTemp)) {
-            return DirtyRegionContainer.DTR_OK;
-        }
-
-        if (dirtyRegionTemp.getMinX() <= clip.getMinX() &&
-            dirtyRegionTemp.getMinY() <= clip.getMinY() &&
-            dirtyRegionTemp.getMaxX() >= clip.getMaxX() &&
-            dirtyRegionTemp.getMaxY() >= clip.getMaxY()) {
-            return DirtyRegionContainer.DTR_CONTAINS_CLIP;
-        }
-
-        dirtyRegionTemp.setMinX(Math.max(dirtyRegionTemp.getMinX(), clip.getMinX()));
-        dirtyRegionTemp.setMinY(Math.max(dirtyRegionTemp.getMinY(), clip.getMinY()));
-        dirtyRegionTemp.setMaxX(Math.min(dirtyRegionTemp.getMaxX(), clip.getMaxX()));
-        dirtyRegionTemp.setMaxY(Math.min(dirtyRegionTemp.getMaxY(), clip.getMaxY()));
-
-        dirtyRegionContainer.addDirtyRegion(dirtyRegionTemp);
-
-        return DirtyRegionContainer.DTR_OK;
-    }
-
-    /**
-     * Accumulates the dirty region of a PGGroup. This is implemented here as opposed to
-     * using polymorphism because we wanted to centralize all of the dirty region
-     * management code in one place, rather than having it spread between Prism,
-     * Scenario, and any other future toolkits.
-     * TODO: Only made protected for the sake of testing (see javafx-sg-prism tests) (RT-23957)
-     */
-    protected int accumulateGroupDirtyRegion(final RectBounds clip,
-                                             final RectBounds dirtyRegionTemp,
-                                             DirtyRegionPool regionPool,
-                                             DirtyRegionContainer dirtyRegionContainer,
-                                             final BaseTransform tx,
-                                             final GeneralTransform3D pvTx) {
-        // We should have only made it to this point if this node has a dirty
-        // child. If this node itself is dirty, this method never would get called.
-        // If this node was not dirty and had no dirty children, then this
-        // method never should have been called. So at this point, the following
-        // assertions should be correct.
-        assert childDirty == true;
-        assert dirty == DirtyFlag.CLEAN;
-
-        int status = DirtyRegionContainer.DTR_OK;
-
-        if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) {
-            status = accumulateNodeDirtyRegion(clip, dirtyRegionTemp, dirtyRegionContainer, tx, pvTx);
-            return status;
-        }
-
-        // If we got here, then we are following a "bread crumb" trail down to
-        // some child (perhaps distant) which is dirty. So we need to iterate
-        // over all the children and accumulate their dirty regions. Before doing
-        // so we, will save off the transform state and restore it after having
-        // called all the children.
-        double mxx = tx.getMxx();
-        double mxy = tx.getMxy();
-        double mxz = tx.getMxz();
-        double mxt = tx.getMxt();
-
-        double myx = tx.getMyx();
-        double myy = tx.getMyy();
-        double myz = tx.getMyz();
-        double myt = tx.getMyt();
-
-        double mzx = tx.getMzx();
-        double mzy = tx.getMzy();
-        double mzz = tx.getMzz();
-        double mzt = tx.getMzt();
-        BaseTransform renderTx = tx;
-        if (this.transform != null) renderTx = renderTx.deriveWithConcatenation(this.transform);
-
-        // If this group node has a clip, then we will perform some special
-        // logic which will cause the dirty region accumulation loops to run
-        // faster. We already have a system whereby if a node determines that
-        // its dirty region exceeds that of the clip, it simply returns null,
-        // short circuiting the accumulation process. We extend that logic
-        // here by also taking into account the clipNode on the group. If
-        // there is a clip node, then we will union the bounds of the clip
-        // node (in boundsInScene space) with the current clip and pass this
-        // new clip down to the children. If they determine that their dirty
-        // regions exceed the bounds of this new clip, then they will return
-        // null. We'll catch that here, and use that information to know that
-        // we ought to simply accumulate the bounds of this group as if it
-        // were dirty. This process will do all the other optimizations we
-        // already have in place for getting the normal dirty region.
-        RectBounds myClip = clip;
-        //Save current dirty region so we can fast-reset to (something like) the last state
-        //and possibly save a few intersects() calls
-
-        DirtyRegionContainer originalDirtyRegion = null;
-        BaseTransform originalRenderTx = null;
-        if (effectFilter != null) {
-            try {
-                myClip = new RectBounds();
-                BaseBounds myClipBaseBounds = renderTx.inverseTransform(clip, TEMP_BOUNDS);
-                myClip.setBounds(myClipBaseBounds.getMinX(),
-                                 myClipBaseBounds.getMinY(),
-                                 myClipBaseBounds.getMaxX(),
-                                 myClipBaseBounds.getMaxY());
-            } catch (NoninvertibleTransformException ex) {
-                return DirtyRegionContainer.DTR_OK;
-            }
-
-            originalRenderTx = renderTx;
-            renderTx = BaseTransform.IDENTITY_TRANSFORM;
-            originalDirtyRegion = dirtyRegionContainer;
-            dirtyRegionContainer = regionPool.checkOut();
-        } else if (clipNode != null) {
-            originalDirtyRegion = dirtyRegionContainer;
-            myClip = new RectBounds();
-            BaseBounds clipBounds = clipNode.getCompleteBounds(myClip, renderTx);
-            pvTx.transform(clipBounds, clipBounds);
-            myClip.deriveWithNewBounds(clipBounds.getMinX(), clipBounds.getMinY(), 0,
-                                         clipBounds.getMaxX(), clipBounds.getMaxY(), 0);
-            myClip.intersectWith(clip);
-            dirtyRegionContainer = regionPool.checkOut();
-        }
-
-
-        //Accumulate also removed children to dirty region.
-        List<NGNode> removed = ((NGGroup) this).getRemovedChildren();
-        if (removed != null) {
-            BaseNode removedChild;
-            for (int i = removed.size() - 1; i >= 0; --i) {
-                removedChild = (BaseNode) removed.get(i);
-                removedChild.dirty = DirtyFlag.DIRTY;
-                    status = removedChild.accumulateDirtyRegions(myClip,
-                            dirtyRegionTemp,regionPool, dirtyRegionContainer, renderTx, pvTx);
-                    if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
-                        break;
-                    }
-            }
-        }
-
-        List<NGNode> children = ((NGGroup) this).getChildren();
-        int num = children.size();
-            for (int i=0; i<num && status == DirtyRegionContainer.DTR_OK; i++) {
-            BaseNode child = (BaseNode) children.get(i);
-            // The child will check the dirty bits itself. If we tested it here
-            // (as we used to), we are just doing the check twice. True, it might
-            // mean fewer method calls, but hotspot will probably inline this all
-            // anyway, and doing the check in one place is less error prone.
-                status = child.accumulateDirtyRegions(myClip, dirtyRegionTemp, regionPool,
-                                                      dirtyRegionContainer, renderTx, pvTx);
-                if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
-                break;
-            }
-        }
-
-        if (effectFilter != null && status == DirtyRegionContainer.DTR_OK) {
-            //apply effect on effect dirty regions
-            applyEffect(effectFilter, dirtyRegionContainer, regionPool);
-
-            if (clipNode != null) {
-                myClip = new RectBounds();
-                BaseBounds clipBounds = clipNode.getCompleteBounds(myClip, renderTx);
-                applyClip(clipBounds, dirtyRegionContainer);
-            }
-
-            //apply transform on effect dirty regions
-            applyTransform(originalRenderTx, dirtyRegionContainer);
-            renderTx = originalRenderTx;
-
-            originalDirtyRegion.merge(dirtyRegionContainer);
-            regionPool.checkIn(dirtyRegionContainer);
-        }
-
-        // If the process of applying the transform caused renderTx to not equal
-        // tx, then there is no point restoring it since it will be a different
-        // reference and will therefore be gc'd.
-        if (renderTx == tx) {
-            tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt);
-        }
-
-        // If the dirty region is null and there is a clip node specified, then what
-        // happened is that the dirty region of content within this group exceeded
-        // the clip of this group, and thus, we should accumulate the bounds of
-        // this group into the dirty region. If the bounds of the group exceeds
-        // the bounds of the dirty region, then we end up returning null in the
-        // end. But the implementation of accumulateNodeDirtyRegion handles this.
-        if (clipNode != null && effectFilter == null) {
-            if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
-                status = accumulateNodeDirtyRegion(clip, dirtyRegionTemp, originalDirtyRegion, tx, pvTx);
-            } else {
-                originalDirtyRegion.merge(dirtyRegionContainer);
-            }
-            regionPool.checkIn(dirtyRegionContainer);
-        }
-        return status;
-    }
-
-    /**
-     * Computes the dirty region for this Node. The specified region is in
-     * scene coordinates. The specified tx can be used to convert local bounds
-     * to scene bounds (it includes everything up to but not including my own
-     * transform).
-     * @param pvTx must not be null, it's the perspective transform of the current
-     *        perspective camera or identity transform if parallel camera is used.
-     */
-    private BaseBounds computeDirtyRegion(BaseBounds region,
-                                          BaseTransform tx,
-                                          GeneralTransform3D pvTx)
-    {
-        // The passed in region is a scratch object that exists for me to use,
-        // such that I don't have to create a temporary object. So I just
-        // hijack it right here giving it the dirtyBounds.
-        if (!dirtyBounds.isEmpty()) {
-            region = region.deriveWithNewBounds(dirtyBounds);
-        } else {
-            // If dirtyBounds is empty, then we will simply set the bounds to
-            // be the same as the transformedBounds (since that means the bounds
-            // haven't changed and right now we don't support dirty sub regions
-            // for generic nodes). This can happen if, for example, this is
-            // a group with a clip and the dirty area of child nodes within
-            // the group exceeds the bounds of the clip on the group. Just trust me.
-            region = region.deriveWithNewBounds(transformedBounds);
-        }
-
-        // We shouldn't do anything with empty region, as we may accidentally make
-        // it non empty or turn it into some nonsense (like (-1,-1,0,0) )
-        if (!region.isEmpty()) {
-            // Now that we have the dirty region, we will simply apply the tx
-            // to it (after slightly padding it for good luck) to get the scene
-            // coordinates for this.
-            region = computePadding(region);
-            region = tx.transform(region, region);
-            region = pvTx.transform(region, region);
-        }
-        return region;
-    }
-
-    /**
-     * LCD Text creates some painful situations where, due to the LCD text
-     * algorithm, we end up with some pixels touched that are normally outside
-     * the bounds. To compensate, we need a hook for NGText to add padding.
-     */
-    protected BaseBounds computePadding(BaseBounds region) {
-        return region;
-    }
-
-    /**
-     * Marks if the node has some visuals and that the bounds change
-     * should be taken into account when using the dirty region.
-     * This will be false for NGGroup (but not for NGRegion)
-     * @return true if the node has some visuals
-     */
-    protected boolean hasVisuals() {
-        return true;
-    }
-
-    /***************************************************************************
-     *                                                                         *
-     * Culling                                                                 *
-     *                                                                         *
-     **************************************************************************/
-
-    /**
-      * Culling support for multiple dirty regions.
-      * Set culling bits for the whole graph.
-      * @param drc Array of dirty regions.
-      * @param tx The transform for this render operation.
-      * @param pvTx Perspective camera transformation.
-      */
-     public void doPreCulling(DirtyRegionContainer drc, BaseTransform tx, GeneralTransform3D pvTx) {
-         markCullRegions(drc, -1, tx, pvTx);
-     }
-
-     /**
-      * Set culling bits for the node.
-      * @param bounds Bounds of the node.
-      * @param regionIndex Index of dirty region used.
-      * @param region Dirty region being processed.
-      * @return Bit setting encoding node position to dirty region (without the shift)
-      */
-     protected int setCullBits(BaseBounds bounds, int regionIndex, RectBounds region) {
-         int b = 0;
-         if (region != null && !region.isEmpty()) {
-             if (region.intersects(bounds)) {
-                 b = 1;
-                 if (region.contains(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())) {
-                     b = 2;
-                 }
-                 cullingBits = cullingBits |  (b << (2 * regionIndex));
-             }
-         }
-         return b;
-     }
-
-     /**
-      * Marks placement of the node in dirty region encoded into 2 bit flag:
-      * 00 - node outside dirty region
-      * 01 - node intersecting dirty region
-      * 11 - node completely within dirty region
-      *
-      * 32 bits = 15 regions max. * 2 bit each.
-      *
-      * @param drc The array of dirty regions.
-      * @param cullingRegionsBitsOfParent culling bits of parent. -1 if there's no parent.
-      * @param tx The transform for this render operation.
-      * @param pvTx Perspective camera transform.
-      */
-     protected abstract void markCullRegions(
-             DirtyRegionContainer drc,
-             int cullingRegionsBitsOfParent,
-             BaseTransform tx, GeneralTransform3D pvTx);
-
-     /**
-      * Helper method draws culling bits for each node.
-      * @param g Graphics.
-      */
-     public abstract void drawCullBits(G g);
-
-    /***************************************************************************
-     *                                                                         *
-     * Rendering                                                               *
-     *                                                                         *
-     **************************************************************************/
-
-    /**
-     * Render the tree of nodes to the specified G (graphics) object
-     * descending from this node as the root. This method is designed to avoid
-     * generated trash as much as possible while descending through the
-     * render graph while rendering. This is the appropriate method both to
-     * initiate painting of an entire scene, and for a branch. The PGGroup
-     * implementation must call this method on each child, not doRender directly.
-     *
-     * @param g The graphics object we're rendering to. This must never be null.
-     */
-    public final void render(G g) {
-        if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Nodes visited during render");
-        if (debug) System.out.println("render called on " + getClass().getSimpleName());
-        // Clear the visuals changed flag
-        if (CLEAR_DIRTY) clearDirty();
-        // If it isn't visible, then punt
-        if (!visible || opacity == 0f) return;
-        // If we are supposed to cull, then go ahead and do so
-//        if (cull(clip, tx)) return;
-
-        // We know that we are going to render this node, so we call the
-        // doRender method, which subclasses implement to do the actual
-        // rendering work.
-        doRender(g);
-    }
-
-    /**
-     * Invoked only by the final render method, this abstract method must
-     * be implemented by subclasses to actually render the node. Implementations
-     * of this method should make sure to save & restore the transform state.
-     */
-    protected abstract void doRender(G g);
-
-    /***************************************************************************
-     *                                                                         *
-     * Stuff                                                                   *
-     *                                                                         *
-     **************************************************************************/
-
-    protected abstract BaseCacheFilter createCacheFilter(CacheHint cacheHint);
-    protected abstract BaseEffectFilter createEffectFilter(Effect effect);
-
-    public void release() {
-    }
-
-    public void applyTransform(final BaseTransform tx, DirtyRegionContainer drc) {
-        for (int i = 0; i < drc.size(); i++) {
-            drc.setDirtyRegion(i, (RectBounds) tx.transform(drc.getDirtyRegion(i), drc.getDirtyRegion(i)));
-            if (drc.checkAndClearRegion(i)) {
-                --i;
-            }
-        }
-    }
-
-    public void applyClip(final BaseBounds clipBounds, DirtyRegionContainer drc) {
-        for (int i = 0; i < drc.size(); i++) {
-            drc.getDirtyRegion(i).intersectWith(clipBounds);
-            if (drc.checkAndClearRegion(i)) {
-                --i;
-            }
-        }
-    }
-
-    public void applyEffect(final BaseEffectFilter effectFilter, DirtyRegionContainer drc, DirtyRegionPool regionPool) {
-        Effect effect = effectFilter.getEffect();
-        EffectDirtyBoundsHelper helper = EffectDirtyBoundsHelper.getInstance();
-        helper.setInputBounds(contentBounds);
-        helper.setDirtyRegions(drc);
-        final DirtyRegionContainer effectDrc = effect.getDirtyRegions(helper, regionPool);
-        drc.deriveWithNewContainer(effectDrc);
-        regionPool.checkIn(effectDrc);
-    }
-
-    private static class EffectDirtyBoundsHelper extends Effect {
-        private BaseBounds bounds;
-        private static EffectDirtyBoundsHelper instance = null;
-        private DirtyRegionContainer drc;
-
-        public void setInputBounds(BaseBounds inputBounds) {
-            bounds = inputBounds;
-        }
-
-        @Override
-        public ImageData filter(FilterContext fctx,
-                BaseTransform transform,
-                Rectangle outputClip,
-                Object renderHelper,
-                Effect defaultInput) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
-            if (bounds.getBoundsType() == BaseBounds.BoundsType.RECTANGLE) {
-                return bounds;
-            } else {
-                //RT-29453 - CCE: in case we get 3D bounds we need to "flatten" them
-                return new RectBounds(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY());
-            }
-        }
-
-        @Override
-        public Effect.AccelType getAccelType(FilterContext fctx) {
-            return null;
-        }
-
-        public static EffectDirtyBoundsHelper getInstance() {
-            if (instance == null) {
-                instance = new EffectDirtyBoundsHelper();
-            }
-            return instance;
-        }
-
-        @Override
-        public boolean reducesOpaquePixels() {
-            return true;
-        }
-
-        private void setDirtyRegions(DirtyRegionContainer drc) {
-            this.drc = drc;
-        }
-
-        @Override
-        public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
-            DirtyRegionContainer ret = regionPool.checkOut();
-            ret.deriveWithNewContainer(drc);
-
-            return ret;
-        }
-
-    }
-
-}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/BaseNodeEffectInput.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import com.sun.scenario.effect.Effect;
-import com.sun.javafx.geom.BaseBounds;
-import com.sun.javafx.geom.RectBounds;
-import com.sun.javafx.geom.transform.BaseTransform;
-
-/**
- */
-public abstract class BaseNodeEffectInput extends Effect {
-    private BaseNode node;
-    private BaseBounds tempBounds = new RectBounds();
-
-    public BaseNodeEffectInput() {
-        this(null);
-    }
-
-    public BaseNodeEffectInput(BaseNode node) {
-        setNode(node);
-    }
-
-    public BaseNode getNode() {
-        return node;
-    }
-
-    public void setNode(BaseNode node) {
-        if (this.node != node) {
-            this.node = node;
-            flush();
-        }
-    }
-
-    @Override
-    public BaseBounds getBounds(BaseTransform transform,
-                              Effect defaultInput)
-    {
-        // TODO: update Effect.getBounds() to take Rectangle2D param so
-        // that we can avoid creating garbage here? (RT-23958)
-        BaseTransform t = transform == null ? 
-                BaseTransform.IDENTITY_TRANSFORM : transform;
-        tempBounds = node.getContentBounds(tempBounds, t);
-        return tempBounds.copy();
-    }
-
-    public abstract void flush();
-}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/DirtyHint.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import java.util.EnumSet;
-
-/*
- * Dirty bounds hints
- */
-public final class DirtyHint {
-    
-    double translateXDelta, translateYDelta;
-
-    public double getTranslateXDelta() {
-        return translateXDelta;
-    }
-
-    public double getTranslateYDelta() {
-        return translateYDelta;
-    }
-}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/GrowableDataBuffer.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,219 +0,0 @@
-/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import java.nio.BufferOverflowException;
-import java.util.Arrays;
-
-/**
- */
-public class GrowableDataBuffer<T> {
-    static final int MIN_BUF_GROW = 1024;
-    static final int MIN_REF_GROW = 32;
-
-    byte buf[];
-    T refs[];
-    int pos;
-    int mark;
-    int savepos;
-    int refpos;
-    int refmark;
-    int refsavepos;
-
-    public GrowableDataBuffer(int initsize) {
-        buf = new byte[initsize];
-    }
-
-    public int position() {
-        return pos;
-    }
-
-    public void save() {
-        savepos = pos;
-        refsavepos = refpos;
-    }
-
-    public void restore() {
-        pos = savepos;
-        refpos = refsavepos;
-    }
-
-    public boolean isEmpty() {
-        return (pos >= mark && refpos >= refmark);
-    }
-
-    public void switchToRead() {
-        mark = pos;
-        refmark = refpos;
-        pos = 0;
-        refpos = 0;
-    }
-
-    public void resetForWrite() {
-        pos = mark = 0;
-        if (refpos > 0 || refmark > 0) {
-            Arrays.fill(refs, 0, Math.max(refpos, refmark), null);
-            refpos = refmark = 0;
-        }
-    }
-
-    public void ensureWriteCapacity(int newbytes) {
-        if (pos + newbytes > buf.length) {
-            if (newbytes < MIN_BUF_GROW) newbytes = MIN_BUF_GROW;
-            buf = Arrays.copyOf(buf, pos + newbytes);
-        }
-    }
-
-    public void ensureReadCapacity(int bytesneeded) {
-        if (pos + bytesneeded > mark) {
-            throw new BufferOverflowException();
-        }
-    }
-
-    public void putBoolean(boolean b) {
-        putByte(b ? (byte) 1 : (byte) 0);
-    }
-
-    public void putByte(byte b) {
-        ensureWriteCapacity(1);
-        buf[pos++] = b;
-    }
-
-    public void putChar(char c) {
-        ensureWriteCapacity(2);
-        buf[pos++] = (byte) (c >>  8);
-        buf[pos++] = (byte) (c      );
-    }
-
-    public void putShort(short s) {
-        ensureWriteCapacity(2);
-        buf[pos++] = (byte) (s >>  8);
-        buf[pos++] = (byte) (s      );
-    }
-
-    public void putInt(int i) {
-        ensureWriteCapacity(4);
-        buf[pos++] = (byte) (i >> 24);
-        buf[pos++] = (byte) (i >> 16);
-        buf[pos++] = (byte) (i >>  8);
-        buf[pos++] = (byte) (i      );
-    }
-
-    public void putLong(long l) {
-        ensureWriteCapacity(8);
-        buf[pos++] = (byte) (l >> 56);
-        buf[pos++] = (byte) (l >> 48);
-        buf[pos++] = (byte) (l >> 40);
-        buf[pos++] = (byte) (l >> 32);
-        buf[pos++] = (byte) (l >> 24);
-        buf[pos++] = (byte) (l >> 16);
-        buf[pos++] = (byte) (l >>  8);
-        buf[pos++] = (byte) (l      );
-    }
-
-    public void putFloat(float f) {
-        putInt(Float.floatToIntBits(f));
-    }
-
-    public void putDouble(double d) {
-        putLong(Double.doubleToLongBits(d));
-    }
-
-    public void putObject(T o) {
-        if (refs == null) {
-            refs = (T[]) new Object[MIN_REF_GROW];
-        } else if (refpos >= refs.length) {
-            refs = Arrays.copyOf(refs, refpos+MIN_REF_GROW);
-        }
-        refs[refpos++] = o;
-    }
-
-    public boolean getBoolean() {
-        ensureReadCapacity(1);
-        return buf[pos++] != 0;
-    }
-
-    public byte getByte() {
-        ensureReadCapacity(1);
-        return buf[pos++];
-    }
-
-    public int getUByte() {
-        ensureReadCapacity(1);
-        return buf[pos++] & 0xff;
-    }
-
-    public char getChar() {
-        ensureReadCapacity(2);
-        int c = buf[pos++];
-        c = (c << 8) | (buf[pos++] & 0xff);
-        return (char) c;
-    }
-
-    public short getShort() {
-        ensureReadCapacity(2);
-        int s = buf[pos++];
-        s = (s << 8) | (buf[pos++] & 0xff);
-        return (short) s;
-    }
-
-    public int getInt() {
-        ensureReadCapacity(4);
-        int i = buf[pos++];
-        i = (i << 8) | (buf[pos++] & 0xff);
-        i = (i << 8) | (buf[pos++] & 0xff);
-        i = (i << 8) | (buf[pos++] & 0xff);
-        return i;
-    }
-
-    public long getLong() {
-        ensureReadCapacity(8);
-        long l = buf[pos++];
-        l = (l << 8) | (buf[pos++] & 0xff);
-        l = (l << 8) | (buf[pos++] & 0xff);
-        l = (l << 8) | (buf[pos++] & 0xff);
-        l = (l << 8) | (buf[pos++] & 0xff);
-        l = (l << 8) | (buf[pos++] & 0xff);
-        l = (l << 8) | (buf[pos++] & 0xff);
-        l = (l << 8) | (buf[pos++] & 0xff);
-        return l;
-    }
-
-    public float getFloat() {
-        return Float.intBitsToFloat(getInt());
-    }
-
-    public double getDouble() {
-        return Double.longBitsToDouble(getLong());
-    }
-
-    public T getObject() {
-        if (refs == null || refpos >= refs.length) {
-            throw new BufferOverflowException();
-        }
-        return refs[refpos++];
-    }
-}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/MediaFrameTracker.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package com.sun.javafx.sg;
-
-/**
- */
-public interface MediaFrameTracker {
-    public void incrementDecodedFrameCount(int count);
-    public void incrementRenderedFrameCount(int count);
-}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/NodePath.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Simple a reusable storage for root-to-node path.
- * 
- */
-public class NodePath<N extends BaseNode> {
-    private List<N> path = new ArrayList<N>();
-    private int position;
-    
-    public NodePath() {
-    }
-    
-    // ITERATION methods
-    
-    public N getCurrentNode() {
-        return path.get(position);
-    }
-
-    public boolean hasNext() {
-        return position > 0;
-    }
-
-    public void next() {
-        if (!hasNext()) {
-            throw new IllegalStateException();
-        }
-        position--;
-    }
-    
-    public void reset() {
-        position = path.size() - 1;
-    }
-    
-    // MODIFICATION methods
-    
-    public void clear() {
-        position = -1;
-        path.clear();
-    }
-    
-    public void add(N n) {
-        path.add(n);
-        position = path.size() - 1;
-    }
-    
-    public int size() {
-        return path.size();
-    }
-    
-    /*
-     * Remove root and set to beginning.
-     */
-    public void removeRoot() {
-        path.remove(path.size() - 1);
-        position = path.size() - 1;
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/BaseCacheFilter.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,618 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import javafx.scene.CacheHint;
+import com.sun.javafx.geom.Rectangle;
+import com.sun.javafx.geom.transform.Affine2D;
+import com.sun.javafx.geom.transform.BaseTransform;
+import com.sun.scenario.effect.FilterContext;
+import com.sun.scenario.effect.Filterable;
+import com.sun.scenario.effect.ImageData;
+
+/*
+ * Base implementation of the Node.cache and cacheHint APIs.
+ *
+ * When all or a portion of the cacheHint becomes enabled, we should try *not*
+ * to re-render the cache.  This avoids a big hiccup at the beginning of the
+ * "use SPEED only while animating" use case:
+ *   0) Under DEFAULT, we should already have a cached image
+ *   1) scale/rotate caching is enabled (no expensive re-render required)
+ *   2) animation happens, using the cached image
+ *   3) animation completes, caching is disable and the node is re-rendered (at
+ *      full-fidelity) with the final transform.
+ *
+ * Certain transform combinations are not supported, notably scaling by unequal
+ * amounts in the x and y directions while also rotating.  Other than simple
+ * translation, animations in this case will require re-rendering every frame.
+ *
+ * Ideally, a simple change to a Node's translation should never regenerate the
+ * cached image.
+ */
+public abstract class BaseCacheFilter {
+    private double lastXDelta;
+    private double lastYDelta;
+    
+    private static final Rectangle TEMP_RECT = new Rectangle();
+
+    /**
+     * Compute the dirty region that must be re-rendered after scrolling
+     */
+    private Rectangle computeDirtyRegionForTranslate() {
+        if (lastXDelta != 0) {
+            if (lastXDelta > 0) {
+                TEMP_RECT.setBounds(0, 0, (int)lastXDelta, cacheBounds.height);
+            } else {
+                TEMP_RECT.setBounds(cacheBounds.width + (int)lastXDelta, 0, -(int)lastXDelta, cacheBounds.height);
+            }
+        } else {
+            if (lastYDelta > 0) {
+                TEMP_RECT.setBounds(0, 0, cacheBounds.width, (int)lastYDelta);
+            } else {
+                TEMP_RECT.setBounds(0, cacheBounds.height + (int)lastYDelta, cacheBounds.width, -(int)lastYDelta);
+            }
+        }
+        return TEMP_RECT;
+    }
+
+    private static enum ScrollCacheState {
+        CHECKING_PRECONDITIONS,
+        ENABLED,
+        DISABLED
+    }
+    private ScrollCacheState scrollCacheState = ScrollCacheState.CHECKING_PRECONDITIONS;
+    
+    // Note: this ImageData is always created and assumed to be untransformed.
+    protected ImageData cachedImageData;
+    private Rectangle cacheBounds = new Rectangle();
+    
+
+    // Used to draw into the cache
+    private Affine2D cachedXform = new Affine2D();
+
+    // The scale and rotate used to draw into the cache
+    private double cachedScaleX;
+    private double cachedScaleY;
+    private double cachedRotate;
+
+    protected double cachedX;
+    protected double cachedY;
+    protected NGNode node;
+
+    // Used to draw the cached image to the screen
+    protected Affine2D screenXform = new Affine2D();
+
+    // Cache hint settings
+    private boolean scaleHint;
+    private boolean rotateHint;
+    // We keep this around for the sake of matchesHint
+    private CacheHint cacheHint;
+
+    // Was the last paint unsupported by the cache?  If so, will need to
+    // regenerate the cache next time.
+    private boolean wasUnsupported = false;
+
+    // Fun with floating point
+    private static final double EPSILON = 0.0000001;
+
+    protected BaseCacheFilter(NGNode node, CacheHint cacheHint) {
+        this.node = node;
+        this.scrollCacheState = ScrollCacheState.CHECKING_PRECONDITIONS;
+        setHint(cacheHint);
+    }
+
+    public void setHint(CacheHint cacheHint) {
+        this.cacheHint = cacheHint;
+        this.scaleHint = (cacheHint == CacheHint.SCALE ||
+                          cacheHint == CacheHint.SCALE_AND_ROTATE);
+        this.rotateHint = (cacheHint == CacheHint.ROTATE ||
+                           cacheHint == CacheHint.SCALE_AND_ROTATE);
+    }
+
+    /**
+     * Indicates whether this BaseCacheFilter's hint matches the CacheHint
+     * passed in.
+     */
+    boolean matchesHint(CacheHint cacheHint) {
+        return this.cacheHint == cacheHint;
+    }
+
+    /**
+     * Implemented by concrete subclasses to create the ImageData for the bitmap
+     * cache.
+     */
+    protected abstract ImageData impl_createImageData(FilterContext fctx,
+                                                      Rectangle bounds);
+    
+    /**
+     * The bounds of the cache
+     */
+    protected abstract Rectangle impl_getCacheBounds(Rectangle bounds, BaseTransform xform);
+    
+    /**
+     * Render node to cache
+     */
+    protected abstract void impl_renderNodeToCache(ImageData imageData,
+                                                   Rectangle cacheBounds,
+                                                   BaseTransform xform,
+                                                   Rectangle dirtyBounds);
+
+    /**
+     * Called on concrete subclasses to render the node directly to the screen,
+     * in the case that the cached image is unexpectedly null.  See RT-6428.
+     */
+    protected abstract void impl_renderNodeToScreen(Object implGraphics,
+                                                    BaseTransform xform);
+
+    /**
+     * Called on concrete subclasses to render the cached image to the screen,
+     * translated by mxt, myt.
+     */
+    protected abstract void impl_renderCacheToScreen(Object implGraphics,
+                                                Filterable implImage,
+                                                double mxt, double myt);
+    
+    /**
+     * Is it possible to use scroll optimization?
+     */
+    protected abstract boolean impl_scrollCacheCapable();
+    
+    /**
+     * Move the subregion of the cache by specified delta
+     */
+    protected abstract void impl_moveCacheBy(ImageData cachedImageData,
+            double lastXDelta, double lastYDelta);
+
+    /**
+     * Render the cached node to the screen, updating the cached image as
+     * necessary given the current cacheHint and specified xform.
+     */
+    public void render(Object implGraphics, BaseTransform xform,
+                       FilterContext fctx) {
+
+        // Note: xform should not be modified, for the sake of Prism
+
+        double mxx = xform.getMxx();
+        double myx = xform.getMyx();
+        double mxy = xform.getMxy();
+        double myy = xform.getMyy();
+        double mxt = xform.getMxt();
+        double myt = xform.getMyt();
+
+        double[] xformInfo = unmatrix(xform);
+        boolean isUnsupported = unsupported(xformInfo);
+
+        
+        lastXDelta = lastXDelta * xformInfo[0];
+        lastYDelta = lastYDelta * xformInfo[1];
+
+        if (cachedImageData != null) {
+            Filterable implImage = cachedImageData.getUntransformedImage();
+            if (implImage != null) {
+                implImage.lock();
+                if (!cachedImageData.validate(fctx)) {
+                    implImage.unlock();
+                    invalidate();
+                }
+            }
+        }
+        if (needToRenderCache(fctx, xform, xformInfo)) {
+            if (cachedImageData != null) {
+                Filterable implImage = cachedImageData.getUntransformedImage();
+                if (implImage != null) {
+                    implImage.unlock();
+                }
+                invalidate();
+            }
+            // Update the cachedXform to the current xform (ignoring translate).
+            cachedXform.setTransform(mxx, myx, mxy, myy, 0.0, 0.0);
+            cachedScaleX = xformInfo[0];
+            cachedScaleY = xformInfo[1];
+            cachedRotate = xformInfo[2];
+            
+            cacheBounds = impl_getCacheBounds(cacheBounds, cachedXform);
+            cachedImageData = impl_createImageData(fctx, cacheBounds);
+            impl_renderNodeToCache(cachedImageData, cacheBounds, cachedXform, null);
+
+            // cachedBounds includes effects, and is in *scene* coords
+            Rectangle cachedBounds = cachedImageData.getUntransformedBounds();
+
+            // Save out the (un-transformed) x & y coordinates.  This accounts
+            // for effects and other reasons the untranslated location may not
+            // be 0,0.
+            cachedX = cachedBounds.x;
+            cachedY = cachedBounds.y;
+
+            // screenXform is always identity in this case, as we've just
+            // rendered into the cache using the render xform.
+            screenXform.setTransform(BaseTransform.IDENTITY_TRANSFORM);
+        } else {
+            if (scrollCacheState == ScrollCacheState.ENABLED && 
+                    (lastXDelta != 0 || lastYDelta != 0) ) {
+                impl_moveCacheBy(cachedImageData, lastXDelta, lastYDelta);
+                impl_renderNodeToCache(cachedImageData, cacheBounds, cachedXform, computeDirtyRegionForTranslate());
+                lastXDelta = lastYDelta = 0;
+            }
+            // Using the cached image; calculate screenXform to paint to screen.
+            if (isUnsupported) {
+                // Only way we should be using the cached image in the
+                // unsupported case is for a change in translate only.  No other
+                // xform should be needed, so use identity.
+
+                // TODO: assert cachedXform == render xform (ignoring translate)
+                //   or  assert xforminfo == cachedXform info (RT-23962)
+                screenXform.setTransform(BaseTransform.IDENTITY_TRANSFORM);
+            } else {
+                updateScreenXform(xformInfo);
+            }
+        }
+        // If this render is unsupported, remember for next time.  We'll need
+        // to regenerate the cache once we're in a supported scenario again.
+        wasUnsupported = isUnsupported;
+
+        Filterable implImage = cachedImageData.getUntransformedImage();
+        if (implImage == null) {
+            impl_renderNodeToScreen(implGraphics, xform);
+        } else {
+            impl_renderCacheToScreen(implGraphics, implImage, mxt, myt);
+            implImage.unlock();
+        }
+    }
+
+    /**
+     * Are we attempting to use cache for an unsupported transform mode?  Mostly
+     * this is for trying to rotate while scaling the object by different
+     * amounts in the x and y directions (this also includes shearing).
+     */
+    boolean unsupported(double[] xformInfo) {
+        double scaleX = xformInfo[0];
+        double scaleY = xformInfo[1];
+        double rotate = xformInfo[2];
+
+        // If we're trying to rotate...
+        if (rotate > EPSILON || rotate < -EPSILON) {
+            // ...and if scaleX != scaleY.  This can be in the render xform, or
+            // may have made it into the cached image.
+            if (scaleX > scaleY + EPSILON || scaleY > scaleX + EPSILON ||
+                scaleX < scaleY - EPSILON || scaleY < scaleX - EPSILON ||
+                cachedScaleX > cachedScaleY + EPSILON ||
+                cachedScaleY > cachedScaleX + EPSILON ||
+                cachedScaleX < cachedScaleY - EPSILON ||
+                cachedScaleY < cachedScaleX - EPSILON ) {
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isXformScrollCacheCapable(double[] xformInfo) {
+        if (unsupported(xformInfo)) {
+            return false;
+        }
+        double rotate = xformInfo[2];
+        return rotateHint || rotate == 0;
+    }
+
+    /*
+     * Do we need to regenerate the cached image?
+     * Assumes that caller locked and validated the cachedImageData.untximage
+     * if not null...
+     */
+    private boolean needToRenderCache(FilterContext fctx, BaseTransform renderXform,
+                                      double[] xformInfo) {
+        if (cachedImageData == null) {
+            return true;
+        }
+        
+        if (lastXDelta != 0 || lastYDelta != 0) {
+            if (Math.abs(lastXDelta) >= cacheBounds.width || Math.abs(lastYDelta) >= cacheBounds.height ||
+                    Math.rint(lastXDelta) != lastXDelta || Math.rint(lastYDelta) != lastYDelta) {
+                node.clearDirtyTree(); // Need to clear dirty (by translation) flags in the children
+                lastXDelta = lastYDelta = 0;
+                return true;
+            }
+            if (scrollCacheState == ScrollCacheState.CHECKING_PRECONDITIONS) {
+                if (impl_scrollCacheCapable() && isXformScrollCacheCapable(xformInfo)) {
+                    scrollCacheState = ScrollCacheState.ENABLED;
+                } else {
+                    scrollCacheState = ScrollCacheState.DISABLED;
+                    return true;
+                }
+            }
+        }
+        
+        // TODO: is == sufficient for floating point comparison here? (RT-23963)
+        if (cachedXform.getMxx() == renderXform.getMxx() &&
+            cachedXform.getMyy() == renderXform.getMyy() &&
+            cachedXform.getMxy() == renderXform.getMxy() &&
+            cachedXform.getMyx() == renderXform.getMyx()) {
+            // It's just a translation - use cached Image
+            return false;
+        }
+        // Not just a translation - if was or is unsupported, then must rerender
+        if (wasUnsupported || unsupported(xformInfo)) {
+            return true;
+        }
+
+        double scaleX = xformInfo[0];
+        double scaleY = xformInfo[1];
+        double rotate = xformInfo[2];
+        if (scaleHint) {
+            if (rotateHint) {
+                return false;
+            } else {
+                // Not caching for rotate: regenerate cache if rotate changed
+                if (cachedRotate - EPSILON < rotate && rotate < cachedRotate + EPSILON) {
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        } else {
+            if (rotateHint) {
+                // Not caching for scale: regenerate cache if scale changed
+                if (cachedScaleX - EPSILON < scaleX && scaleX < cachedScaleX + EPSILON &&
+                    cachedScaleY - EPSILON < scaleY && scaleY < cachedScaleY + EPSILON) {
+                    return false;
+                } else {// Scale is not "equal enough" - regenerate
+                    return true;
+                }
+            }
+            else { // Not caching for anything; always regenerate
+                return true;
+            }
+        }
+    }
+
+    /*
+     * Given the new xform info, update the screenXform as needed to correctly
+     * paint the cache to the screen.
+     */
+    void updateScreenXform(double[] xformInfo) {
+        // screenXform will be the difference between the cachedXform and the
+        // render xform.
+
+        if (scaleHint) {
+            if (rotateHint) {
+                double screenScaleX = xformInfo[0] / cachedScaleX;
+                double screenScaleY = xformInfo[1] / cachedScaleY;
+                double screenRotate = xformInfo[2] - cachedRotate;
+
+                screenXform.setToScale(screenScaleX, screenScaleY);
+                screenXform.rotate(screenRotate);
+            } else {
+                double screenScaleX = xformInfo[0] / cachedScaleX;
+                double screenScaleY = xformInfo[1] / cachedScaleY;
+                screenXform.setToScale(screenScaleX, screenScaleY);
+            }
+        } else {
+            if (rotateHint) {
+                double screenRotate = xformInfo[2] - cachedRotate;
+                screenXform.setToRotation(screenRotate, 0.0, 0.0);
+            } else {
+                // No caching, cache already rendered with xform; just paint it
+                screenXform.setTransform(BaseTransform.IDENTITY_TRANSFORM);
+            }
+        }
+    }
+
+    public void invalidate() {
+        if (scrollCacheState == ScrollCacheState.ENABLED) {
+            scrollCacheState = ScrollCacheState.CHECKING_PRECONDITIONS;
+        }
+        imageDataUnref();
+        lastXDelta = lastYDelta = 0;
+    }
+
+    protected void imageDataUnref() {
+        if (cachedImageData != null) {
+            // While we hold on to this ImageData we leave the texture
+            // unlocked so it can be reclaimed, but the default unref()
+            // method assumes it was locked.
+            Filterable implImage = cachedImageData.getUntransformedImage();
+            if (implImage != null) {
+                implImage.lock();
+            }
+            cachedImageData.unref();
+            cachedImageData = null;
+        }
+    }
+    
+    void invalidateByTranslation(double translateXDelta, double translateYDelta) {
+        if (cachedImageData == null) {
+            return;
+        }
+
+        if (scrollCacheState == ScrollCacheState.DISABLED) {
+            imageDataUnref();
+        } else {
+             // When both mxt and myt change, we don't currently use scroll optimization
+            if (translateXDelta != 0 && translateYDelta != 0) {
+                imageDataUnref();
+            } else {
+                lastYDelta = translateYDelta;
+                lastXDelta = translateXDelta;
+            }
+        }
+    }
+
+    public void dispose() {
+        invalidate();
+        node = null;
+    }
+
+    /*
+     * unmatrix() and the supporting functions are based on the code from
+     * "Decomposing A Matrix Into Simple Transformations" by Spencer W. Thomas
+     * from Graphics Gems II, as found at
+     * http://tog.acm.org/resources/GraphicsGems/
+     * which states, "All code here can be used without restrictions."
+     *
+     * The code was reduced from handling a 4x4 matrix (3D w/ perspective)
+     * to handle just a 2x2 (2D scale/rotate, w/o translate, as that is handled
+     * separately).
+     */
+
+    /**
+     * Given a BaseTransform, decompose it into values for scaleX, scaleY and
+     * rotate.
+     *
+     * The return value is a double[3], the values being:
+     *   [0]: scaleX
+     *   [1]: scaleY
+     *   [2]: rotation angle, in radians, between *** and ***
+     *
+     * From unmatrix() in unmatrix.c
+     */
+    double[] unmatrix(BaseTransform xform) {
+        double[] retVal = new double[3];
+
+        double[][] row = {{xform.getMxx(), xform.getMxy()},
+            {xform.getMyx(), xform.getMyy()}};
+        final double xSignum = Math.signum(row[0][0]);
+        final double ySignum = Math.signum(row[1][1]);
+
+        // Compute X scale factor and normalize first row.
+        // tran[U_SCALEX] = V3Length(&row[0]);
+        // row[0] = *V3Scale(&row[0], 1.0);
+
+        double scaleX = xSignum * v2length(row[0]);
+        v2scale(row[0], xSignum);
+
+        // Compute XY shear factor and make 2nd row orthogonal to 1st.
+        // tran[U_SHEARXY] = V3Dot(&row[0], &row[1]);
+        // (void)V3Combine(&row[1], &row[0], &row[1], 1.0, -tran[U_SHEARXY]);
+        //
+        // "this is too large by the y scaling factor"
+        double shearXY = v2dot(row[0], row[1]);
+
+        // Combine into row[1]
+        v2combine(row[1], row[0], row[1], 1.0, -shearXY);
+
+        // Now, compute Y scale and normalize 2nd row
+        // tran[U_SCALEY] = V3Length(&row[1]);
+        // V3Scale(&row[1], 1.0);
+        // tran[U_SHEARXY] /= tran[U_SCALEY];
+
+        double scaleY = ySignum * v2length(row[1]);
+        v2scale(row[1], ySignum);
+
+        // Now extract the rotation. (This is new code, not from the Gem.)
+        //
+        // In our matrix, we now have
+        // [   cos(theta)    -sin(theta)    ]
+        // [   sin(theta)     cos(theta)    ]
+        //
+        // TODO: assert: all 4 values are sane (RT-23962)
+        //
+        double sin = row[1][0];
+        double cos = row[0][0];
+        double angleRad = 0.0;
+
+        // Recall:
+        // arcsin works for theta: -90 -> 90
+        // arccos works for theta:   0 -> 180
+        if (sin >= 0) {
+            // theta is 0 -> 180, use acos()
+            angleRad = Math.acos(cos);
+        } else {
+            if (cos > 0) {
+                // sin < 0, cos > 0, so theta is 270 -> 360, aka -90 -> 0
+                // use asin(), add 360
+                angleRad = 2.0 * Math.PI + Math.asin(sin);
+            } else {
+                // sin < 0, cos < 0, so theta 180 -> 270
+                // cos from 180 -> 270 is inverse of cos from 0->90,
+                // so take acos(-cos) and add 180
+                angleRad = Math.PI + Math.acos(-cos);
+            }
+        }
+
+        retVal[0] = scaleX;
+        retVal[1] = scaleY;
+        retVal[2] = angleRad;
+
+        return retVal;
+    }
+
+
+    /**
+     * make a linear combination of two vectors and return the result
+     * result = (v0 * scalarA) + (v1 * scalarB)
+     *
+     * From V3Combine() in GGVecLib.c
+     */
+    void v2combine(double v0[], double v1[], double result[], double scalarA, double scalarB) {
+        // make a linear combination of two vectors and return the result.
+        // result = (a * ascl) + (b * bscl)
+        /*
+        Vector3 *V3Combine (a, b, result, ascl, bscl)
+        Vector3 *a, *b, *result;
+        double ascl, bscl;
+        {
+                result->x = (ascl * a->x) + (bscl * b->x);
+                result->y = (ascl * a->y) + (bscl * b->y);
+                result->z = (ascl * a->z) + (bscl * b->z);
+                return(result);
+        */
+
+        result[0] = scalarA*v0[0] + scalarB*v1[0];
+        result[1] = scalarA*v0[1] + scalarB*v1[1];
+    }
+
+
+    /**
+     * dot product of 2 vectors of length 2
+     */
+    double v2dot(double v0[], double v1[]) {
+        return v0[0]*v1[0] + v0[1]*v1[1];
+    }
+
+    /**
+     * scale v[] to be relative to newLen
+     *
+     * From V3Scale() in GGVecLib.c
+     */
+    double[] v2scale(double v[], double newLen) {
+        double len = v2length(v);
+        double[] retVal = v;
+        if (len != 0) {
+            v[0] *= newLen / len;
+            v[1] *= newLen / len;
+        }
+        return retVal;
+    }
+
+    /**
+     * returns length of input vector
+     *
+     * Based on V3Length() in GGVecLib.c
+     */
+    double v2length(double v[]) {
+        return Math.sqrt(v[0]*v[0] + v[1]*v[1]);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/BaseEffectFilter.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import com.sun.javafx.geom.BaseBounds;
+import com.sun.javafx.geom.transform.BaseTransform;
+import com.sun.scenario.effect.Effect;
+
+/**
+ */
+public abstract class BaseEffectFilter {
+    private Effect effect;
+    private BaseNodeEffectInput nodeInput;
+
+    protected BaseEffectFilter(Effect effect, NGNode node) {
+        this.effect = effect;
+        this.nodeInput = createNodeEffectInput(node);
+    }
+
+    public Effect getEffect() { return effect; }
+
+    public BaseNodeEffectInput getNodeInput() { return nodeInput; }
+
+    protected void dispose() {
+        effect = null;
+        nodeInput.setNode(null);
+        nodeInput = null;
+    }
+
+    public BaseBounds getBounds(BaseBounds bounds, BaseTransform xform) {
+        BaseBounds r = getEffect().getBounds(xform, nodeInput);
+        return bounds.deriveWithNewBounds(r);
+    }
+
+    protected abstract BaseNodeEffectInput createNodeEffectInput(NGNode node);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/BaseNodeEffectInput.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import com.sun.javafx.geom.BaseBounds;
+import com.sun.javafx.geom.RectBounds;
+import com.sun.javafx.geom.transform.BaseTransform;
+import com.sun.scenario.effect.Effect;
+
+/**
+ */
+public abstract class BaseNodeEffectInput extends Effect {
+    private NGNode node;
+    private BaseBounds tempBounds = new RectBounds();
+
+    public BaseNodeEffectInput() {
+        this(null);
+    }
+
+    public BaseNodeEffectInput(NGNode node) {
+        setNode(node);
+    }
+
+    public NGNode getNode() {
+        return node;
+    }
+
+    public void setNode(NGNode node) {
+        if (this.node != node) {
+            this.node = node;
+            flush();
+        }
+    }
+
+    @Override
+    public BaseBounds getBounds(BaseTransform transform,
+                              Effect defaultInput)
+    {
+        // TODO: update Effect.getBounds() to take Rectangle2D param so
+        // that we can avoid creating garbage here? (RT-23958)
+        BaseTransform t = transform == null ? 
+                BaseTransform.IDENTITY_TRANSFORM : transform;
+        tempBounds = node.getContentBounds(tempBounds, t);
+        return tempBounds.copy();
+    }
+
+    public abstract void flush();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/DirtyHint.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+/*
+ * Dirty bounds hints
+ */
+public final class DirtyHint {
+    
+    double translateXDelta, translateYDelta;
+
+    public double getTranslateXDelta() {
+        return translateXDelta;
+    }
+
+    public double getTranslateYDelta() {
+        return translateYDelta;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/GrowableDataBuffer.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import java.nio.BufferOverflowException;
+import java.util.Arrays;
+
+/**
+ */
+public class GrowableDataBuffer<T> {
+    static final int MIN_BUF_GROW = 1024;
+    static final int MIN_REF_GROW = 32;
+
+    byte buf[];
+    T refs[];
+    int pos;
+    int mark;
+    int savepos;
+    int refpos;
+    int refmark;
+    int refsavepos;
+
+    public GrowableDataBuffer(int initsize) {
+        buf = new byte[initsize];
+    }
+
+    public int position() {
+        return pos;
+    }
+
+    public void save() {
+        savepos = pos;
+        refsavepos = refpos;
+    }
+
+    public void restore() {
+        pos = savepos;
+        refpos = refsavepos;
+    }
+
+    public boolean isEmpty() {
+        return (pos >= mark && refpos >= refmark);
+    }
+
+    public void switchToRead() {
+        mark = pos;
+        refmark = refpos;
+        pos = 0;
+        refpos = 0;
+    }
+
+    public void resetForWrite() {
+        pos = mark = 0;
+        if (refpos > 0 || refmark > 0) {
+            Arrays.fill(refs, 0, Math.max(refpos, refmark), null);
+            refpos = refmark = 0;
+        }
+    }
+
+    public void ensureWriteCapacity(int newbytes) {
+        if (pos + newbytes > buf.length) {
+            if (newbytes < MIN_BUF_GROW) newbytes = MIN_BUF_GROW;
+            buf = Arrays.copyOf(buf, pos + newbytes);
+        }
+    }
+
+    public void ensureReadCapacity(int bytesneeded) {
+        if (pos + bytesneeded > mark) {
+            throw new BufferOverflowException();
+        }
+    }
+
+    public void putBoolean(boolean b) {
+        putByte(b ? (byte) 1 : (byte) 0);
+    }
+
+    public void putByte(byte b) {
+        ensureWriteCapacity(1);
+        buf[pos++] = b;
+    }
+
+    public void putChar(char c) {
+        ensureWriteCapacity(2);
+        buf[pos++] = (byte) (c >>  8);
+        buf[pos++] = (byte) (c      );
+    }
+
+    public void putShort(short s) {
+        ensureWriteCapacity(2);
+        buf[pos++] = (byte) (s >>  8);
+        buf[pos++] = (byte) (s      );
+    }
+
+    public void putInt(int i) {
+        ensureWriteCapacity(4);
+        buf[pos++] = (byte) (i >> 24);
+        buf[pos++] = (byte) (i >> 16);
+        buf[pos++] = (byte) (i >>  8);
+        buf[pos++] = (byte) (i      );
+    }
+
+    public void putLong(long l) {
+        ensureWriteCapacity(8);
+        buf[pos++] = (byte) (l >> 56);
+        buf[pos++] = (byte) (l >> 48);
+        buf[pos++] = (byte) (l >> 40);
+        buf[pos++] = (byte) (l >> 32);
+        buf[pos++] = (byte) (l >> 24);
+        buf[pos++] = (byte) (l >> 16);
+        buf[pos++] = (byte) (l >>  8);
+        buf[pos++] = (byte) (l      );
+    }
+
+    public void putFloat(float f) {
+        putInt(Float.floatToIntBits(f));
+    }
+
+    public void putDouble(double d) {
+        putLong(Double.doubleToLongBits(d));
+    }
+
+    public void putObject(T o) {
+        if (refs == null) {
+            refs = (T[]) new Object[MIN_REF_GROW];
+        } else if (refpos >= refs.length) {
+            refs = Arrays.copyOf(refs, refpos+MIN_REF_GROW);
+        }
+        refs[refpos++] = o;
+    }
+
+    public boolean getBoolean() {
+        ensureReadCapacity(1);
+        return buf[pos++] != 0;
+    }
+
+    public byte getByte() {
+        ensureReadCapacity(1);
+        return buf[pos++];
+    }
+
+    public int getUByte() {
+        ensureReadCapacity(1);
+        return buf[pos++] & 0xff;
+    }
+
+    public char getChar() {
+        ensureReadCapacity(2);
+        int c = buf[pos++];
+        c = (c << 8) | (buf[pos++] & 0xff);
+        return (char) c;
+    }
+
+    public short getShort() {
+        ensureReadCapacity(2);
+        int s = buf[pos++];
+        s = (s << 8) | (buf[pos++] & 0xff);
+        return (short) s;
+    }
+
+    public int getInt() {
+        ensureReadCapacity(4);
+        int i = buf[pos++];
+        i = (i << 8) | (buf[pos++] & 0xff);
+        i = (i << 8) | (buf[pos++] & 0xff);
+        i = (i << 8) | (buf[pos++] & 0xff);
+        return i;
+    }
+
+    public long getLong() {
+        ensureReadCapacity(8);
+        long l = buf[pos++];
+        l = (l << 8) | (buf[pos++] & 0xff);
+        l = (l << 8) | (buf[pos++] & 0xff);
+        l = (l << 8) | (buf[pos++] & 0xff);
+        l = (l << 8) | (buf[pos++] & 0xff);
+        l = (l << 8) | (buf[pos++] & 0xff);
+        l = (l << 8) | (buf[pos++] & 0xff);
+        l = (l << 8) | (buf[pos++] & 0xff);
+        return l;
+    }
+
+    public float getFloat() {
+        return Float.intBitsToFloat(getInt());
+    }
+
+    public double getDouble() {
+        return Double.longBitsToDouble(getLong());
+    }
+
+    public T getObject() {
+        if (refs == null || refpos >= refs.length) {
+            throw new BufferOverflowException();
+        }
+        return refs[refpos++];
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/MediaFrameTracker.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.javafx.sg.prism;
+
+/**
+ */
+public interface MediaFrameTracker {
+    public void incrementDecodedFrameCount(int count);
+    public void incrementRenderedFrameCount(int count);
+}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGArc.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGArc.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,11 +25,11 @@
 
 package com.sun.javafx.sg.prism;
 
+import javafx.scene.shape.ArcType;
 import com.sun.javafx.geom.Arc2D;
 import com.sun.javafx.geom.Shape;
 import com.sun.prism.Graphics;
 import com.sun.prism.shape.ShapeRep;
-import javafx.scene.shape.ArcType;
 
 /**
  *
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGCanvas.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGCanvas.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,6 +25,10 @@
 
 package com.sun.javafx.sg.prism;
 
+import javafx.geometry.VPos;
+import javafx.scene.text.Font;
+import java.nio.IntBuffer;
+import java.util.LinkedList;
 import com.sun.javafx.font.PGFont;
 import com.sun.javafx.geom.Arc2D;
 import com.sun.javafx.geom.BaseBounds;
@@ -38,7 +42,6 @@
 import com.sun.javafx.geom.transform.Affine2D;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
-import com.sun.javafx.sg.GrowableDataBuffer;
 import com.sun.javafx.text.PrismTextLayout;
 import com.sun.prism.BasicStroke;
 import com.sun.prism.CompositeMode;
@@ -59,10 +62,6 @@
 import com.sun.scenario.effect.impl.prism.PrDrawable;
 import com.sun.scenario.effect.impl.prism.PrFilterContext;
 import com.sun.scenario.effect.impl.prism.PrTexture;
-import java.nio.IntBuffer;
-import java.util.LinkedList;
-import javafx.geometry.VPos;
-import javafx.scene.text.Font;
 
 /**
  */
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGExternalNode.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGExternalNode.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,17 +25,16 @@
 
 package com.sun.javafx.sg.prism;
 
+import java.nio.Buffer;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.UnaryOperator;
 import com.sun.javafx.geom.Rectangle;
 import com.sun.prism.Graphics;
 import com.sun.prism.PixelFormat;
 import com.sun.prism.ResourceFactory;
 import com.sun.prism.Texture;
 
-import java.nio.Buffer;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.UnaryOperator;
-
 public class NGExternalNode extends NGNode {
     
     private Texture dsttexture;
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGGroup.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGGroup.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,13 +25,14 @@
 
 package com.sun.javafx.sg.prism;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import com.sun.javafx.geom.DirtyRegionContainer;
 import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.Rectangle;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.transform.GeneralTransform3D;
-import com.sun.javafx.sg.BaseNode;
-import com.sun.javafx.sg.NodePath;
 import com.sun.prism.Graphics;
 import com.sun.scenario.effect.Blend;
 import com.sun.scenario.effect.Blend.Mode;
@@ -40,10 +41,6 @@
 import com.sun.scenario.effect.impl.prism.PrDrawable;
 import com.sun.scenario.effect.impl.prism.PrEffectHelper;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 /**
  */
 public class NGGroup extends NGNode {
@@ -94,7 +91,7 @@
         // a waste of processing because the FX side should be doing this
         // work. TODO We should only do this when assertions are enabled
         // (RT-26980)
-        BaseNode child = (BaseNode)node;
+        NGNode child = node;
         // NOTE: We used to do checks here to make sure that a node
         // being added didn't already have another parent listed as
         // its parent. Now we just silently accept them. The FX side
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGLightBase.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGLightBase.java	Fri Jul 12 08:35:56 2013 -0700
@@ -27,7 +27,6 @@
 
 import com.sun.javafx.geom.transform.Affine3D;
 import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.javafx.sg.BaseNode;
 import com.sun.prism.Graphics;
 import com.sun.prism.paint.Color;
 
@@ -115,7 +114,7 @@
             for (int i = 0; i < scopedNodes.length; i++) {
                 Object scopedNode = scopedNodes[i];
                 if (scopedNode instanceof NGGroup) {
-                    BaseNode parent = n3d.getParent();
+                    NGNode parent = n3d.getParent();
                     while (parent != null) {
                         if (scopedNode == parent) {
                             return true;
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGNode.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGNode.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,8 +25,11 @@
 
 package com.sun.javafx.sg.prism;
 
+import javafx.scene.CacheHint;
+import java.util.List;
 import com.sun.glass.ui.Screen;
 import com.sun.javafx.geom.BaseBounds;
+import com.sun.javafx.geom.BoxBounds;
 import com.sun.javafx.geom.DirtyRegionContainer;
 import com.sun.javafx.geom.DirtyRegionPool;
 import com.sun.javafx.geom.Point2D;
@@ -35,11 +38,7 @@
 import com.sun.javafx.geom.transform.Affine3D;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.transform.GeneralTransform3D;
-import com.sun.javafx.sg.BaseCacheFilter;
-import com.sun.javafx.sg.BaseEffectFilter;
-import com.sun.javafx.sg.BaseNode;
-import com.sun.javafx.sg.BaseNodeEffectInput;
-import com.sun.javafx.sg.NodePath;
+import com.sun.javafx.geom.transform.NoninvertibleTransformException;
 import com.sun.prism.CompositeMode;
 import com.sun.prism.Graphics;
 import com.sun.prism.GraphicsPipeline;
@@ -57,18 +56,33 @@
 import com.sun.scenario.effect.impl.prism.PrDrawable;
 import com.sun.scenario.effect.impl.prism.PrEffectHelper;
 import com.sun.scenario.effect.impl.prism.PrFilterContext;
-import javafx.scene.CacheHint;
-
-import java.util.List;
-
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 
-
 /**
- * Basic implementation of node.
+ * NGNode is the abstract base class peer of Node, forming
+ * the basis for Prism and Scenario render graphs.
+ * <p>
+ * During synchronization, the FX scene graph will pass down to us
+ * the transform which takes us from local space to parent space, the
+ * content bounds (ie: geom bounds), and the transformed bounds
+ * (ie: boundsInParent), and the clippedBounds. The effect bounds have
+ * already been passed to the Effect peer (if there is one).
+ * <p>
+ * Whenever the transformedBounds of the NGNode are changed, we update
+ * the dirtyBounds, so that the next time we need to accumulate dirty
+ * regions, we will have the information we need to make sure we create
+ * an appropriate dirty region.
+ * <p>
+ * NGNode maintains a single "dirty" flag, which indicates that this
+ * node itself is dirty and must contribute to the dirty region. More
+ * specifically, it indicates that this node is now dirty with respect
+ * to the back buffer. Any rendering of the scene which will go on the
+ * back buffer will cause the dirty flag to be cleared, whereas a
+ * rendering of the scene which is for an intermediate image will not
+ * clear this dirty flag.
  */
-public abstract class NGNode extends BaseNode<Graphics> {
+public abstract class NGNode {
     protected static float highestPixelScale;
     static {
         // TODO: temporary until RT-27958 is fixed. Screens may be null or could be not initialized
@@ -89,16 +103,1154 @@
     private final static Boolean effectsSupported =
         (pipeline == null ? false : pipeline.isEffectSupported());
 
+    protected static enum DirtyFlag {
+        CLEAN,
+        // Means that the node is dirty, but only because of translation
+        DIRTY_BY_TRANSLATION,
+        DIRTY
+    }
+
+    public boolean debug = false;
+
+    /**
+     * Temporary bounds for use by this class or subclasses, designed to
+     * reduce the amount of garbage we generate. If we get to the point
+     * where we have multi-threaded rasterization, we might need to make
+     * this per-instance instead of static.
+     */
+    protected static final BaseBounds TEMP_BOUNDS = new BoxBounds();
+    protected static final RectBounds TEMP_RECT_BOUNDS = new RectBounds();
+    protected static final Affine3D TEMP_TRANSFORM = new Affine3D();
+
+    /**
+     * The transform for this node. Although we are handed all the bounds
+     * during synchronization (including the transformed bounds), we still
+     * need the transform so that we can apply it to the clip and so forth
+     * while accumulating dirty regions and rendering.
+     */
+    private BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
+
+    /**
+     * The cached transformed bounds. This is never null, but is frequently set
+     * to be invalid whenever the bounds for the node have changed. These are
+     * "complete" bounds, that is, with transforms and effect and clip applied.
+     * Note that this is equivalent to boundsInParent in FX.
+     */
+    protected BaseBounds transformedBounds = new RectBounds();
+
+    /**
+     * The cached bounds. This is never null, but is frequently set to be
+     * invalid whenever the bounds for the node have changed. These are the
+     * "content" bounds, that is, without transforms or filters applied.
+     */
+    protected BaseBounds contentBounds = new RectBounds();
+
+    /**
+     * We keep a reference to the last transform bounds that were valid
+     * and known. We do this to significantly speed up the rendering of the
+     * scene by culling and clipping based on "dirty" regions, which are
+     * essentially the rectangle formed by the union of the dirtyBounds
+     * and the transformedBounds.
+     */
+    private BaseBounds dirtyBounds = new RectBounds();
+
+    /**
+     * Whether the node is visible. We need to know about the visibility of
+     * the node so that we can determine whether to cull it out, and perform
+     * other such optimizations.
+     */
+    private boolean visible = true;
+
+    /**
+     * Indicates that this NGNode is itself dirty and needs its full bounds
+     * included in the next repaint. This means it is dirty with respect to
+     * the back buffer. We don't bother differentiating between bounds dirty
+     * and visuals dirty because we can simply inspect the dirtyBounds to
+     * see if it is valid. If so, then bounds must be dirty.
+     */
+    protected DirtyFlag dirty = DirtyFlag.DIRTY;
+
+    /**
+     * The parent of the node. In the case of a normal render graph node,
+     * this will be an NGGroup. However, if this node is being used as
+     * a clip node, then the parent is the node it is the clip for.
+     */
+    private NGNode parent;
+
+    /**
+     * True if this node is a clip. This means the parent is clipped by this node.
+     */
+    private boolean isClip;
+
+    /**
+     * The node used for specifying the clipping shape for this node. If null,
+     * then there is no clip.
+     */
+    private NGNode clipNode;
+
+    /**
+     * The opacity of this node.
+     */
+    private float opacity = 1f;
+
+    /**
+     * The blend mode that controls how the pixels of this node blend into
+     * the rest of the scene behind it.
+     */
+    private Blend.Mode nodeBlendMode;
+
+    /**
+     * The depth test flag for this node. It is used when rendering if the window
+     * into which we are rendering has a depth buffer.
+     */
+    private boolean depthTest = true;
+
+    /**
+     * A filter used when the node is cached. If null, then the node is not
+     * being cached. While in theory this could be created automatically by
+     * the implementation due to some form of heuristic, currently we
+     * only set this if the application has requested that the node be cached.
+     */
+    private BaseCacheFilter cacheFilter;
+
+    /**
+     * A filter used whenever an effect is placed on the node. Of course
+     * effects can form a kind of tree, such that this one effect might be
+     * an accumulation of several different effects. This will be null if
+     * there are no effects on the FX scene graph node.
+     */
+    private BaseEffectFilter effectFilter;
+
+    /**
+     * If this node is an NGGroup, then this flag will be used to indicate
+     * whether one or more of its children is dirty. While it would seem this
+     * flag should be on NGGroup, the code turns out to be a bit cleaner with
+     * this flag in the NGNode class.
+     */
+    protected boolean childDirty = false;
+
+    /**
+     * How many children are going to be accumulated
+     */
+    protected int dirtyChildrenAccumulated = 0;
+
+    /**
+     * Do not iterate over all children in group. Mark group as dirty
+     * when threshold was reached.
+     */
+    protected final static int DIRTY_CHILDREN_ACCUMULATED_THRESHOLD = 12;
+
+    /**
+     * Marks position of this node in dirty regions.
+     */
+    protected int cullingBits = 0x0;
+
     private RectBounds opaqueRegion = null;
     private boolean opaqueRegionInvalid = true;
+    private DirtyHint hint;
 
     protected NGNode() { }
 
     /***************************************************************************
      *                                                                         *
-     *                     Culling Related Methods.                            *
+     *                Methods invoked during synchronization                   *
      *                                                                         *
      **************************************************************************/
+
+    /**
+     * Called by the FX scene graph to tell us whether we should be visible or not.
+     * @param value whether it is visible
+     */
+    public void setVisible(boolean value) {
+        // If the visibility changes, we need to mark this node as being dirty.
+        // If this node is being cached, changing visibility should have no
+        // effect, since it doesn't affect the rendering of the content in
+        // any way. If we were to release the cached image, that might thwart
+        // the developer's attempt to improve performance for things that
+        // rapidly appear and disappear but which are expensive to render.
+        // Ancestors, of course, must still have their caches invalidated.
+        if (visible != value) {
+            this.visible = value;
+            markDirty();
+        }
+    }
+
+    /**
+     * Called by the FX scene graph to tell us what our new content bounds are.
+     * @param bounds must not be null
+     */
+    public void setContentBounds(BaseBounds bounds) {
+        // Note, there isn't anything to do here. We're dirty if geom or
+        // visuals or transformed bounds or effects or clip have changed.
+        // There's no point dealing with it here.
+        contentBounds = contentBounds.deriveWithNewBounds(bounds);
+    }
+
+    /**
+     * Called by the FX scene graph to tell us what our transformed bounds are.
+     * @param bounds must not be null
+     */
+    public void setTransformedBounds(BaseBounds bounds, boolean byTransformChangeOnly) {
+        if (transformedBounds.equals(bounds)) {
+            // There has been no change, so ignore. It turns out this happens
+            // a lot, because when a leaf has dirty bounds, all parents also
+            // assume their bounds have changed, and only when they recompute
+            // their bounds do we discover otherwise. This check could happen
+            // on the FX side, however, then the FX side needs to cache the
+            // former content bounds at the time of the last sync or needs to
+            // be able to read state back from the NG side. Yuck. Just doing
+            // it here for now.
+            return;
+        }
+        // If the transformed bounds have changed, then we need to save off the
+        // transformed bounds into the dirty bounds, so that the resulting
+        // dirty region will be correct. If this node is cached, we DO NOT
+        // invalidate the cache. The cacheFilter will compare its cached
+        // transform to the accumulated transform to determine whether the
+        // cache needs to be regenerated. So we will not invalidate it here.
+        if (dirtyBounds.isEmpty()) {
+            dirtyBounds = dirtyBounds.deriveWithNewBounds(transformedBounds);
+            dirtyBounds = dirtyBounds.deriveWithUnion(bounds);
+        } else {
+            // TODO I think this is vestigial from Scenario and will never
+            // actually occur in real life... (RT-23956)
+            dirtyBounds = dirtyBounds.deriveWithUnion(transformedBounds);
+        }
+        transformedBounds = transformedBounds.deriveWithNewBounds(bounds);
+        if (hasVisuals() && !byTransformChangeOnly) {
+            markDirty();
+        }
+    }
+
+    /**
+     * Called by the FX scene graph to tell us what our transform matrix is.
+     * @param tx must not be null
+     */
+    public void setTransformMatrix(BaseTransform tx) {
+        // If the transform matrix has changed, then we need to update it,
+        // and mark this node as dirty. If this node is cached, we DO NOT
+        // invalidate the cache. The cacheFilter will compare its cached
+        // transform to the accumulated transform to determine whether the
+        // cache needs to be regenerated. So we will not invalidate it here.
+        // This approach allows the cached image to be reused in situations
+        // where only the translation parameters of the accumulated transform
+        // are changing. The scene will still be marked dirty and cached
+        // images of any ancestors will be invalidated.
+        boolean useHint = false;
+
+        // If the parent is cached, try to check if the transformation is only a translation
+        if (parent != null && parent.cacheFilter != null) {
+            if (hint == null) {
+                hint = new DirtyHint();
+            // If there's no hint created yet, this is the first setTransformMatrix
+            // call and we have nothing to compare to yet.
+            } else {
+                if (transform.getMxx() == tx.getMxx()
+                        && transform.getMxy() == tx.getMxy()
+                        && transform.getMyy() == tx.getMyy()
+                        && transform.getMyx() == tx.getMyx()
+                        && transform.getMxz() == tx.getMxz()
+                        && transform.getMyz() == tx.getMyz()
+                        && transform.getMzx() == tx.getMzx()
+                        && transform.getMzy() == tx.getMzy()
+                        && transform.getMzz() == tx.getMzz()
+                        && transform.getMzt() == tx.getMzt()) {
+                    useHint = true;
+                    hint.translateXDelta = tx.getMxt() - transform.getMxt();
+                    hint.translateYDelta = tx.getMyt() - transform.getMyt();
+                }
+            }
+        }
+
+        transform = transform.deriveWithNewTransform(tx);
+        if (useHint) {
+            markDirtyByTranslation();
+        } else {
+            markDirty();
+        }
+    }
+
+    /**
+     * Called by the FX scene graph whenever the clip node for this node changes.
+     * @param clipNode can be null if the clip node is being cleared
+     */
+    public void setClipNode(NGNode clipNode) {
+        // Whenever the clipNode itself has changed (that is, the reference to
+        // the clipNode), we need to be sure to mark this node dirty and to
+        // invalidate the cache of this node (if there is one) and all parents.
+        if (clipNode != this.clipNode) {
+            // Clear the "parent" property of the clip node, if there was one
+            if (this.clipNode != null) this.clipNode.setParent(null);
+            // Make the "parent" property of the clip node point to this
+            if (clipNode != null) clipNode.setParent(this, true);
+            // Keep the reference to the new clip node
+            this.clipNode = clipNode;
+            // Mark this node dirty, invalidate its cache, and all parents.
+            visualsChanged();
+        }
+    }
+
+    /**
+     * Called by the FX scene graph whenever the opacity for the node changes.
+     * We create a special filter when the opacity is < 1.
+     * @param opacity A value between 0 and 1.
+     */
+    public void setOpacity(float opacity) {
+        // Check the argument to make sure it is valid.
+        if (opacity < 0 || opacity > 1) {
+            throw new IllegalArgumentException("Internal Error: The opacity must be between 0 and 1");
+        }
+        // If the opacity has changed, react. If this node is being cached,
+        // then we do not want to invalidate the cache due to an opacity
+        // change. However, as usual, all parent caches must be invalidated.
+        if (opacity != this.opacity) {
+            this.opacity = opacity;
+            markDirty();
+        }
+    }
+
+    /**
+     * Set by the FX scene graph.
+     * @param blendMode may be null to indicate "default"
+     */
+    public void setNodeBlendMode(Blend.Mode blendMode) {
+        // If the blend mode has changed, react. If this node is being cached,
+        // then we do not want to invalidate the cache due to a compositing
+        // change. However, as usual, all parent caches must be invalidated.
+        if (this.nodeBlendMode != blendMode) {
+            this.nodeBlendMode = blendMode;
+            markDirty();
+        }
+    }
+
+    /**
+     * Called by the FX scene graph whenever the derived depth test flag for
+     * the node changes.
+     * @param depthTest indicates whether to perform a depth test operation
+     * (if the window has a depth buffer).
+     */
+    public void setDepthTest(boolean depthTest) {
+        // If the depth test flag has changed, react.
+        if (depthTest != this.depthTest) {
+            this.depthTest = depthTest;
+            // Mark this node dirty, invalidate its cache, and all parents.
+            visualsChanged();
+        }
+    }
+
+    /**
+     * Called by the FX scene graph whenever "cached" or "cacheHint" changes.
+     * These hints provide a way for the developer to indicate whether they
+     * want this node to be cached as a raster, which can be quite a performance
+     * optimization in some cases (and lethal in others).
+     * @param cached specifies whether or not this node should be cached
+     * @param cacheHint never null, indicates some hint as to how to cache
+     */
+    public void setCachedAsBitmap(boolean cached, CacheHint cacheHint) {
+        // Validate the arguments
+        if (cacheHint == null) {
+            throw new IllegalArgumentException("Internal Error: cacheHint must not be null");
+        }
+
+        if (cached) {
+            if (cacheFilter == null) {
+                cacheFilter = createCacheFilter(cacheHint);
+                // We do not technically need to do a render pass here, but if
+                // we wait for the next render pass to cache it, then we will
+                // cache not the current visuals, but the visuals as defined
+                // by any transform changes that happen between now and then.
+                // Repainting now encourages the cached version to be as close
+                // as possible to the state of the node when the cache hint
+                // was set...
+                markDirty();
+            } else {
+                if (!cacheFilter.matchesHint(cacheHint)) {
+                    cacheFilter.setHint(cacheHint);
+                    // Different hints may have different requirements of
+                    // whether the cache is stale.  We do not have enough info
+                    // right here to evaluate that, but it will be determined
+                    // naturally during a repaint cycle.
+                    // If the new hint is more relaxed (QUALITY => SPEED for
+                    // instance) then rendering should be quick.
+                    // If the new hint is more restricted (SPEED => QUALITY)
+                    // then we need to render to improve the results anyway.
+                    markDirty();
+                }
+            }
+        } else {
+            if (cacheFilter != null) {
+                cacheFilter.dispose();
+                cacheFilter = null;
+                // A cache will often look worse than uncached rendering.  It
+                // may look the same in some circumstances, and this may then
+                // be an unnecessary rendering pass, but we do not have enough
+                // information here to be able to optimize that when possible.
+                markDirty();
+            }
+        }
+    }
+
+    /**
+     * Called by the FX scene graph to set the effect.
+     * @param effect the effect (can be null to clear it)
+     */
+    public void setEffect(Object effect) {
+        // We only need to take action if the effect is different than what was
+        // set previously. There are four possibilities. Of these, #1 and #3 matter:
+        // 0. effectFilter == null, effect == null
+        // 1. effectFilter == null, effect != null
+        // 2. effectFilter != null, effectFilter.effect == effect
+        // 3. effectFilter != null, effectFilter.effect != effect
+        // In any case where the effect is changed, we must both invalidate
+        // the cache for this node (if there is one) and all parents, and mark
+        // this node as dirty.
+        if (effectFilter == null && effect != null) {
+            effectFilter = createEffectFilter((Effect)effect);
+            visualsChanged();
+        } else if (effectFilter != null && effectFilter.getEffect() != effect) {
+            effectFilter.dispose();
+            effectFilter = null;
+            if (effect != null) {
+                effectFilter = createEffectFilter((Effect)effect);
+            }
+            visualsChanged();
+        }
+    }
+
+    /**
+     * Called by the FX scene graph when an effect in the effect chain on the node
+     * changes internally.
+     */
+    public void effectChanged() {
+        visualsChanged();
+    }
+
+    /**
+     * Return true if contentBounds is purely a 2D bounds, ie. it is a
+     * RectBounds or its Z dimension is almost zero.
+     */
+    public boolean isContentBounds2D() {
+        return (contentBounds.is2D()
+                || (Affine3D.almostZero(contentBounds.getMaxZ())
+                && Affine3D.almostZero(contentBounds.getMinZ())));
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Hierarchy, visibility, and other such miscellaneous NGNode properties   *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * Gets the parent of this node. The parent might be an NGGroup. However,
+     * if this node is a clip node on some other node, then the node on which
+     * it is set as the clip will be returned. That is, suppose some node A
+     * has a clip node B. The method B.getParent() will return A.
+     */
+    public NGNode getParent() { return parent; }
+
+    /**
+     * Only called by this class, or by the NGGroup class.
+     */
+    public void setParent(NGNode parent) {
+        setParent(parent, false);
+    }
+
+    private void setParent(NGNode parent, boolean isClip) {
+        this.parent = parent;
+        this.isClip = isClip;
+    }
+
+    protected final Effect getEffect() { return effectFilter == null ? null : effectFilter.getEffect(); }
+
+    /**
+     * Gets whether this node's visible property is set
+     */
+    public boolean isVisible() { return visible; }
+
+    public final BaseTransform getTransform() { return transform; }
+    public final float getOpacity() { return opacity; }
+    public final Blend.Mode getNodeBlendMode() { return nodeBlendMode; }
+    public final boolean isDepthTest() { return depthTest; }
+    public final BaseCacheFilter getCacheFilter() { return cacheFilter; }
+    public final BaseEffectFilter getEffectFilter() { return effectFilter; }
+    public final NGNode getClipNode() { return clipNode; }
+
+    public BaseBounds getContentBounds(BaseBounds bounds, BaseTransform tx) {
+        if (tx.isTranslateOrIdentity()) {
+            bounds = bounds.deriveWithNewBounds(contentBounds);
+            if (!tx.isIdentity()) {
+                float translateX = (float) tx.getMxt();
+                float translateY = (float) tx.getMyt();
+                float translateZ = (float) tx.getMzt();
+                bounds = bounds.deriveWithNewBounds(
+                    bounds.getMinX() + translateX,
+                    bounds.getMinY() + translateY,
+                    bounds.getMinZ() + translateZ,
+                    bounds.getMaxX() + translateX,
+                    bounds.getMaxY() + translateY,
+                    bounds.getMaxZ() + translateZ);
+            }
+            return bounds;
+        } else {
+            // This is a scale / rotate / skew transform.
+            // We have contentBounds cached throughout the entire tree.
+            // just walk down the tree and add everything up
+            return computeBounds(bounds, tx);
+        }
+    }
+
+    private BaseBounds computeBounds(BaseBounds bounds, BaseTransform tx) {
+        // TODO: This code almost worked, but it ignored the local to
+        // parent transforms on the nodes.  The short fix is to disable
+        // this block and use the more general form below, but we need
+        // to revisit this and see if we can make it work more optimally.
+        // @see RT-12105 http://javafx-jira.kenai.com/browse/RT-12105
+        if (false && this instanceof NGGroup) {
+            List<NGNode> children = ((NGGroup)this).getChildren();
+            BaseBounds tmp = TEMP_BOUNDS;
+            for (int i=0; i<children.size(); i++) {
+                float minX = bounds.getMinX();
+                float minY = bounds.getMinY();
+                float minZ = bounds.getMinZ();
+                float maxX = bounds.getMaxX();
+                float maxY = bounds.getMaxY();
+                float maxZ = bounds.getMaxZ();
+                NGNode child = children.get(i);
+                bounds = child.computeBounds(bounds, tx);
+                tmp = tmp.deriveWithNewBounds(minX, minY, minZ, maxX, maxY, maxZ);
+                bounds = bounds.deriveWithUnion(tmp);
+            }
+            return bounds;
+        } else {
+            bounds = bounds.deriveWithNewBounds(contentBounds);
+            return tx.transform(contentBounds, bounds);
+        }
+    }
+
+    /**
+     */
+    public final BaseBounds getClippedBounds(BaseBounds bounds, BaseTransform tx) {
+        BaseBounds effectBounds = getEffectBounds(bounds, tx);
+        if (clipNode != null) {
+            // there is a clip in place, so we will save off the effect/content
+            // bounds (so as not to generate garbage) and will then get the
+            // bounds of the clip node and do an intersection of the two
+            float ex1 = effectBounds.getMinX();
+            float ey1 = effectBounds.getMinY();
+            float ez1 = effectBounds.getMinZ();
+            float ex2 = effectBounds.getMaxX();
+            float ey2 = effectBounds.getMaxY();
+            float ez2 = effectBounds.getMaxZ();
+            effectBounds = clipNode.getCompleteBounds(effectBounds, tx);
+            effectBounds.intersectWith(ex1, ey1, ez1, ex2, ey2, ez2);
+        }
+        return effectBounds;
+    }
+
+    public final BaseBounds getEffectBounds(BaseBounds bounds, BaseTransform tx) {
+        if (effectFilter != null) {
+            return effectFilter.getBounds(bounds, tx);
+        } else {
+            return getContentBounds(bounds, tx);
+        }
+    }
+
+    public final BaseBounds getCompleteBounds(BaseBounds bounds, BaseTransform tx) {
+        if (tx.isIdentity()) {
+            bounds = bounds.deriveWithNewBounds(transformedBounds);
+            return bounds;
+        } else if (transform.isIdentity()) {
+            return getClippedBounds(bounds, tx);
+        } else {
+            double mxx = tx.getMxx();
+            double mxy = tx.getMxy();
+            double mxz = tx.getMxz();
+            double mxt = tx.getMxt();
+            double myx = tx.getMyx();
+            double myy = tx.getMyy();
+            double myz = tx.getMyz();
+            double myt = tx.getMyt();
+            double mzx = tx.getMzx();
+            double mzy = tx.getMzy();
+            double mzz = tx.getMzz();
+            double mzt = tx.getMzt();
+            BaseTransform boundsTx = tx.deriveWithConcatenation(this.transform);
+            bounds = getClippedBounds(bounds, tx);
+            if (boundsTx == tx) {
+                tx.restoreTransform(mxx, mxy, mxz, mxt,
+                                    myx, myy, myz, myt,
+                                    mzx, mzy, mzz, mzt);
+            }
+            return bounds;
+        }
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Dirty States and Dirty Regions                                          *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * Invoked by subclasses whenever some change to the geometry or visuals
+     * has occurred. This will mark the node as dirty and invalidate the cache.
+     */
+    protected void visualsChanged() {
+        invalidateCache();
+        markDirty();
+    }
+
+    protected void geometryChanged() {
+        invalidateCache();
+        if (hasVisuals()) {
+            markDirty();
+        }
+    }
+
+    /**
+     * Makes this node dirty, meaning that it needs to be included in the
+     * next repaint to the back buffer, and its bounds should be included
+     * in the dirty region. This flag means that this node itself is dirty.
+     * In contrast, the childDirty flag indicates that a child of the node
+     * (maybe a distant child) is dirty. This method does not invalidate the
+     * cache of this node. However, it ends up walking up the tree marking
+     * all parents as having a dirty child and also invalidating their caches.
+     * This method has no effect if the node is already dirty.
+     */
+    public final void markDirty() {
+        if (dirty != DirtyFlag.DIRTY) {
+            dirty = DirtyFlag.DIRTY;
+            markTreeDirty();
+        }
+    }
+
+    /**
+     * Mark the node as DIRTY_BY_TRANSLATION. This will call special cache invalidation
+     */
+    public void markDirtyByTranslation() {
+        if (dirty == DirtyFlag.CLEAN) {
+            if (parent != null && parent.dirty == DirtyFlag.CLEAN && !parent.childDirty) {
+                dirty = DirtyFlag.DIRTY_BY_TRANSLATION;
+                parent.childDirty = true;
+                parent.dirtyChildrenAccumulated++;
+                parent.invalidateCacheByTranslation();
+                parent.markTreeDirty();
+            } else {
+                markDirty();
+            }
+        }
+    }
+
+    //Mark tree dirty, but make sure this node's
+    // dirtyChildrenAccumulated has not been incremented.
+    // Useful when a markTree is called on a node that's not
+    // the dirty source of change, e.g. group knows it has new child
+    // or one of it's child has been removed
+    protected final void markTreeDirtyNoIncrement() {
+        if (parent != null && (!parent.childDirty || dirty == DirtyFlag.DIRTY_BY_TRANSLATION)) {
+            markTreeDirty();
+        }
+    }
+
+    /**
+     * Notifies the parent (whether an NGGroup or just a NGNode) that
+     * a child has become dirty. This walk will continue all the way up
+     * to the root of the tree. If a node is encountered which is already
+     * dirty, or which already has childDirty set, then this loop will
+     * terminate (ie: there is no point going further so we might as well
+     * just bail). This method ends up invalidating the cache of each
+     * parent up the tree. Since it is possible for a node to already
+     * have its dirty bit set, but not have its cache invalidated, this
+     * method is careful to make sure the first parent it encounters
+     * which is already marked dirty still has its cache invalidated. If
+     * this turns out to be expensive due to high occurrence, we can add
+     * a quick "invalidated" flag to every node (at the cost of yet
+     * another bit).
+     */
+    protected final void markTreeDirty() {
+        NGNode p = parent;
+        boolean atClip = isClip;
+        boolean byTranslation = dirty == DirtyFlag.DIRTY_BY_TRANSLATION;
+        while (p != null && p.dirty != DirtyFlag.DIRTY && (!p.childDirty || atClip || byTranslation)) {
+            if (atClip) {
+                p.dirty = DirtyFlag.DIRTY;
+            } else if (!byTranslation) {
+                p.childDirty = true;
+                p.dirtyChildrenAccumulated++;
+            }
+            p.invalidateCache();
+            atClip = p.isClip;
+            byTranslation = p.dirty == DirtyFlag.DIRTY_BY_TRANSLATION;
+            p = p.parent;
+        }
+        // if we stopped on a parent that already has dirty children, increase it's
+        // dirty children count.
+        // Note that when incrementDirty is false, we dont increment in this case.
+        if (p != null && p.dirty == DirtyFlag.CLEAN && !atClip && !byTranslation) {
+            p.dirtyChildrenAccumulated++;
+        }
+        // Must make sure this happens. In some cases, a parent might
+        // already be marked dirty (for example, its opacity may have
+        // changed) but its cache has not been made invalid. This call
+        // will make sure it is invalidated in that case
+        if (p != null) p.invalidateCache();
+    }
+
+    /**
+     * Gets whether this SGNode is clean. This will return true only if
+     * this node and any / all child nodes are clean.
+     */
+    public final boolean isClean() {
+        return dirty == DirtyFlag.CLEAN && !childDirty;
+    }
+
+    /**
+     * Clears the dirty flag. This should only happen during rendering.
+     */
+    protected void clearDirty() {
+        dirty = DirtyFlag.CLEAN;
+        childDirty = false;
+        dirtyBounds.makeEmpty();
+        dirtyChildrenAccumulated = 0;
+    }
+
+    public void clearDirtyTree() {
+        clearDirty();
+        if (getClipNode() != null) {
+            getClipNode().clearDirtyTree();
+        }
+        if (this instanceof NGGroup) {
+            List<NGNode> children = ((NGGroup) this).getChildren();
+            for (int i = 0; i < children.size(); ++i) {
+                NGNode child = children.get(i);
+                if (child.dirty != DirtyFlag.CLEAN || child.childDirty) {
+                    child.clearDirtyTree();
+                }
+            }
+        }
+    }
+
+    /**
+     * Invalidates the cache, if it is in use. There are several operations
+     * which need to cause the cached raster to become invalid so that a
+     * subsequent render operation will result in the cached image being
+     * reconstructed.
+     */
+    protected final void invalidateCache() {
+        if (cacheFilter != null) {
+            cacheFilter.invalidate();
+        }
+    }
+
+    /**
+     * Mark the cache as invalid due to a translation of a child. The cache filter
+     * might use this information for optimizations.
+     */
+    protected final void invalidateCacheByTranslation() {
+        if (cacheFilter != null) {
+            cacheFilter.invalidateByTranslation(hint.translateXDelta, hint.translateYDelta);
+        }
+    }
+
+    /**
+     * Accumulates and returns the dirty regions in transformed coordinates for
+     * this node. This method is designed such that a single downward traversal
+     * of the tree is sufficient to update the dirty regions.
+     * <p>
+     * This method only accumulates dirty regions for parts of the tree which lie
+     * inside the clip since there is no point in accumulating dirty regions which
+     * lie outside the clip. The returned dirty regions bounds  the same object
+     * as that passed into the function. The returned dirty regions bounds will
+     * always be adjusted such that they do not extend beyond the clip.
+     * <p>
+     * The given transform is the accumulated transform up to but not including the
+     * transform of this node.
+     *
+     * @param clip must not be null, the clip in scene coordinates, supplied by the
+     *        rendering system. At most, this is usually the bounds of the window's
+     *        content area, however it might be smaller.
+     * @param dirtyRegionTemp must not be null, the dirty region in scene coordinates.
+     *        When this method is initially invoked by the rendering system, the
+     *        dirtyRegion should be marked as invalid.
+     * @param dirtyRegionContainer must not be null, the container of dirty regions in scene
+     *        coordinates.
+     * @param tx must not be null, the accumulated transform up to but not
+     *        including this node's transform. When this method concludes, it must
+     *        restore this transform if it was changed within the function.
+     * @param pvTx must not be null, it's the perspective transform of the current
+     *        perspective camera or identity transform if parallel camera is used.
+     * @return The dirty region container. If the returned value is null, then that means
+     *         the clip should be used as the dirty region. This is a special
+     *         case indicating that there is no more need to walk the tree but
+     *         we can take a shortcut. Note that returning null is *always*
+     *         safe. Returning something other than null is simply an
+     *         optimization for cases where the dirty region is substantially
+     *         smaller than the clip.
+     * TODO: Only made non-final for the sake of testing (see javafx-sg-prism tests) (RT-23957)
+     */
+
+    public /*final*/ int accumulateDirtyRegions(final RectBounds clip,
+                                                final RectBounds dirtyRegionTemp,
+                                                DirtyRegionPool regionPool,
+                                                final DirtyRegionContainer dirtyRegionContainer,
+                                                final BaseTransform tx,
+                                                final GeneralTransform3D pvTx)
+    {
+        // Even though a node with 0 visibility or 0 opacity doesn't get
+        // rendered, it may contribute to the dirty bounds, for example, if it
+        // WAS visible or if it HAD an opacity > 0 last time we rendered then
+        // we must honor its dirty region. We have front-loaded this work so
+        // that we don't mark nodes as having dirty flags or dirtyBounds if
+        // they shouldn't contribute to the dirty region. So we can simply
+        // treat all nodes, regardless of their opacity or visibility, as
+        // though their dirty regions matter. They do.
+
+        // If this node is clean then we can simply return the dirty region as
+        // there is no need to walk any further down this branch of the tree.
+        // The node is "clean" if neither it, nor its children, are dirty.
+         if (dirty == DirtyFlag.CLEAN && !childDirty) {
+             return DirtyRegionContainer.DTR_OK;
+         }
+
+        // We simply collect this nodes dirty region if it has its dirty flag
+        // set, regardless of whether it is a group or not. However, if this
+        // node is not dirty, then we can ask the accumulateGroupDirtyRegion
+        // method to collect the dirty regions of the children.
+        if (dirty != DirtyFlag.CLEAN) {
+            return accumulateNodeDirtyRegion(clip, dirtyRegionTemp, dirtyRegionContainer, tx, pvTx);
+        } else {
+            assert childDirty; // this must be true by this point
+            return accumulateGroupDirtyRegion(clip, dirtyRegionTemp, regionPool,
+                                              dirtyRegionContainer, tx, pvTx);
+        }
+    }
+
+    /**
+     * Accumulates the dirty region of a node.
+     * TODO: Only made protected for the sake of testing (see javafx-sg-prism tests) (RT-23957)
+     */
+    protected int accumulateNodeDirtyRegion(final RectBounds clip,
+                                            final RectBounds dirtyRegionTemp,
+                                            final DirtyRegionContainer dirtyRegionContainer,
+                                            final BaseTransform tx,
+                                            final GeneralTransform3D pvTx) {
+
+        // Get the dirty bounds of this specific node in scene coordinates
+        BaseBounds bb = computeDirtyRegion(dirtyRegionTemp, tx, pvTx);
+
+        // Note: dirtyRegion is strictly a 2D operation. We simply need the largest
+        // rectangular bounds of bb. Hence the Z-axis projection of bb; taking
+        // minX, minY, maxX and maxY values from this point on.
+        dirtyRegionTemp.setMinX(bb.getMinX());
+        dirtyRegionTemp.setMinY(bb.getMinY());
+        dirtyRegionTemp.setMaxX(bb.getMaxX());
+        dirtyRegionTemp.setMaxY(bb.getMaxY());
+
+        // If my dirty region is empty, or if it doesn't intersect with the
+        // clip, then we can simply return the passed in dirty region since
+        // this node's dirty region is not helpful
+        if (dirtyRegionTemp.isEmpty() || clip.disjoint(dirtyRegionTemp)) {
+            return DirtyRegionContainer.DTR_OK;
+        }
+
+        if (dirtyRegionTemp.getMinX() <= clip.getMinX() &&
+            dirtyRegionTemp.getMinY() <= clip.getMinY() &&
+            dirtyRegionTemp.getMaxX() >= clip.getMaxX() &&
+            dirtyRegionTemp.getMaxY() >= clip.getMaxY()) {
+            return DirtyRegionContainer.DTR_CONTAINS_CLIP;
+        }
+
+        dirtyRegionTemp.setMinX(Math.max(dirtyRegionTemp.getMinX(), clip.getMinX()));
+        dirtyRegionTemp.setMinY(Math.max(dirtyRegionTemp.getMinY(), clip.getMinY()));
+        dirtyRegionTemp.setMaxX(Math.min(dirtyRegionTemp.getMaxX(), clip.getMaxX()));
+        dirtyRegionTemp.setMaxY(Math.min(dirtyRegionTemp.getMaxY(), clip.getMaxY()));
+
+        dirtyRegionContainer.addDirtyRegion(dirtyRegionTemp);
+
+        return DirtyRegionContainer.DTR_OK;
+    }
+
+    /**
+     * Accumulates the dirty region of an NGGroup. This is implemented here as opposed to
+     * using polymorphism because we wanted to centralize all of the dirty region
+     * management code in one place, rather than having it spread between Prism,
+     * Scenario, and any other future toolkits.
+     * TODO: Only made protected for the sake of testing (see javafx-sg-prism tests) (RT-23957)
+     */
+    protected int accumulateGroupDirtyRegion(final RectBounds clip,
+                                             final RectBounds dirtyRegionTemp,
+                                             DirtyRegionPool regionPool,
+                                             DirtyRegionContainer dirtyRegionContainer,
+                                             final BaseTransform tx,
+                                             final GeneralTransform3D pvTx) {
+        // We should have only made it to this point if this node has a dirty
+        // child. If this node itself is dirty, this method never would get called.
+        // If this node was not dirty and had no dirty children, then this
+        // method never should have been called. So at this point, the following
+        // assertions should be correct.
+        assert childDirty;
+        assert dirty == DirtyFlag.CLEAN;
+
+        int status = DirtyRegionContainer.DTR_OK;
+
+        if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) {
+            status = accumulateNodeDirtyRegion(clip, dirtyRegionTemp, dirtyRegionContainer, tx, pvTx);
+            return status;
+        }
+
+        // If we got here, then we are following a "bread crumb" trail down to
+        // some child (perhaps distant) which is dirty. So we need to iterate
+        // over all the children and accumulate their dirty regions. Before doing
+        // so we, will save off the transform state and restore it after having
+        // called all the children.
+        double mxx = tx.getMxx();
+        double mxy = tx.getMxy();
+        double mxz = tx.getMxz();
+        double mxt = tx.getMxt();
+
+        double myx = tx.getMyx();
+        double myy = tx.getMyy();
+        double myz = tx.getMyz();
+        double myt = tx.getMyt();
+
+        double mzx = tx.getMzx();
+        double mzy = tx.getMzy();
+        double mzz = tx.getMzz();
+        double mzt = tx.getMzt();
+        BaseTransform renderTx = tx;
+        if (this.transform != null) renderTx = renderTx.deriveWithConcatenation(this.transform);
+
+        // If this group node has a clip, then we will perform some special
+        // logic which will cause the dirty region accumulation loops to run
+        // faster. We already have a system whereby if a node determines that
+        // its dirty region exceeds that of the clip, it simply returns null,
+        // short circuiting the accumulation process. We extend that logic
+        // here by also taking into account the clipNode on the group. If
+        // there is a clip node, then we will union the bounds of the clip
+        // node (in boundsInScene space) with the current clip and pass this
+        // new clip down to the children. If they determine that their dirty
+        // regions exceed the bounds of this new clip, then they will return
+        // null. We'll catch that here, and use that information to know that
+        // we ought to simply accumulate the bounds of this group as if it
+        // were dirty. This process will do all the other optimizations we
+        // already have in place for getting the normal dirty region.
+        RectBounds myClip = clip;
+        //Save current dirty region so we can fast-reset to (something like) the last state
+        //and possibly save a few intersects() calls
+
+        DirtyRegionContainer originalDirtyRegion = null;
+        BaseTransform originalRenderTx = null;
+        if (effectFilter != null) {
+            try {
+                myClip = new RectBounds();
+                BaseBounds myClipBaseBounds = renderTx.inverseTransform(clip, TEMP_BOUNDS);
+                myClip.setBounds(myClipBaseBounds.getMinX(),
+                                 myClipBaseBounds.getMinY(),
+                                 myClipBaseBounds.getMaxX(),
+                                 myClipBaseBounds.getMaxY());
+            } catch (NoninvertibleTransformException ex) {
+                return DirtyRegionContainer.DTR_OK;
+            }
+
+            originalRenderTx = renderTx;
+            renderTx = BaseTransform.IDENTITY_TRANSFORM;
+            originalDirtyRegion = dirtyRegionContainer;
+            dirtyRegionContainer = regionPool.checkOut();
+        } else if (clipNode != null) {
+            originalDirtyRegion = dirtyRegionContainer;
+            myClip = new RectBounds();
+            BaseBounds clipBounds = clipNode.getCompleteBounds(myClip, renderTx);
+            pvTx.transform(clipBounds, clipBounds);
+            myClip.deriveWithNewBounds(clipBounds.getMinX(), clipBounds.getMinY(), 0,
+                                         clipBounds.getMaxX(), clipBounds.getMaxY(), 0);
+            myClip.intersectWith(clip);
+            dirtyRegionContainer = regionPool.checkOut();
+        }
+
+
+        //Accumulate also removed children to dirty region.
+        List<NGNode> removed = ((NGGroup) this).getRemovedChildren();
+        if (removed != null) {
+            NGNode removedChild;
+            for (int i = removed.size() - 1; i >= 0; --i) {
+                removedChild = removed.get(i);
+                removedChild.dirty = DirtyFlag.DIRTY;
+                    status = removedChild.accumulateDirtyRegions(myClip,
+                            dirtyRegionTemp,regionPool, dirtyRegionContainer, renderTx, pvTx);
+                    if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
+                        break;
+                    }
+            }
+        }
+
+        List<NGNode> children = ((NGGroup) this).getChildren();
+        int num = children.size();
+            for (int i=0; i<num && status == DirtyRegionContainer.DTR_OK; i++) {
+            NGNode child = children.get(i);
+            // The child will check the dirty bits itself. If we tested it here
+            // (as we used to), we are just doing the check twice. True, it might
+            // mean fewer method calls, but hotspot will probably inline this all
+            // anyway, and doing the check in one place is less error prone.
+                status = child.accumulateDirtyRegions(myClip, dirtyRegionTemp, regionPool,
+                                                      dirtyRegionContainer, renderTx, pvTx);
+                if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
+                break;
+            }
+        }
+
+        if (effectFilter != null && status == DirtyRegionContainer.DTR_OK) {
+            //apply effect on effect dirty regions
+            applyEffect(effectFilter, dirtyRegionContainer, regionPool);
+
+            if (clipNode != null) {
+                myClip = new RectBounds();
+                BaseBounds clipBounds = clipNode.getCompleteBounds(myClip, renderTx);
+                applyClip(clipBounds, dirtyRegionContainer);
+            }
+
+            //apply transform on effect dirty regions
+            applyTransform(originalRenderTx, dirtyRegionContainer);
+            renderTx = originalRenderTx;
+
+            originalDirtyRegion.merge(dirtyRegionContainer);
+            regionPool.checkIn(dirtyRegionContainer);
+        }
+
+        // If the process of applying the transform caused renderTx to not equal
+        // tx, then there is no point restoring it since it will be a different
+        // reference and will therefore be gc'd.
+        if (renderTx == tx) {
+            tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt);
+        }
+
+        // If the dirty region is null and there is a clip node specified, then what
+        // happened is that the dirty region of content within this group exceeded
+        // the clip of this group, and thus, we should accumulate the bounds of
+        // this group into the dirty region. If the bounds of the group exceeds
+        // the bounds of the dirty region, then we end up returning null in the
+        // end. But the implementation of accumulateNodeDirtyRegion handles this.
+        if (clipNode != null && effectFilter == null) {
+            if (status == DirtyRegionContainer.DTR_CONTAINS_CLIP) {
+                status = accumulateNodeDirtyRegion(clip, dirtyRegionTemp, originalDirtyRegion, tx, pvTx);
+            } else {
+                originalDirtyRegion.merge(dirtyRegionContainer);
+            }
+            regionPool.checkIn(dirtyRegionContainer);
+        }
+        return status;
+    }
+
+    /**
+     * Computes the dirty region for this Node. The specified region is in
+     * scene coordinates. The specified tx can be used to convert local bounds
+     * to scene bounds (it includes everything up to but not including my own
+     * transform).
+     * @param pvTx must not be null, it's the perspective transform of the current
+     *        perspective camera or identity transform if parallel camera is used.
+     */
+    private BaseBounds computeDirtyRegion(BaseBounds region,
+                                          BaseTransform tx,
+                                          GeneralTransform3D pvTx)
+    {
+        // The passed in region is a scratch object that exists for me to use,
+        // such that I don't have to create a temporary object. So I just
+        // hijack it right here giving it the dirtyBounds.
+        if (!dirtyBounds.isEmpty()) {
+            region = region.deriveWithNewBounds(dirtyBounds);
+        } else {
+            // If dirtyBounds is empty, then we will simply set the bounds to
+            // be the same as the transformedBounds (since that means the bounds
+            // haven't changed and right now we don't support dirty sub regions
+            // for generic nodes). This can happen if, for example, this is
+            // a group with a clip and the dirty area of child nodes within
+            // the group exceeds the bounds of the clip on the group. Just trust me.
+            region = region.deriveWithNewBounds(transformedBounds);
+        }
+
+        // We shouldn't do anything with empty region, as we may accidentally make
+        // it non empty or turn it into some nonsense (like (-1,-1,0,0) )
+        if (!region.isEmpty()) {
+            // Now that we have the dirty region, we will simply apply the tx
+            // to it (after slightly padding it for good luck) to get the scene
+            // coordinates for this.
+            region = computePadding(region);
+            region = tx.transform(region, region);
+            region = pvTx.transform(region, region);
+        }
+        return region;
+    }
+
+    /**
+     * LCD Text creates some painful situations where, due to the LCD text
+     * algorithm, we end up with some pixels touched that are normally outside
+     * the bounds. To compensate, we need a hook for NGText to add padding.
+     */
+    protected BaseBounds computePadding(BaseBounds region) {
+        return region;
+    }
+
+    /**
+     * Marks if the node has some visuals and that the bounds change
+     * should be taken into account when using the dirty region.
+     * This will be false for NGGroup (but not for NGRegion)
+     * @return true if the node has some visuals
+     */
+    protected boolean hasVisuals() {
+        return true;
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Culling                                                                 *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * Culling support for multiple dirty regions.
+     * Set culling bits for the whole graph.
+     * @param drc Array of dirty regions.
+     * @param tx The transform for this render operation.
+     * @param pvTx Perspective camera transformation.
+     */
+    public void doPreCulling(DirtyRegionContainer drc, BaseTransform tx, GeneralTransform3D pvTx) {
+        markCullRegions(drc, -1, tx, pvTx);
+    }
+
+    /**
+     * Set culling bits for the node.
+     * @param bounds Bounds of the node.
+     * @param regionIndex Index of dirty region used.
+     * @param region Dirty region being processed.
+     * @return Bit setting encoding node position to dirty region (without the shift)
+     */
+    protected int setCullBits(BaseBounds bounds, int regionIndex, RectBounds region) {
+        int b = 0;
+        if (region != null && !region.isEmpty()) {
+            if (region.intersects(bounds)) {
+                b = 1;
+                if (region.contains(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())) {
+                    b = 2;
+                }
+                cullingBits = cullingBits |  (b << (2 * regionIndex));
+            }
+        }
+        return b;
+    }
+
+    /**
+     * Marks placement of the node in dirty region encoded into 2 bit flag:
+     * 00 - node outside dirty region
+     * 01 - node intersecting dirty region
+     * 11 - node completely within dirty region
+     *
+     * 32 bits = 15 regions max. * 2 bit each.
+     *
+     * @param drc The array of dirty regions.
+     * @param cullingRegionsBitsOfParent culling bits of parent. -1 if there's no parent.
+     * @param tx The transform for this render operation.
+     * @param pvTx Perspective camera transform.
+     */
     protected void markCullRegions(
             DirtyRegionContainer drc,
             int cullingRegionsBitsOfParent,
@@ -150,7 +1302,10 @@
         }
     }
 
-
+    /**
+     * Helper method draws culling bits for each node.
+     * @param g Graphics.
+     */
     public void drawCullBits(Graphics g) {
         if (cullingBits == 0){
             g.setPaint(new Color(0, 0, 0, .3f));
@@ -335,16 +1490,45 @@
 
     /***************************************************************************
      *                                                                         *
-     *                     Rendering Related Methods.                          *
+     * Rendering                                                               *
      *                                                                         *
      **************************************************************************/
 
+    /**
+     * Render the tree of nodes to the specified G (graphics) object
+     * descending from this node as the root. This method is designed to avoid
+     * generated trash as much as possible while descending through the
+     * render graph while rendering. This is the appropriate method both to
+     * initiate painting of an entire scene, and for a branch. The NGGroup
+     * implementation must call this method on each child, not doRender directly.
+     *
+     * @param g The graphics object we're rendering to. This must never be null.
+     */
+    public final void render(Graphics g) {
+        if (PULSE_LOGGING_ENABLED) PULSE_LOGGER.renderIncrementCounter("Nodes visited during render");
+        if (debug) System.out.println("render called on " + getClass().getSimpleName());
+        // Clear the visuals changed flag
+        clearDirty();
+        // If it isn't visible, then punt
+        if (!visible || opacity == 0f) return;
+        // If we are supposed to cull, then go ahead and do so
+//        if (cull(clip, tx)) return;
+
+        // We know that we are going to render this node, so we call the
+        // doRender method, which subclasses implement to do the actual
+        // rendering work.
+        doRender(g);
+    }
+
     // This node requires 2D graphics state for rendering
     boolean isShape3D() {
         return false;
     }
 
-    @Override
+    /**
+     * Invoked only by the final render method. Implementations
+     * of this method should make sure to save & restore the transform state.
+     */
     protected void doRender(Graphics g) {
 
         g.setState3D(isShape3D());
@@ -566,7 +1750,7 @@
             Rectangle savedClip = g.getClipRect();
             g.setClipRect(clipRect);
             NodeEffectInput clipInput =
-                new NodeEffectInput((NGNode) getClipNode(),
+                new NodeEffectInput(getClipNode(),
                                     NodeEffectInput.RenderType.FULL_CONTENT);
             NodeEffectInput nodeInput =
                 new NodeEffectInput(this,
@@ -724,11 +1908,11 @@
      *                                                                         *
      **************************************************************************/
 
-    @Override protected BaseCacheFilter createCacheFilter(CacheHint cacheHint) {
+    BaseCacheFilter createCacheFilter(CacheHint cacheHint) {
         return new CacheFilter(this, cacheHint);
     }
 
-    @Override protected BaseEffectFilter createEffectFilter(Effect effect) {
+    BaseEffectFilter createEffectFilter(Effect effect) {
         return new EffectFilter(effect, this);
     }
 
@@ -743,7 +1927,7 @@
         private static final Affine3D TEMP_CACHEFILTER_TRANSFORM = new Affine3D();
         private RTTexture tempTexture;
 
-        CacheFilter(BaseNode node, CacheHint cacheHint) {
+        CacheFilter(NGNode node, CacheHint cacheHint) {
             super(node, cacheHint);
         }
 
@@ -805,11 +1989,11 @@
                 }
                 g.transform(TEMP_CACHEFILTER_TRANSFORM);
                 if (node.getClipNode() != null) {
-                    ((NGNode)node).renderClip(g);
+                    node.renderClip(g);
                 } else if (node.getEffectFilter() != null) {
-                    ((NGNode)node).renderEffect(g);
+                    node.renderEffect(g);
                 } else {
-                    ((NGNode)node).renderContent(g);
+                    node.renderContent(g);
                 }
             }
         }
@@ -824,12 +2008,10 @@
                                                BaseTransform xform/* ignored */)
         {
             Graphics g = (Graphics)implGraphics;
-            NGNode implNode = (NGNode)node;
-
-            if (implNode.getEffectFilter() != null) {
-                implNode.renderEffect(g);
+            if (node.getEffectFilter() != null) {
+                node.renderEffect(g);
             } else {
-                implNode.renderContent(g);
+                node.renderContent(g);
             }
         }
 
@@ -873,7 +2055,7 @@
                 return false;
             }
             
-            NGNode clip = (NGNode) node.getClipNode();
+            NGNode clip = node.getClipNode();
             return clip != null && clip instanceof NGRectangle && 
                     ((NGRectangle)clip).isRectClip(BaseTransform.IDENTITY_TRANSFORM, false);
         }
@@ -954,8 +2136,8 @@
             nodeInput.flush();
         }
 
-        @Override protected BaseNodeEffectInput createNodeEffectInput(BaseNode node) {
-            return new NodeEffectInput((NGNode)node);
+        @Override protected BaseNodeEffectInput createNodeEffectInput(NGNode node) {
+            return new NodeEffectInput(node);
         }
     }
 
@@ -1007,4 +2189,100 @@
             return null; //Never called
         }
     }
+
+    /***************************************************************************
+     *                                                                         *
+     * Stuff                                                                   *
+     *                                                                         *
+     **************************************************************************/
+
+    public void release() {
+    }
+
+    public void applyTransform(final BaseTransform tx, DirtyRegionContainer drc) {
+        for (int i = 0; i < drc.size(); i++) {
+            drc.setDirtyRegion(i, (RectBounds) tx.transform(drc.getDirtyRegion(i), drc.getDirtyRegion(i)));
+            if (drc.checkAndClearRegion(i)) {
+                --i;
+            }
+        }
+    }
+
+    public void applyClip(final BaseBounds clipBounds, DirtyRegionContainer drc) {
+        for (int i = 0; i < drc.size(); i++) {
+            drc.getDirtyRegion(i).intersectWith(clipBounds);
+            if (drc.checkAndClearRegion(i)) {
+                --i;
+            }
+        }
+    }
+
+    public void applyEffect(final BaseEffectFilter effectFilter, DirtyRegionContainer drc, DirtyRegionPool regionPool) {
+        Effect effect = effectFilter.getEffect();
+        EffectDirtyBoundsHelper helper = EffectDirtyBoundsHelper.getInstance();
+        helper.setInputBounds(contentBounds);
+        helper.setDirtyRegions(drc);
+        final DirtyRegionContainer effectDrc = effect.getDirtyRegions(helper, regionPool);
+        drc.deriveWithNewContainer(effectDrc);
+        regionPool.checkIn(effectDrc);
+    }
+
+    private static class EffectDirtyBoundsHelper extends Effect {
+        private BaseBounds bounds;
+        private static EffectDirtyBoundsHelper instance = null;
+        private DirtyRegionContainer drc;
+
+        public void setInputBounds(BaseBounds inputBounds) {
+            bounds = inputBounds;
+        }
+
+        @Override
+        public ImageData filter(FilterContext fctx,
+                BaseTransform transform,
+                Rectangle outputClip,
+                Object renderHelper,
+                Effect defaultInput) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
+            if (bounds.getBoundsType() == BaseBounds.BoundsType.RECTANGLE) {
+                return bounds;
+            } else {
+                //RT-29453 - CCE: in case we get 3D bounds we need to "flatten" them
+                return new RectBounds(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY());
+            }
+        }
+
+        @Override
+        public Effect.AccelType getAccelType(FilterContext fctx) {
+            return null;
+        }
+
+        public static EffectDirtyBoundsHelper getInstance() {
+            if (instance == null) {
+                instance = new EffectDirtyBoundsHelper();
+            }
+            return instance;
+        }
+
+        @Override
+        public boolean reducesOpaquePixels() {
+            return true;
+        }
+
+        private void setDirtyRegions(DirtyRegionContainer drc) {
+            this.drc = drc;
+        }
+
+        @Override
+        public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
+            DirtyRegionContainer ret = regionPool.checkOut();
+            ret.deriveWithNewContainer(drc);
+
+            return ret;
+        }
+
+    }
 }
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPath.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPath.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,12 +25,12 @@
 
 package com.sun.javafx.sg.prism;
 
+import javafx.scene.shape.FillRule;
 import com.sun.javafx.geom.Arc2D;
 import com.sun.javafx.geom.Path2D;
 import com.sun.javafx.geom.PathIterator;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.geom.transform.BaseTransform;
-import javafx.scene.shape.FillRule;
 
 /**
  */
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPhongMaterial.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPhongMaterial.java	Fri Jul 12 08:35:56 2013 -0700
@@ -29,7 +29,6 @@
 import com.sun.prism.Material;
 import com.sun.prism.PhongMaterial;
 import com.sun.prism.ResourceFactory;
-import com.sun.prism.Texture;
 import com.sun.prism.TextureMap;
 import com.sun.prism.paint.Color;
 
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGRegion.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGRegion.java	Fri Jul 12 08:35:56 2013 -0700
@@ -56,9 +56,6 @@
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.transform.GeneralTransform3D;
 import com.sun.javafx.logging.PulseLogger;
-import com.sun.javafx.sg.BaseEffectFilter;
-import com.sun.javafx.sg.BaseNode;
-import com.sun.javafx.sg.NodePath;
 import com.sun.javafx.tk.Toolkit;
 import com.sun.prism.BasicStroke;
 import com.sun.prism.Graphics;
@@ -387,7 +384,7 @@
      * @return
      */
     @Override protected RectBounds computeOpaqueRegion(RectBounds opaqueRegion) {
-        final BaseNode clip = getClipNode();
+        final NGNode clip = getClipNode();
         final Effect effect = getEffect();
         // compute opaque region
         if ((effect == null || !effect.reducesOpaquePixels()) &&
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGShape.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGShape.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,6 +25,9 @@
 
 package com.sun.javafx.sg.prism;
 
+import javafx.scene.shape.StrokeLineCap;
+import javafx.scene.shape.StrokeLineJoin;
+import javafx.scene.shape.StrokeType;
 import com.sun.javafx.geom.BaseBounds;
 import com.sun.javafx.geom.PathIterator;
 import com.sun.javafx.geom.RectBounds;
@@ -36,10 +39,6 @@
 import com.sun.prism.PrinterGraphics;
 import com.sun.prism.paint.Paint;
 import com.sun.prism.shape.ShapeRep;
-import javafx.scene.shape.StrokeLineCap;
-import javafx.scene.shape.StrokeLineJoin;
-import javafx.scene.shape.StrokeType;
-
 import static com.sun.prism.shape.ShapeRep.InvalidationType.LOCATION;
 import static com.sun.prism.shape.ShapeRep.InvalidationType.LOCATION_AND_GEOMETRY;
 
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGShape3D.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGShape3D.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,16 +25,16 @@
 
 package com.sun.javafx.sg.prism;
 
+import javafx.application.ConditionalFeature;
+import javafx.application.Platform;
+import javafx.scene.shape.CullFace;
+import javafx.scene.shape.DrawMode;
 import com.sun.javafx.geom.Vec3d;
 import com.sun.javafx.geom.transform.Affine3D;
 import com.sun.prism.Graphics;
 import com.sun.prism.Material;
 import com.sun.prism.MeshView;
 import com.sun.prism.ResourceFactory;
-import javafx.application.ConditionalFeature;
-import javafx.application.Platform;
-import javafx.scene.shape.CullFace;
-import javafx.scene.shape.DrawMode;
 
 /**
  * TODO: 3D - Need documentation
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NodeEffectInput.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NodeEffectInput.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,9 +25,6 @@
 
 package com.sun.javafx.sg.prism;
 
-import com.sun.scenario.effect.Effect;
-import com.sun.scenario.effect.FilterContext;
-import com.sun.scenario.effect.ImageData;
 import com.sun.javafx.geom.BaseBounds;
 import com.sun.javafx.geom.DirtyRegionContainer;
 import com.sun.javafx.geom.DirtyRegionPool;
@@ -35,9 +32,11 @@
 import com.sun.javafx.geom.Rectangle;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.prism.Graphics;
+import com.sun.scenario.effect.Effect;
+import com.sun.scenario.effect.FilterContext;
+import com.sun.scenario.effect.ImageData;
 import com.sun.scenario.effect.impl.prism.PrDrawable;
 import com.sun.scenario.effect.impl.prism.PrRenderInfo;
-import com.sun.javafx.sg.BaseNodeEffectInput;
 
 /**
  */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NodePath.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple a reusable storage for root-to-node path.
+ * 
+ */
+public class NodePath<N extends NGNode> {
+    private List<N> path = new ArrayList<>();
+    private int position;
+    
+    public NodePath() {
+    }
+    
+    // ITERATION methods
+    
+    public N getCurrentNode() {
+        return path.get(position);
+    }
+
+    public boolean hasNext() {
+        return position > 0;
+    }
+
+    public void next() {
+        if (!hasNext()) {
+            throw new IllegalStateException();
+        }
+        position--;
+    }
+    
+    public void reset() {
+        position = path.size() - 1;
+    }
+    
+    // MODIFICATION methods
+    
+    public void clear() {
+        position = -1;
+        path.clear();
+    }
+    
+    public void add(N n) {
+        path.add(n);
+        position = path.size() - 1;
+    }
+    
+    public int size() {
+        return path.size();
+    }
+    
+    /*
+     * Remove root and set to beginning.
+     */
+    public void removeRoot() {
+        path.remove(path.size() - 1);
+        position = path.size() - 1;
+    }
+
+}
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/ShapeEvaluator.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/ShapeEvaluator.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,16 +25,16 @@
 
 package com.sun.javafx.sg.prism;
 
+import java.util.Vector;
 import com.sun.javafx.geom.FlatteningPathIterator;
+import com.sun.javafx.geom.IllegalPathStateException;
 import com.sun.javafx.geom.Path2D;
-import com.sun.javafx.geom.IllegalPathStateException;
 import com.sun.javafx.geom.PathIterator;
 import com.sun.javafx.geom.Point2D;
+import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.Rectangle;
-import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.Shape;
 import com.sun.javafx.geom.transform.BaseTransform;
-import java.util.Vector;
 
 /**
  * A {@link KeyFrame} {@link Evaluator} for {@link Shape} objects.
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/AbstractPainter.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/AbstractPainter.java	Fri Jul 12 08:35:56 2013 -0700
@@ -36,7 +36,7 @@
 import com.sun.javafx.geom.transform.GeneralTransform3D;
 import com.sun.javafx.jmx.HighlightRegion;
 import com.sun.javafx.runtime.SystemProperties;
-import com.sun.javafx.sg.NodePath;
+import com.sun.javafx.sg.prism.NodePath;
 import com.sun.javafx.sg.prism.NGNode;
 import com.sun.javafx.tk.Toolkit;
 import com.sun.prism.BasicStroke;
--- a/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/com/sun/javafx/tk/quantum/ViewPainter.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,7 +25,7 @@
 
 package com.sun.javafx.tk.quantum;
 
-import com.sun.javafx.sg.NodePath;
+import com.sun.javafx.sg.prism.NodePath;
 import com.sun.javafx.sg.prism.NGNode;
 import com.sun.prism.Graphics;
 import com.sun.prism.impl.PrismSettings;
--- a/modules/graphics/src/main/java/javafx/scene/canvas/Canvas.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/javafx/scene/canvas/Canvas.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,19 +25,19 @@
 
 package javafx.scene.canvas;
 
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.DoublePropertyBase;
+import javafx.geometry.NodeOrientation;
+import javafx.scene.Node;
 import com.sun.javafx.geom.BaseBounds;
 import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.jmx.MXNodeAlgorithm;
 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
 import com.sun.javafx.scene.DirtyBits;
-import com.sun.javafx.sg.GrowableDataBuffer;
+import com.sun.javafx.sg.prism.GrowableDataBuffer;
 import com.sun.javafx.sg.prism.NGCanvas;
 import com.sun.javafx.sg.prism.NGNode;
-import javafx.beans.property.DoubleProperty;
-import javafx.beans.property.DoublePropertyBase;
-import javafx.geometry.NodeOrientation;
-import javafx.scene.Node;
 
 /**
  * {@code Canvas} is an image that can be drawn on using a set of graphics 
--- a/modules/graphics/src/main/java/javafx/scene/canvas/GraphicsContext.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/main/java/javafx/scene/canvas/GraphicsContext.java	Fri Jul 12 08:35:56 2013 -0700
@@ -32,7 +32,7 @@
 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
 import com.sun.javafx.image.*;
 import com.sun.javafx.image.impl.ByteBgraPre;
-import com.sun.javafx.sg.GrowableDataBuffer;
+import com.sun.javafx.sg.prism.GrowableDataBuffer;
 import com.sun.javafx.sg.prism.NGCanvas;
 import com.sun.javafx.tk.Toolkit;
 import javafx.geometry.VPos;
--- a/modules/graphics/src/test/java/com/sun/javafx/sg/ContentBoundsTest.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,318 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import com.sun.javafx.geom.BaseBounds;
-import com.sun.javafx.geom.RectBounds;
-import com.sun.javafx.geom.transform.Affine2D;
-import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.javafx.sg.prism.NGNode;
-import javafx.scene.Group;
-import javafx.scene.Node;
-import javafx.scene.Parent;
-import javafx.scene.Scene;
-import javafx.scene.shape.Rectangle;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * A base class for testing the content bounds methods of BaseNode used
- * by all of the FX toolkits.
- */
-public class ContentBoundsTest {
-    public static final BaseTransform IDENTITY;
-    public static final BaseTransform TRANSLATE;
-    public static final BaseTransform SCALE;
-    public static final BaseTransform ROTATE;
-    public static final BaseTransform SCALE_TRANSLATE;
-    public static final BaseTransform TRANSLATE_SCALE;
-
-    public static BaseTransform translate(BaseTransform transform,
-                                          double tx, double ty)
-    {
-        transform = BaseTransform.getInstance(transform);
-        return transform.deriveWithConcatenation(1, 0, 0, 1, tx, ty);
-    }
-
-    public static BaseTransform scale(BaseTransform transform,
-                                      double sx, double sy)
-    {
-        transform = BaseTransform.getInstance(transform);
-        return transform.deriveWithConcatenation(sx, 0, 0, sy, 0, 0);
-    }
-
-    public static BaseTransform rotate(BaseTransform transform,
-                                       double degrees)
-    {
-        Affine2D t2d = new Affine2D(transform);
-        t2d.rotate(Math.toRadians(degrees));
-        return t2d;
-    }
-
-    static {
-        IDENTITY = BaseTransform.IDENTITY_TRANSFORM;
-        TRANSLATE = translate(IDENTITY, 42.3, 16.5);
-        SCALE = scale(IDENTITY, 0.7, 0.6);
-        ROTATE = rotate(IDENTITY, 135);
-        TRANSLATE_SCALE = scale(TRANSLATE, 0.8, 0.9);
-        SCALE_TRANSLATE = translate(SCALE, 23.7, 83.5);
-    }
-
-    public static Node translate(double tx, double ty, Node n) {
-        n.setTranslateX(tx);
-        n.setTranslateY(ty);
-        return n;
-    }
-
-    public static Node scale(double sx, double sy, Node n) {
-        n.setScaleX(sx);
-        n.setScaleY(sy);
-        return n;
-    }
-
-    public static Node rotate(double rot, Node n) {
-        n.setRotate(rot);
-        return n;
-    }
-
-    public static Node group(Node... n) {
-        Group g = new Group(n);
-        return g;
-    }
-
-    public static Node makeRectangle(double x, double y, double w, double h) {
-        return new Rectangle(x, y, w, h);
-    }
-
-    public static NGNode getValidatedPGNode(Node n) {
-        if (n instanceof Parent) {
-            for (Node child : ((Parent) n).getChildrenUnmodifiable()) {
-                getValidatedPGNode(child);
-            }
-        }
-        NGNode pgn = n.impl_getPeer();
-        // Eeek, this is gross! I have to use reflection to invoke this
-        // method so that bounds are updated...
-        try {
-            java.lang.reflect.Method method = Node.class.getDeclaredMethod("updateBounds");
-            method.setAccessible(true);
-            method.invoke(n);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to update bounds", e);
-        }
-        n.impl_updatePeer();
-        return pgn;
-    }
-
-    public static BaseBounds getBounds(Node n, BaseTransform tx) {
-        Scene.impl_setAllowPGAccess(true);
-        NGNode pgn = getValidatedPGNode(n);
-        Scene.impl_setAllowPGAccess(false);
-        return pgn.getContentBounds(new RectBounds(), tx);
-    }
-
-    public static class TestPoint {
-        private float x;
-        private float y;
-        private boolean contains;
-
-        public TestPoint(float x, float y, boolean contains) {
-            this.x = x;
-            this.y = y;
-            this.contains = contains;
-        }
-
-        public boolean isContains() {
-            return contains;
-        }
-
-        public float getX() {
-            return x;
-        }
-
-        public float getY() {
-            return y;
-        }
-    }
-
-    public static void checkContentPoint(Node n, TestPoint tp,
-                                         BaseTransform transform)
-    {
-        BaseBounds bounds = getBounds(n, transform);
-        float c[] = new float[] {tp.getX(), tp.getY()};
-        transform.transform(c, 0, c, 0, 1);
-        boolean success = false;
-        try {
-            assertEquals(bounds.contains(c[0], c[1]), tp.isContains());
-            success = true;
-        } finally {
-            if (!success) {
-                System.err.println("Failed on bounds = "+bounds);
-                System.err.println("with transform = "+transform);
-                System.err.println("with  x,  y = "+tp.getX()+", "+tp.getY());
-                System.err.println("and  tx, ty = "+c[0]+", "+c[1]);
-            }
-        }
-    }
-
-    // When a chain of transforms is involved, it can help to back off
-    // slightly from the edges of a shape using this tiny constant
-    // to avoid failing a test due to floating point rounding error.
-    static final float EPSILON = 1e-6f;
-
-    public TestPoint[] rectPoints(float x, float y, float w, float h) {
-        return new TestPoint[] {
-            new TestPoint(x  +EPSILON, y  +EPSILON, true),
-            new TestPoint(x+w-EPSILON, y  +EPSILON, true),
-            new TestPoint(x  +EPSILON, y+h-EPSILON, true),
-            new TestPoint(x+w-EPSILON, y+h-EPSILON, true),
-            new TestPoint(x+w, y+h+h, false)
-        };
-    }
-
-    public BaseBounds getBounds(TestPoint... testpts) {
-        RectBounds rb = new RectBounds();
-        for (TestPoint tp : testpts) {
-            if (tp.isContains()) {
-                rb.add(tp.getX(), tp.getY());
-            }
-        }
-        return rb;
-    }
-
-    public TestPoint[] translate(float tx, float ty, TestPoint... testpts) {
-        TestPoint ret[] = new TestPoint[testpts.length];
-        for (int i = 0; i < testpts.length; i++) {
-            TestPoint tp = testpts[i];
-            ret[i] = new TestPoint(tp.getX() + tx, tp.getY() + ty,
-                                   tp.isContains());
-        }
-        return ret;
-    }
-
-    public TestPoint[] scale(float sx, float sy, TestPoint... testpts) {
-        BaseBounds bounds = getBounds(testpts);
-        float cx = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
-        float cy = (bounds.getMinY() + bounds.getMaxY()) / 2.0f;
-        TestPoint ret[] = new TestPoint[testpts.length];
-        for (int i = 0; i < testpts.length; i++) {
-            TestPoint tp = testpts[i];
-            ret[i] = new TestPoint((tp.getX() - cx) * sx + cx,
-                                   (tp.getY() - cy) * sy + cy,
-                                   tp.isContains());
-        }
-        return ret;
-    }
-
-    public TestPoint[] rotate(double degrees, TestPoint... testpts) {
-        BaseBounds bounds = getBounds(testpts);
-        float cx = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
-        float cy = (bounds.getMinY() + bounds.getMaxY()) / 2.0f;
-        TestPoint ret[] = new TestPoint[testpts.length];
-        double radians = Math.toRadians(degrees);
-        float cos = (float) Math.cos(radians);
-        float sin = (float) Math.sin(radians);
-        for (int i = 0; i < testpts.length; i++) {
-            TestPoint tp = testpts[i];
-            float relx = tp.getX() - cx;
-            float rely = tp.getY() - cy;
-            ret[i] = new TestPoint(relx * cos - rely * sin + cx,
-                                   relx * sin + rely * cos + cy,
-                                   tp.isContains());
-        }
-        return ret;
-    }
-
-    public void checkPoints(Node n, TestPoint... testpts) {
-        for (TestPoint tp : testpts) {
-            checkContentPoint(n, tp, IDENTITY);
-            checkContentPoint(n, tp, TRANSLATE);
-            checkContentPoint(n, tp, SCALE);
-            checkContentPoint(n, tp, ROTATE);
-            checkContentPoint(n, tp, TRANSLATE_SCALE);
-            checkContentPoint(n, tp, SCALE_TRANSLATE);
-        }
-    }
-
-    @Test public void testRectangle() {
-        Node r = makeRectangle(10, 10, 20, 20);
-        checkPoints(r, rectPoints(10, 10, 20, 20));
-    }
-
-    @Test public void testTranslatedRectangle() {
-        Node r = translate(234.7f, 176.3f, makeRectangle(10, 10, 20, 20));
-        // Content bounds is local to the node, so we ignore the tx, ty
-        checkPoints(r, rectPoints(10, 10, 20, 20));
-    }
-
-    @Test public void testScaledRectangle() {
-        Node r = scale(1.3, 1.1, makeRectangle(10, 10, 20, 20));
-        // Content bounds is local to the node, so we ignore the sx, sy
-        checkPoints(r, rectPoints(10, 10, 20, 20));
-    }
-
-    @Test public void testRotatedRectangle() {
-        Node r = rotate(15, makeRectangle(10, 10, 20, 20));
-        // Content bounds is local to the node, so we ignore the rot
-        checkPoints(r, rectPoints(10, 10, 20, 20));
-    }
-
-    @Test public void testGroupedRectangle() {
-        Node r = group(makeRectangle(10, 10, 20, 20));
-        checkPoints(r, rectPoints(10, 10, 20, 20));
-    }
-
-    @Test public void testGroupedTranslatedRectangle() {
-        float tx = 234.7f;
-        float ty = 165.3f;
-        Node r = group(translate(tx, ty, makeRectangle(10, 10, 20, 20)));
-        checkPoints(r, translate(tx, ty, rectPoints(10, 10, 20, 20)));
-    }
-
-    @Test public void testGroupedScaledRectangle() {
-        float sx = 1.3f;
-        float sy = 1.1f;
-        Node n = group(scale(sx, sy, makeRectangle(10, 10, 20, 20)));
-        checkPoints(n, scale(sx, sy, rectPoints(10, 10, 20, 20)));
-    }
-
-    @Test public void testGroupedScaledGroupedTranslatedGroupedRotatedRectangle() {
-        float sx = 1.3f;
-        float sy = 1.1f;
-        float tx = 35.7f;
-        float ty = 93.1f;
-        float rot = 25;
-        Node n = group(scale(sx, sy,
-                    group(translate(tx, ty,
-                        group(rotate(rot,
-                            makeRectangle(10, 10, 20, 20)))))));
-        checkPoints(n, scale(sx, sy,
-                           translate(tx, ty,
-                               rotate(rot,
-                                   rectPoints(10, 10, 20, 20)))));
-    }
-}
--- a/modules/graphics/src/test/java/com/sun/javafx/sg/CullingTest.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,277 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import com.sun.javafx.geom.BaseBounds;
-import com.sun.javafx.geom.DirtyRegionContainer;
-import com.sun.javafx.geom.RectBounds;
-import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.javafx.sg.prism.NGNode;
-import javafx.scene.Group;
-import javafx.scene.Node;
-import javafx.scene.Scene;
-import javafx.scene.shape.Rectangle;
-import junit.framework.Assert;
-import org.junit.Test;
-
-public class CullingTest {
-
-    @Test
-    public void test_setCullBits_intersect() {
-        BaseNode bn = getBaseNode(new Rectangle(0, 0, 100, 100));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]
-                    {
-                    new RectBounds(50, 50, 150, 150),
-                    new RectBounds(70, 70, 170, 170)
-                    });
-        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(1 |(1 << 2), bn.cullingBits);
-    }
-
-    @Test
-    public void test_setCullBits_disjoint() {
-        BaseNode bn = getBaseNode(new Rectangle(0, 0, 100, 100));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]
-                    {
-                    new RectBounds(110, 110, 150, 150),
-                    new RectBounds(0, 101, 170, 170)
-                    });
-        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(0, bn.cullingBits);
-    }
-
-    @Test
-    public void test_setCullBits_within() {
-        BaseNode bn = getBaseNode(new Rectangle(50, 50, 100, 100));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]
-                    {
-                    new RectBounds(40, 40, 170, 170),
-                    new RectBounds(0, 0, 200, 200)
-                    });
-        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(2 | (2 << 2), bn.cullingBits);
-    }
-    
-    @Test
-    public void test_setCullBits_region_within() {
-        BaseNode bn = getBaseNode(new Rectangle(0, 0, 100, 100));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]
-                    {
-                    new RectBounds(40, 40, 70, 70),
-                    new RectBounds(10, 10, 20, 20)
-                    });
-        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(1 | (1 << 2), bn.cullingBits);
-    }
-
-    @Test
-    public void test_setCullBits_empty() {
-        BaseNode bn = getBaseNode(new Rectangle(50, 50, 100, 100));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]
-                    {
-                    new RectBounds(40, 40, 170, 170),
-                    new RectBounds().makeEmpty()
-                    });
-        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(2, bn.cullingBits);
-    }
-
-    @Test
-    public void test_setCullBits_null() {
-        BaseNode bn = getBaseNode(new Rectangle(50, 50, 100, 100));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]
-                    {
-                    new RectBounds(40, 40, 170, 170),
-                    null
-                    });
-        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(2, bn.cullingBits);
-    }
-
-    @Test
-    public void test_setCullBits_empty_regions() {
-        BaseNode bn = getBaseNode(new Rectangle(50, 50, 100, 100));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]{});
-        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(0, bn.cullingBits);
-    }
-
-    @Test
-    public void test_group_disjoint() {
-        Node g = group(new Rectangle(150, 0, 50, 50), new Rectangle(150, 60, 50, 50));
-        g.setTranslateX(10);
-        g.setTranslateY(10);
-        BaseNode gbn = getBaseNode(g);
-        BaseNode bn1 = getBaseNode(((Group)g).getChildren().get(0));
-        BaseNode bn2 = getBaseNode(((Group)g).getChildren().get(1));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 100, 100), new RectBounds(0, 110, 100, 210)});
-        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-        Assert.assertEquals(0, gbn.cullingBits);
-        for(Node n:((Group)g).getChildren()) {
-            Assert.assertEquals(0, getBaseNode(n).cullingBits);
-        }
-    }
-
-    @Test
-    public void test_group_intersect() {
-        Node g = group(new Rectangle(50, 50, 30, 30), new Rectangle(50, 120, 30, 30));
-        g.setTranslateX(10);
-        g.setTranslateY(10);
-        BaseNode gbn = getBaseNode(g);
-        BaseNode bn1 = getBaseNode(((Group)g).getChildren().get(0));
-        BaseNode bn2 = getBaseNode(((Group)g).getChildren().get(1));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 100, 100), new RectBounds(0, 110, 100, 210)});
-        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-
-        //check group
-        Assert.assertEquals(1 | (1 << 2), gbn.cullingBits);
-        
-        //check children               
-        Assert.assertEquals(2, bn1.cullingBits);
-        Assert.assertEquals(2 << 2, bn2.cullingBits);
-    }
-    
-    @Test
-    public void test_group_within() {
-        Node g = group(new Rectangle(50, 50, 30, 30), new Rectangle(50, 10, 30, 30));
-        g.setTranslateX(10);
-        g.setTranslateY(10);
-        BaseNode gbn = getBaseNode(g);
-        BaseNode bn1 = getBaseNode(((Group)g).getChildren().get(0));
-        BaseNode bn2 = getBaseNode(((Group)g).getChildren().get(1));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 100, 100), new RectBounds(0, 110, 100, 210)});
-        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-
-        //check group
-        Assert.assertEquals(2, gbn.cullingBits);
-        
-        //check children (as the group is "completely covered", the children should not have been processed)          
-        Assert.assertEquals(0, bn1.cullingBits);
-        Assert.assertEquals(0, bn2.cullingBits);
-    }
-    
-    @Test
-    public void test_region_within_group() {
-        Node g = group(new Rectangle(50, 10, 100, 100), new Rectangle(50, 120, 100, 100));
-        g.setTranslateX(5);
-        g.setTranslateY(5);
-        BaseNode gbn = getBaseNode(g);
-        BaseNode bn1 = getBaseNode(((Group)g).getChildren().get(0));
-        BaseNode bn2 = getBaseNode(((Group)g).getChildren().get(1));
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(70, 20, 100, 105), new RectBounds(70, 130, 100, 200)});
-        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-
-        //check group
-        Assert.assertEquals(1 | (1 << 2), gbn.cullingBits);
-
-        //check children
-        Assert.assertEquals(1, bn1.cullingBits);
-        Assert.assertEquals(1 << 2, bn2.cullingBits);
-    }
-
-    @Test
-    public void test_region_within_group_group() {
-        Node g1 = group(new Rectangle(50, 10, 100, 100), new Rectangle(50, 120, 100, 100));
-        g1.setTranslateX(5);
-        g1.setTranslateY(5);
-        BaseNode g1bn = getBaseNode(g1);
-        BaseNode bn1 = getBaseNode(((Group)g1).getChildren().get(0));
-        BaseNode bn2 = getBaseNode(((Group)g1).getChildren().get(1));
-
-        Node g = group(g1, new Rectangle(200, 200, 100, 100));
-        g.setTranslateX(5);
-        g.setTranslateY(5);
-        BaseNode gbn = getBaseNode(g);
-        BaseNode bn3 = getBaseNode(((Group)g).getChildren().get(1));
-
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(70, 25, 100, 105), new RectBounds(70, 130, 100, 200)});
-        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-
-        //check group
-        Assert.assertEquals(1 | (1 << 2), gbn.cullingBits);
-
-        //check group1
-        Assert.assertEquals(1 | (1 << 2), g1bn.cullingBits);
-
-        //check children
-        Assert.assertEquals(1, bn1.cullingBits);
-        Assert.assertEquals(1 << 2, bn2.cullingBits);
-        Assert.assertEquals(0, bn3.cullingBits);
-    }
-
-    @Test
-    public void test_region_group_full_withing_then_partial() {
-        Node g = group(new Rectangle(50, 50, 100, 100));
-        BaseNode gbn = getBaseNode(g);
-        BaseNode bn1 = getBaseNode(((Group)g).getChildren().get(0));
-
-        DirtyRegionContainer drc = new DirtyRegionContainer(2);
-        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 160, 160), new RectBounds(80, 80, 100, 100)});
-        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
-
-        Assert.assertEquals(2 | (1 << 2), gbn.cullingBits);
-        Assert.assertEquals(1 << 2, bn1.cullingBits);
-    }
-
-    public static BaseNode getBaseNode(Node n) {
-        Scene.impl_setAllowPGAccess(true);
-        // Eeek, this is gross! I have to use reflection to invoke this
-        // method so that bounds are updated...
-        try {
-            java.lang.reflect.Method method = Node.class.getDeclaredMethod("updateBounds");
-            method.setAccessible(true);
-            method.invoke(n);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to update bounds", e);
-        }
-        n.impl_updatePeer();
-        NGNode bn = n.impl_getPeer();
-        Scene.impl_setAllowPGAccess(false);
-        return bn;
-    }
-
-    public static Node group(Node... n) {
-        Group g = new Group(n);
-        return g;
-    }
-
-    public static BaseBounds getBounds(Node n, BaseTransform tx) {
-        BaseNode pgn = getBaseNode(n);
-        return pgn.getContentBounds(new RectBounds(), tx);
-    }
-}
--- a/modules/graphics/src/test/java/com/sun/javafx/sg/EffectDirtyRegionTest.java	Fri Jul 12 14:51:13 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,497 +0,0 @@
-/*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.javafx.sg;
-
-import com.sun.javafx.geom.BaseBounds;
-import com.sun.javafx.geom.DirtyRegionContainer;
-import com.sun.javafx.geom.DirtyRegionPool;
-import com.sun.javafx.geom.RectBounds;
-import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.javafx.sg.prism.NGNode;
-import com.sun.scenario.effect.*;
-import javafx.scene.Group;
-import javafx.scene.Node;
-import javafx.scene.Scene;
-import javafx.scene.shape.Rectangle;
-import junit.framework.Assert;
-import org.junit.Test;
-
-public class EffectDirtyRegionTest {
-
-    DirtyRegionContainer drc;
-    DirtyRegionContainer drcExpected;
-    DirtyRegionPool drp;
-    Effect effect;
-    BaseNode g1bn;
-    BaseNode gbn;
-
-    @Test
-    public void dropShadowTest() {
-        setupTest();
-
-        effect = new DropShadow();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(40, 40, 70, 70)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(30, 30, 90, 90)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void colorAdjustTest() {
-        setupTest();
-
-        effect = new ColorAdjust();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60),
-                new RectBounds(70, 70, 80, 80)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void bloomTest() {
-        setupTest();
-
-        effect = new Bloom();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 80, 80)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void blendTest() {
-        setupTest();
-
-        effect = new Blend(Blend.Mode.ADD, new DropShadow(), new ColorAdjust());
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(40, 40, 70, 70)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(30, 30, 90, 90)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void boxBlurTest() {
-        setupTest();
-
-        effect = new BoxBlur(10, 5, 3);
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(36, 44, 74, 66)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(22, 38, 88, 72),
-                new RectBounds(56, 64, 94, 86)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void gausianBlurTest() {
-        setupTest();
-
-        effect = new GaussianBlur(5);
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(45, 45, 65, 65)
-            });
-        compareResult(drcExpected, drc);
-
-        ((GaussianBlur)effect).setRadius(2);
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(43, 43, 67, 67),
-                new RectBounds(68, 68, 82, 82)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void glowTest() {
-        setupTest();
-
-        effect = new Glow();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 80, 80)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void innerShadowTest() {
-        setupTest();
-
-        effect = new InnerShadow();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(40, 40, 70, 70)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(30, 30, 90, 90)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void motionBlurTest() {
-        setupTest();
-
-        effect = new MotionBlur();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(40, 50, 70, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        ((MotionBlur)effect).setAngle((float) Math.toRadians(90));
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(39, 40, 71, 70),
-                new RectBounds(69, 60, 81, 90)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void sepiaToneTest() {
-        setupTest();
-
-        effect = new SepiaTone();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60),
-                new RectBounds(70, 70, 80, 80)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void boxShadowTest() {
-        setupTest();
-
-        effect = new BoxShadow();
-        ((BoxShadow) effect).setHorizontalSize(10);
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(45, 50, 65, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        ((BoxShadow) effect).setHorizontalSize(0);
-        ((BoxShadow) effect).setVerticalSize(10);
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(45, 45, 65, 65),
-                new RectBounds(70, 65, 80, 85)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void generalShadowTest() {
-        setupTest();
-
-        effect = new GeneralShadow();
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(40, 40, 70, 70)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(30, 30, 90, 90)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void cropTest() {
-        setupTest();
-
-        effect = new Crop(new DropShadow());
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 80, 80)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    @Test
-    public void offsetTest() {
-        setupTest();
-
-        effect = new Offset(10, -10, new DropShadow());
-
-        drc.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 50, 60, 60)
-            });
-        g1bn.setEffect(effect);
-        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 30, 80, 60)
-            });
-        compareResult(drcExpected, drc);
-
-        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
-        gbn.setEffect(effect);
-        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
-        drcExpected.deriveWithNewRegions(new RectBounds[]
-            {
-                new RectBounds(50, 10, 100, 80)
-            });
-        compareResult(drcExpected, drc);
-    }
-
-    private void setupTest() {
-        drc = new DirtyRegionContainer(6);
-        drcExpected = new DirtyRegionContainer(6);
-        drp = new DirtyRegionPool(4);
-
-        Node g1 = group(new Rectangle(50, 50, 10, 10));
-        g1bn = getBaseNode(g1);
-        Node g = group(g1, new Rectangle(70, 70, 10, 10));
-        gbn = getBaseNode(g);
-    }
-
-    private void compareResult(DirtyRegionContainer expected, DirtyRegionContainer computed) {
-        for (int i = 0; i < computed.size(); i++) {
-            Assert.assertEquals(expected.getDirtyRegion(i), computed.getDirtyRegion(i));
-        }
-    }
-
-    public static BaseNode getBaseNode(Node n) {
-        Scene.impl_setAllowPGAccess(true);
-        // Eeek, this is gross! I have to use reflection to invoke this
-        // method so that bounds are updated...
-        try {
-            java.lang.reflect.Method method = Node.class.getDeclaredMethod("updateBounds");
-            method.setAccessible(true);
-            method.invoke(n);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to update bounds", e);
-        }
-        n.impl_updatePeer();
-        NGNode bn = n.impl_getPeer();
-        Scene.impl_setAllowPGAccess(false);
-        return bn;
-    }
-
-    public static Node group(Node... n) {
-        Group g = new Group(n);
-        return g;
-    }
-
-    public static BaseBounds getBounds(Node n, BaseTransform tx) {
-        BaseNode pgn = getBaseNode(n);
-        return pgn.getContentBounds(new RectBounds(), tx);
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/test/java/com/sun/javafx/sg/prism/ContentBoundsTest.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import com.sun.javafx.geom.BaseBounds;
+import com.sun.javafx.geom.RectBounds;
+import com.sun.javafx.geom.transform.Affine2D;
+import com.sun.javafx.geom.transform.BaseTransform;
+import com.sun.javafx.sg.prism.NGNode;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * A base class for testing the content bounds methods of BaseNode used
+ * by all of the FX toolkits.
+ */
+public class ContentBoundsTest {
+    public static final BaseTransform IDENTITY;
+    public static final BaseTransform TRANSLATE;
+    public static final BaseTransform SCALE;
+    public static final BaseTransform ROTATE;
+    public static final BaseTransform SCALE_TRANSLATE;
+    public static final BaseTransform TRANSLATE_SCALE;
+
+    public static BaseTransform translate(BaseTransform transform,
+                                          double tx, double ty)
+    {
+        transform = BaseTransform.getInstance(transform);
+        return transform.deriveWithConcatenation(1, 0, 0, 1, tx, ty);
+    }
+
+    public static BaseTransform scale(BaseTransform transform,
+                                      double sx, double sy)
+    {
+        transform = BaseTransform.getInstance(transform);
+        return transform.deriveWithConcatenation(sx, 0, 0, sy, 0, 0);
+    }
+
+    public static BaseTransform rotate(BaseTransform transform,
+                                       double degrees)
+    {
+        Affine2D t2d = new Affine2D(transform);
+        t2d.rotate(Math.toRadians(degrees));
+        return t2d;
+    }
+
+    static {
+        IDENTITY = BaseTransform.IDENTITY_TRANSFORM;
+        TRANSLATE = translate(IDENTITY, 42.3, 16.5);
+        SCALE = scale(IDENTITY, 0.7, 0.6);
+        ROTATE = rotate(IDENTITY, 135);
+        TRANSLATE_SCALE = scale(TRANSLATE, 0.8, 0.9);
+        SCALE_TRANSLATE = translate(SCALE, 23.7, 83.5);
+    }
+
+    public static Node translate(double tx, double ty, Node n) {
+        n.setTranslateX(tx);
+        n.setTranslateY(ty);
+        return n;
+    }
+
+    public static Node scale(double sx, double sy, Node n) {
+        n.setScaleX(sx);
+        n.setScaleY(sy);
+        return n;
+    }
+
+    public static Node rotate(double rot, Node n) {
+        n.setRotate(rot);
+        return n;
+    }
+
+    public static Node group(Node... n) {
+        Group g = new Group(n);
+        return g;
+    }
+
+    public static Node makeRectangle(double x, double y, double w, double h) {
+        return new Rectangle(x, y, w, h);
+    }
+
+    public static NGNode getValidatedPGNode(Node n) {
+        if (n instanceof Parent) {
+            for (Node child : ((Parent) n).getChildrenUnmodifiable()) {
+                getValidatedPGNode(child);
+            }
+        }
+        NGNode pgn = n.impl_getPeer();
+        // Eeek, this is gross! I have to use reflection to invoke this
+        // method so that bounds are updated...
+        try {
+            java.lang.reflect.Method method = Node.class.getDeclaredMethod("updateBounds");
+            method.setAccessible(true);
+            method.invoke(n);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to update bounds", e);
+        }
+        n.impl_updatePeer();
+        return pgn;
+    }
+
+    public static BaseBounds getBounds(Node n, BaseTransform tx) {
+        Scene.impl_setAllowPGAccess(true);
+        NGNode pgn = getValidatedPGNode(n);
+        Scene.impl_setAllowPGAccess(false);
+        return pgn.getContentBounds(new RectBounds(), tx);
+    }
+
+    public static class TestPoint {
+        private float x;
+        private float y;
+        private boolean contains;
+
+        public TestPoint(float x, float y, boolean contains) {
+            this.x = x;
+            this.y = y;
+            this.contains = contains;
+        }
+
+        public boolean isContains() {
+            return contains;
+        }
+
+        public float getX() {
+            return x;
+        }
+
+        public float getY() {
+            return y;
+        }
+    }
+
+    public static void checkContentPoint(Node n, TestPoint tp,
+                                         BaseTransform transform)
+    {
+        BaseBounds bounds = getBounds(n, transform);
+        float c[] = new float[] {tp.getX(), tp.getY()};
+        transform.transform(c, 0, c, 0, 1);
+        boolean success = false;
+        try {
+            assertEquals(bounds.contains(c[0], c[1]), tp.isContains());
+            success = true;
+        } finally {
+            if (!success) {
+                System.err.println("Failed on bounds = "+bounds);
+                System.err.println("with transform = "+transform);
+                System.err.println("with  x,  y = "+tp.getX()+", "+tp.getY());
+                System.err.println("and  tx, ty = "+c[0]+", "+c[1]);
+            }
+        }
+    }
+
+    // When a chain of transforms is involved, it can help to back off
+    // slightly from the edges of a shape using this tiny constant
+    // to avoid failing a test due to floating point rounding error.
+    static final float EPSILON = 1e-6f;
+
+    public TestPoint[] rectPoints(float x, float y, float w, float h) {
+        return new TestPoint[] {
+            new TestPoint(x  +EPSILON, y  +EPSILON, true),
+            new TestPoint(x+w-EPSILON, y  +EPSILON, true),
+            new TestPoint(x  +EPSILON, y+h-EPSILON, true),
+            new TestPoint(x+w-EPSILON, y+h-EPSILON, true),
+            new TestPoint(x+w, y+h+h, false)
+        };
+    }
+
+    public BaseBounds getBounds(TestPoint... testpts) {
+        RectBounds rb = new RectBounds();
+        for (TestPoint tp : testpts) {
+            if (tp.isContains()) {
+                rb.add(tp.getX(), tp.getY());
+            }
+        }
+        return rb;
+    }
+
+    public TestPoint[] translate(float tx, float ty, TestPoint... testpts) {
+        TestPoint ret[] = new TestPoint[testpts.length];
+        for (int i = 0; i < testpts.length; i++) {
+            TestPoint tp = testpts[i];
+            ret[i] = new TestPoint(tp.getX() + tx, tp.getY() + ty,
+                                   tp.isContains());
+        }
+        return ret;
+    }
+
+    public TestPoint[] scale(float sx, float sy, TestPoint... testpts) {
+        BaseBounds bounds = getBounds(testpts);
+        float cx = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
+        float cy = (bounds.getMinY() + bounds.getMaxY()) / 2.0f;
+        TestPoint ret[] = new TestPoint[testpts.length];
+        for (int i = 0; i < testpts.length; i++) {
+            TestPoint tp = testpts[i];
+            ret[i] = new TestPoint((tp.getX() - cx) * sx + cx,
+                                   (tp.getY() - cy) * sy + cy,
+                                   tp.isContains());
+        }
+        return ret;
+    }
+
+    public TestPoint[] rotate(double degrees, TestPoint... testpts) {
+        BaseBounds bounds = getBounds(testpts);
+        float cx = (bounds.getMinX() + bounds.getMaxX()) / 2.0f;
+        float cy = (bounds.getMinY() + bounds.getMaxY()) / 2.0f;
+        TestPoint ret[] = new TestPoint[testpts.length];
+        double radians = Math.toRadians(degrees);
+        float cos = (float) Math.cos(radians);
+        float sin = (float) Math.sin(radians);
+        for (int i = 0; i < testpts.length; i++) {
+            TestPoint tp = testpts[i];
+            float relx = tp.getX() - cx;
+            float rely = tp.getY() - cy;
+            ret[i] = new TestPoint(relx * cos - rely * sin + cx,
+                                   relx * sin + rely * cos + cy,
+                                   tp.isContains());
+        }
+        return ret;
+    }
+
+    public void checkPoints(Node n, TestPoint... testpts) {
+        for (TestPoint tp : testpts) {
+            checkContentPoint(n, tp, IDENTITY);
+            checkContentPoint(n, tp, TRANSLATE);
+            checkContentPoint(n, tp, SCALE);
+            checkContentPoint(n, tp, ROTATE);
+            checkContentPoint(n, tp, TRANSLATE_SCALE);
+            checkContentPoint(n, tp, SCALE_TRANSLATE);
+        }
+    }
+
+    @Test public void testRectangle() {
+        Node r = makeRectangle(10, 10, 20, 20);
+        checkPoints(r, rectPoints(10, 10, 20, 20));
+    }
+
+    @Test public void testTranslatedRectangle() {
+        Node r = translate(234.7f, 176.3f, makeRectangle(10, 10, 20, 20));
+        // Content bounds is local to the node, so we ignore the tx, ty
+        checkPoints(r, rectPoints(10, 10, 20, 20));
+    }
+
+    @Test public void testScaledRectangle() {
+        Node r = scale(1.3, 1.1, makeRectangle(10, 10, 20, 20));
+        // Content bounds is local to the node, so we ignore the sx, sy
+        checkPoints(r, rectPoints(10, 10, 20, 20));
+    }
+
+    @Test public void testRotatedRectangle() {
+        Node r = rotate(15, makeRectangle(10, 10, 20, 20));
+        // Content bounds is local to the node, so we ignore the rot
+        checkPoints(r, rectPoints(10, 10, 20, 20));
+    }
+
+    @Test public void testGroupedRectangle() {
+        Node r = group(makeRectangle(10, 10, 20, 20));
+        checkPoints(r, rectPoints(10, 10, 20, 20));
+    }
+
+    @Test public void testGroupedTranslatedRectangle() {
+        float tx = 234.7f;
+        float ty = 165.3f;
+        Node r = group(translate(tx, ty, makeRectangle(10, 10, 20, 20)));
+        checkPoints(r, translate(tx, ty, rectPoints(10, 10, 20, 20)));
+    }
+
+    @Test public void testGroupedScaledRectangle() {
+        float sx = 1.3f;
+        float sy = 1.1f;
+        Node n = group(scale(sx, sy, makeRectangle(10, 10, 20, 20)));
+        checkPoints(n, scale(sx, sy, rectPoints(10, 10, 20, 20)));
+    }
+
+    @Test public void testGroupedScaledGroupedTranslatedGroupedRotatedRectangle() {
+        float sx = 1.3f;
+        float sy = 1.1f;
+        float tx = 35.7f;
+        float ty = 93.1f;
+        float rot = 25;
+        Node n = group(scale(sx, sy,
+                    group(translate(tx, ty,
+                        group(rotate(rot,
+                            makeRectangle(10, 10, 20, 20)))))));
+        checkPoints(n, scale(sx, sy,
+                           translate(tx, ty,
+                               rotate(rot,
+                                   rectPoints(10, 10, 20, 20)))));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/test/java/com/sun/javafx/sg/prism/CullingTest.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import com.sun.javafx.geom.BaseBounds;
+import com.sun.javafx.geom.DirtyRegionContainer;
+import com.sun.javafx.geom.RectBounds;
+import com.sun.javafx.geom.transform.BaseTransform;
+import junit.framework.Assert;
+import org.junit.Test;
+
+public class CullingTest {
+
+    @Test
+    public void test_setCullBits_intersect() {
+        NGNode bn = getPeerNode(new Rectangle(0, 0, 100, 100));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]
+                    {
+                    new RectBounds(50, 50, 150, 150),
+                    new RectBounds(70, 70, 170, 170)
+                    });
+        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(1 |(1 << 2), bn.cullingBits);
+    }
+
+    @Test
+    public void test_setCullBits_disjoint() {
+        NGNode bn = getPeerNode(new Rectangle(0, 0, 100, 100));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]
+                    {
+                    new RectBounds(110, 110, 150, 150),
+                    new RectBounds(0, 101, 170, 170)
+                    });
+        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(0, bn.cullingBits);
+    }
+
+    @Test
+    public void test_setCullBits_within() {
+        NGNode bn = getPeerNode(new Rectangle(50, 50, 100, 100));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]
+                    {
+                    new RectBounds(40, 40, 170, 170),
+                    new RectBounds(0, 0, 200, 200)
+                    });
+        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(2 | (2 << 2), bn.cullingBits);
+    }
+    
+    @Test
+    public void test_setCullBits_region_within() {
+        NGNode bn = getPeerNode(new Rectangle(0, 0, 100, 100));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]
+                    {
+                    new RectBounds(40, 40, 70, 70),
+                    new RectBounds(10, 10, 20, 20)
+                    });
+        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(1 | (1 << 2), bn.cullingBits);
+    }
+
+    @Test
+    public void test_setCullBits_empty() {
+        NGNode bn = getPeerNode(new Rectangle(50, 50, 100, 100));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]
+                    {
+                    new RectBounds(40, 40, 170, 170),
+                    new RectBounds().makeEmpty()
+                    });
+        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(2, bn.cullingBits);
+    }
+
+    @Test
+    public void test_setCullBits_null() {
+        NGNode bn = getPeerNode(new Rectangle(50, 50, 100, 100));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]
+                    {
+                    new RectBounds(40, 40, 170, 170),
+                    null
+                    });
+        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(2, bn.cullingBits);
+    }
+
+    @Test
+    public void test_setCullBits_empty_regions() {
+        NGNode bn = getPeerNode(new Rectangle(50, 50, 100, 100));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]{});
+        bn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(0, bn.cullingBits);
+    }
+
+    @Test
+    public void test_group_disjoint() {
+        Node g = group(new Rectangle(150, 0, 50, 50), new Rectangle(150, 60, 50, 50));
+        g.setTranslateX(10);
+        g.setTranslateY(10);
+        NGNode gbn = getPeerNode(g);
+        NGNode bn1 = getPeerNode(((Group) g).getChildren().get(0));
+        NGNode bn2 = getPeerNode(((Group) g).getChildren().get(1));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 100, 100), new RectBounds(0, 110, 100, 210)});
+        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+        Assert.assertEquals(0, gbn.cullingBits);
+        for(Node n:((Group)g).getChildren()) {
+            Assert.assertEquals(0, getPeerNode(n).cullingBits);
+        }
+    }
+
+    @Test
+    public void test_group_intersect() {
+        Node g = group(new Rectangle(50, 50, 30, 30), new Rectangle(50, 120, 30, 30));
+        g.setTranslateX(10);
+        g.setTranslateY(10);
+        NGNode gbn = getPeerNode(g);
+        NGNode bn1 = getPeerNode(((Group) g).getChildren().get(0));
+        NGNode bn2 = getPeerNode(((Group) g).getChildren().get(1));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 100, 100), new RectBounds(0, 110, 100, 210)});
+        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+
+        //check group
+        Assert.assertEquals(1 | (1 << 2), gbn.cullingBits);
+        
+        //check children               
+        Assert.assertEquals(2, bn1.cullingBits);
+        Assert.assertEquals(2 << 2, bn2.cullingBits);
+    }
+    
+    @Test
+    public void test_group_within() {
+        Node g = group(new Rectangle(50, 50, 30, 30), new Rectangle(50, 10, 30, 30));
+        g.setTranslateX(10);
+        g.setTranslateY(10);
+        NGNode gbn = getPeerNode(g);
+        NGNode bn1 = getPeerNode(((Group) g).getChildren().get(0));
+        NGNode bn2 = getPeerNode(((Group) g).getChildren().get(1));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 100, 100), new RectBounds(0, 110, 100, 210)});
+        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+
+        //check group
+        Assert.assertEquals(2, gbn.cullingBits);
+        
+        //check children (as the group is "completely covered", the children should not have been processed)          
+        Assert.assertEquals(0, bn1.cullingBits);
+        Assert.assertEquals(0, bn2.cullingBits);
+    }
+    
+    @Test
+    public void test_region_within_group() {
+        Node g = group(new Rectangle(50, 10, 100, 100), new Rectangle(50, 120, 100, 100));
+        g.setTranslateX(5);
+        g.setTranslateY(5);
+        NGNode gbn = getPeerNode(g);
+        NGNode bn1 = getPeerNode(((Group) g).getChildren().get(0));
+        NGNode bn2 = getPeerNode(((Group) g).getChildren().get(1));
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(70, 20, 100, 105), new RectBounds(70, 130, 100, 200)});
+        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+
+        //check group
+        Assert.assertEquals(1 | (1 << 2), gbn.cullingBits);
+
+        //check children
+        Assert.assertEquals(1, bn1.cullingBits);
+        Assert.assertEquals(1 << 2, bn2.cullingBits);
+    }
+
+    @Test
+    public void test_region_within_group_group() {
+        Node g1 = group(new Rectangle(50, 10, 100, 100), new Rectangle(50, 120, 100, 100));
+        g1.setTranslateX(5);
+        g1.setTranslateY(5);
+        NGNode g1bn = getPeerNode(g1);
+        NGNode bn1 = getPeerNode(((Group) g1).getChildren().get(0));
+        NGNode bn2 = getPeerNode(((Group) g1).getChildren().get(1));
+
+        Node g = group(g1, new Rectangle(200, 200, 100, 100));
+        g.setTranslateX(5);
+        g.setTranslateY(5);
+        NGNode gbn = getPeerNode(g);
+        NGNode bn3 = getPeerNode(((Group) g).getChildren().get(1));
+
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(70, 25, 100, 105), new RectBounds(70, 130, 100, 200)});
+        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+
+        //check group
+        Assert.assertEquals(1 | (1 << 2), gbn.cullingBits);
+
+        //check group1
+        Assert.assertEquals(1 | (1 << 2), g1bn.cullingBits);
+
+        //check children
+        Assert.assertEquals(1, bn1.cullingBits);
+        Assert.assertEquals(1 << 2, bn2.cullingBits);
+        Assert.assertEquals(0, bn3.cullingBits);
+    }
+
+    @Test
+    public void test_region_group_full_withing_then_partial() {
+        Node g = group(new Rectangle(50, 50, 100, 100));
+        NGNode gbn = getPeerNode(g);
+        NGNode bn1 = getPeerNode(((Group) g).getChildren().get(0));
+
+        DirtyRegionContainer drc = new DirtyRegionContainer(2);
+        drc.deriveWithNewRegions(new RectBounds[]{new RectBounds(0, 0, 160, 160), new RectBounds(80, 80, 100, 100)});
+        gbn.markCullRegions(drc, -1, BaseTransform.IDENTITY_TRANSFORM, null);
+
+        Assert.assertEquals(2 | (1 << 2), gbn.cullingBits);
+        Assert.assertEquals(1 << 2, bn1.cullingBits);
+    }
+
+    public static NGNode getPeerNode(Node n) {
+        Scene.impl_setAllowPGAccess(true);
+        // Eeek, this is gross! I have to use reflection to invoke this
+        // method so that bounds are updated...
+        try {
+            java.lang.reflect.Method method = Node.class.getDeclaredMethod("updateBounds");
+            method.setAccessible(true);
+            method.invoke(n);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to update bounds", e);
+        }
+        n.impl_updatePeer();
+        NGNode bn = n.impl_getPeer();
+        Scene.impl_setAllowPGAccess(false);
+        return bn;
+    }
+
+    public static Node group(Node... n) {
+        Group g = new Group(n);
+        return g;
+    }
+
+    public static BaseBounds getBounds(Node n, BaseTransform tx) {
+        NGNode pgn = getPeerNode(n);
+        return pgn.getContentBounds(new RectBounds(), tx);
+    }
+}
--- a/modules/graphics/src/test/java/com/sun/javafx/sg/prism/DirtyRegionTestBase.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/test/java/com/sun/javafx/sg/prism/DirtyRegionTestBase.java	Fri Jul 12 08:35:56 2013 -0700
@@ -25,25 +25,27 @@
 
 package com.sun.javafx.sg.prism;
 
+import javafx.geometry.Insets;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.CornerRadii;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import com.sun.javafx.geom.BaseBounds;
 import com.sun.javafx.geom.DirtyRegionContainer;
 import com.sun.javafx.geom.DirtyRegionPool;
 import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.geom.transform.GeneralTransform3D;
-import com.sun.javafx.sg.BaseNode;
 import com.sun.javafx.sg.prism.NodeTestUtils.TestNGGroup;
 import com.sun.prism.paint.Color;
 import com.sun.scenario.effect.Effect;
-import javafx.geometry.Insets;
-import javafx.scene.layout.Background;
-import javafx.scene.layout.BackgroundFill;
-import javafx.scene.layout.CornerRadii;
 import org.junit.runners.Parameterized;
-
-import java.lang.reflect.Field;
-import java.util.*;
-
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -90,7 +92,7 @@
                 // This simulates making it invisible, painting, and then
                 // making it visible again.
                 node.setOpacity(0f);
-                BaseNode parent = node;
+                NGNode parent = node;
                 while(parent.getParent() != null) parent = parent.getParent();
                 parent.render(TestGraphics.TEST_GRAPHICS);
                 // Now we can go ahead and set the opacity
@@ -184,7 +186,7 @@
                 // This simulates making it invisible, painting, and then
                 // making it visible again.
                 node.setVisible(false);
-                BaseNode parent = node;
+                NGNode parent = node;
                 while(parent.getParent() != null) parent = parent.getParent();
                 parent.render(TestGraphics.TEST_GRAPHICS);
                 // Now we can go ahead and set the opacity
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/test/java/com/sun/javafx/sg/prism/EffectDirtyRegionTest.java	Fri Jul 12 08:35:56 2013 -0700
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.javafx.sg.prism;
+
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import com.sun.javafx.geom.BaseBounds;
+import com.sun.javafx.geom.DirtyRegionContainer;
+import com.sun.javafx.geom.DirtyRegionPool;
+import com.sun.javafx.geom.RectBounds;
+import com.sun.javafx.geom.transform.BaseTransform;
+import com.sun.javafx.sg.prism.NGNode;
+import com.sun.scenario.effect.Blend;
+import com.sun.scenario.effect.Bloom;
+import com.sun.scenario.effect.BoxBlur;
+import com.sun.scenario.effect.BoxShadow;
+import com.sun.scenario.effect.ColorAdjust;
+import com.sun.scenario.effect.Crop;
+import com.sun.scenario.effect.DropShadow;
+import com.sun.scenario.effect.Effect;
+import com.sun.scenario.effect.GaussianBlur;
+import com.sun.scenario.effect.GeneralShadow;
+import com.sun.scenario.effect.Glow;
+import com.sun.scenario.effect.InnerShadow;
+import com.sun.scenario.effect.MotionBlur;
+import com.sun.scenario.effect.Offset;
+import com.sun.scenario.effect.SepiaTone;
+import junit.framework.Assert;
+import org.junit.Test;
+
+public class EffectDirtyRegionTest {
+
+    DirtyRegionContainer drc;
+    DirtyRegionContainer drcExpected;
+    DirtyRegionPool drp;
+    Effect effect;
+    NGNode g1bn;
+    NGNode gbn;
+
+    @Test
+    public void dropShadowTest() {
+        setupTest();
+
+        effect = new DropShadow();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(40, 40, 70, 70)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(30, 30, 90, 90)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void colorAdjustTest() {
+        setupTest();
+
+        effect = new ColorAdjust();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60),
+                new RectBounds(70, 70, 80, 80)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void bloomTest() {
+        setupTest();
+
+        effect = new Bloom();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 80, 80)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void blendTest() {
+        setupTest();
+
+        effect = new Blend(Blend.Mode.ADD, new DropShadow(), new ColorAdjust());
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(40, 40, 70, 70)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(30, 30, 90, 90)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void boxBlurTest() {
+        setupTest();
+
+        effect = new BoxBlur(10, 5, 3);
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(36, 44, 74, 66)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(22, 38, 88, 72),
+                new RectBounds(56, 64, 94, 86)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void gausianBlurTest() {
+        setupTest();
+
+        effect = new GaussianBlur(5);
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(45, 45, 65, 65)
+            });
+        compareResult(drcExpected, drc);
+
+        ((GaussianBlur)effect).setRadius(2);
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(43, 43, 67, 67),
+                new RectBounds(68, 68, 82, 82)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void glowTest() {
+        setupTest();
+
+        effect = new Glow();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 80, 80)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void innerShadowTest() {
+        setupTest();
+
+        effect = new InnerShadow();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(40, 40, 70, 70)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(30, 30, 90, 90)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void motionBlurTest() {
+        setupTest();
+
+        effect = new MotionBlur();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(40, 50, 70, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        ((MotionBlur)effect).setAngle((float) Math.toRadians(90));
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(39, 40, 71, 70),
+                new RectBounds(69, 60, 81, 90)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void sepiaToneTest() {
+        setupTest();
+
+        effect = new SepiaTone();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60),
+                new RectBounds(70, 70, 80, 80)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void boxShadowTest() {
+        setupTest();
+
+        effect = new BoxShadow();
+        ((BoxShadow) effect).setHorizontalSize(10);
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(45, 50, 65, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        ((BoxShadow) effect).setHorizontalSize(0);
+        ((BoxShadow) effect).setVerticalSize(10);
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(45, 45, 65, 65),
+                new RectBounds(70, 65, 80, 85)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void generalShadowTest() {
+        setupTest();
+
+        effect = new GeneralShadow();
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(40, 40, 70, 70)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(30, 30, 90, 90)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void cropTest() {
+        setupTest();
+
+        effect = new Crop(new DropShadow());
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 80, 80)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    @Test
+    public void offsetTest() {
+        setupTest();
+
+        effect = new Offset(10, -10, new DropShadow());
+
+        drc.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 50, 60, 60)
+            });
+        g1bn.setEffect(effect);
+        g1bn.applyEffect(g1bn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 30, 80, 60)
+            });
+        compareResult(drcExpected, drc);
+
+        drc.addDirtyRegion(new RectBounds(70, 70, 80, 80));
+        gbn.setEffect(effect);
+        gbn.applyEffect(gbn.getEffectFilter(), drc, drp);
+        drcExpected.deriveWithNewRegions(new RectBounds[]
+            {
+                new RectBounds(50, 10, 100, 80)
+            });
+        compareResult(drcExpected, drc);
+    }
+
+    private void setupTest() {
+        drc = new DirtyRegionContainer(6);
+        drcExpected = new DirtyRegionContainer(6);
+        drp = new DirtyRegionPool(4);
+
+        Node g1 = group(new Rectangle(50, 50, 10, 10));
+        g1bn = getBaseNode(g1);
+        Node g = group(g1, new Rectangle(70, 70, 10, 10));
+        gbn = getBaseNode(g);
+    }
+
+    private void compareResult(DirtyRegionContainer expected, DirtyRegionContainer computed) {
+        for (int i = 0; i < computed.size(); i++) {
+            Assert.assertEquals(expected.getDirtyRegion(i), computed.getDirtyRegion(i));
+        }
+    }
+
+    public static NGNode getBaseNode(Node n) {
+        Scene.impl_setAllowPGAccess(true);
+        // Eeek, this is gross! I have to use reflection to invoke this
+        // method so that bounds are updated...
+        try {
+            java.lang.reflect.Method method = Node.class.getDeclaredMethod("updateBounds");
+            method.setAccessible(true);
+            method.invoke(n);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to update bounds", e);
+        }
+        n.impl_updatePeer();
+        NGNode bn = n.impl_getPeer();
+        Scene.impl_setAllowPGAccess(false);
+        return bn;
+    }
+
+    public static Node group(Node... n) {
+        Group g = new Group(n);
+        return g;
+    }
+
+    public static BaseBounds getBounds(Node n, BaseTransform tx) {
+        NGNode pgn = getBaseNode(n);
+        return pgn.getContentBounds(new RectBounds(), tx);
+    }
+}
--- a/modules/graphics/src/test/java/com/sun/javafx/sg/prism/OcclusionCullingTest.java	Fri Jul 12 14:51:13 2013 +0200
+++ b/modules/graphics/src/test/java/com/sun/javafx/sg/prism/OcclusionCullingTest.java	Fri Jul 12 08:35:56 2013 -0700
@@ -27,7 +27,7 @@
 
 import com.sun.javafx.geom.RectBounds;
 import com.sun.javafx.geom.transform.BaseTransform;
-import com.sun.javafx.sg.NodePath;
+import com.sun.javafx.sg.prism.NodePath;
 import org.junit.Test;
 
 import static com.sun.javafx.sg.prism.NodeTestUtils.*;