All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.javafx.sg.BaseNode Maven / Gradle / Ivy

/*
 * 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.BoxBounds;
import com.sun.javafx.geom.DirtyRegionContainer;
import com.sun.javafx.geom.DirtyRegionPool;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.geom.RectBounds;
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.scenario.effect.Blend;
import com.sun.scenario.effect.Effect;
import com.sun.scenario.effect.FilterContext;
import com.sun.scenario.effect.ImageData;
import java.util.List;

import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;

/**
 * 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.
 * 

* 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). *

* 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. *

* 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 implements PGNode { 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 * @param transformed new transformed bounds, base on */ 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(PGNode 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 = (BaseNode)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, PGNode.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 */ protected 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 PGGroup) { List children = ((PGGroup)this).getChildren(); BaseBounds tmp = TEMP_BOUNDS; for (int i=0; i children = ((PGGroup) 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. *

* 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. *

* 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 removed = ((PGGroup) 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 children = ((PGGroup) this).getChildren(); int num = children.size(); for (int i=0; i





© 2015 - 2024 Weber Informatics LLC | Privacy Policy