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

com.sun.javafx.sg.prism.NGGroup Maven / Gradle / Ivy

The newest version!
/*
 * 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.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.javafx.sg.PGGroup;
import com.sun.javafx.sg.PGNode;
import com.sun.prism.Graphics;
import com.sun.scenario.effect.Blend;
import com.sun.scenario.effect.Blend.Mode;
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.PrEffectHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 */
public class NGGroup extends NGNode implements PGGroup {
    /**
     * The blend mode to use with this group.
     */
    private Blend.Mode blendMode = Blend.Mode.SRC_OVER;
    // NOTE I need a special array list here where all nodes added can have
    // their parent set correctly, and all nodes removed have it cleared correctly.
    // Actually, if a node is removed, I probably don't have to worry about
    // clearing it because as soon as it is added to another parent it will be set
    // and there is no magic listener foo going on here.
    private List children = new ArrayList(1);
    private List unmod = Collections.unmodifiableList(children);
    private List removed;
    
    /**
     * This mask has all bits that mark that a region intersects this group.
     * Which means it looks like this: 00010101010101010101010101010101 (first bit for sign)
     */
    private static final int REGION_INTERSECTS_MASK = 0x15555555;
    
    /***************************************************************************
     *                                                                         *
     * Implementation of the PGGroup interface                                 *
     *                                                                         *
     **************************************************************************/

    /**
     * Gets an unmodifiable list of the current children on this group
     */
    public List getChildren() { return unmod; }

    /**
     * Adds a node to the given index. An index of -1 means "append", for legacy
     * reasons (it was easier than asking for the number of children, iirc).
     * @param index -1, or <= node.size()
     * @param node
     */
    public void add(int index, PGNode node) {
        // Validate the arguments
        if ((index < -1) || (index > children.size())) {
            throw new IndexOutOfBoundsException("invalid index");
        }

        // We're going to do a little verification to make sure something
        // awful hasn't happened. Really, this is pretty superfluous and
        // 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;
        // 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
        // is already doing this check, and implementing this check
        // properly would require that the "clear" implementation visit
        // all nodes and clear this flag, which is really just wasted work.

        // When a new node is added, we need to make sure the new node has this
        // group registered as its parent. We also need to make sure I invalidate
        // this group's cache and mark it dirty. Note that we don't have to worry
        // about notifying the other parent that it has lost a node: the FX
        // scene graph will be sure to send a "remove" notification to the other
        // parent, so we don't have to be concerned with the other parent
        // having to be marked dirty or whatnot.
        child.setParent(this);
        childDirty = true;
        if (index == -1) {
            children.add(node);
        } else {
            children.add(index, node);
        }
        child.markDirty();
        markTreeDirtyNoIncrement();
        geometryChanged();
    }

    @Override public void clearFrom(int fromIndex) {
        if (fromIndex < children.size()) {
            children.subList(fromIndex, children.size()).clear();
            geometryChanged();
            childDirty = true;
            markTreeDirtyNoIncrement();
        }
    }

    public List getRemovedChildren() {
        return removed;
    }

    public void addToRemoved(PGNode n) {
        if (removed == null) removed = new ArrayList();
        if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) {
            return;
        }
        
        removed.add(n);
        dirtyChildrenAccumulated++;
        
        if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) {
            removed.clear(); //no need to store anything in this case
        }
    }

    @Override
    protected void clearDirty() {
        super.clearDirty();
        if (removed != null) removed.clear();
    }

    public void remove(PGNode node) {
        // We just remove the node and mark this group as being dirty. Really, if we
        // supported sub-regions within the group, we'd only have to mark the
        // sub-region that had been occupied by the node as dirty, but we do not
        // as yet have this optimization (mostly because we didn't have it in
        // Scenario, mostly because it was hard to optimize correctly).
        children.remove(node);
        geometryChanged();
        childDirty = true;
        markTreeDirtyNoIncrement();
    }

    public void remove(int index) {
        children.remove(index);
        geometryChanged();
        childDirty = true;
        markTreeDirtyNoIncrement();
    }

    public void clear() {
        children.clear();
        childDirty = false;
        geometryChanged();
        markTreeDirtyNoIncrement();
    }

    /**
     * Set by the FX scene graph.
     * @param blendMode cannot be null
     */
    public void setBlendMode(Object blendMode) {
        // Verify the arguments
        if (blendMode == null) {
            throw new IllegalArgumentException("Mode must be non-null");
        }
        // If the blend mode has changed, mark this node as dirty and
        // invalidate its cache
        if (this.blendMode != blendMode) {
            this.blendMode = (Blend.Mode)blendMode;
            visualsChanged();
        }
    }

    @Override
    protected void renderContent(Graphics g) {
        renderChildren(g, (NodePath)g.getRenderRoot());
    }
    
    private void renderChildren(Graphics g, NodePath renderRoot) {
        if (children == null) {
            return;
        }
        
        int startPos = 0;
        if (renderRoot != null) {
            startPos = children.indexOf(renderRoot.getCurrentNode());

            for (int i = 0; i < startPos; ++i) {
                ((NGNode)children.get(i)).clearDirtyTree();
            }
            if (renderRoot.hasNext()) {
                renderRoot.next();
            } else {
                g.setRenderRoot(null);
            }
        }

        if (blendMode == Blend.Mode.SRC_OVER ||
                children.size() < 2) {  // Blend modes only work "between" siblings

            for (int i = startPos; i < children.size(); i++) {
                NGNode child;
                try {
                    child = (NGNode)children.get(i);
                } catch (Exception e) {
                    child = null;
                }
                // minimal protection against concurrent update of the list.
                if (child != null) {
                    child.render(g);
                }
            }
            return;
        }

        Blend b = new Blend(blendMode, null, null);
        FilterContext fctx = getFilterContext(g);

        ImageData bot = null;
        boolean idValid = true;
        do {
            // TODO: probably don't need to wrap the transform here... (RT-26981)
            BaseTransform transform = g.getTransformNoClone().copy();
            if (bot != null) {
                bot.unref();
                bot = null;
            }
            Rectangle rclip = PrEffectHelper.getGraphicsClipNoClone(g);
            for (int i = startPos; i < children.size(); i++) {
                NGNode child = (NGNode)children.get(i);
                ImageData top = NodeEffectInput.
                    getImageDataForNode(fctx, child, false, transform, rclip);
                if (bot == null) {
                    bot = top;
                } else {
                    ImageData newbot =
                        b.filterImageDatas(fctx, transform, rclip, bot, top);
                    bot.unref();
                    top.unref();
                    bot = newbot;
                }
            }
            if (bot != null && (idValid = bot.validate(fctx))) {
                Rectangle r = bot.getUntransformedBounds();
                PrDrawable botimg = (PrDrawable)bot.getUntransformedImage();
                g.setTransform(bot.getTransform());
                g.drawTexture(botimg.getTextureObject(),
                        r.x, r.y, r.width, r.height);
            }
        } while (bot == null || !idValid);

        if (bot != null) {
            bot.unref();
        }
    }
    
    @Override
    protected boolean hasOverlappingContents() {
        if (blendMode != Mode.SRC_OVER) {
            // All other modes are flattened so there are no overlapping issues
            return false;
        }
        int n = (children == null ? 0 : children.size());
        if (n == 1) {
            return ((NGNode)children.get(0)).hasOverlappingContents();
        }
        return (n != 0);
    }

    public boolean isEmpty() {
        return children == null || children.isEmpty();
    }
    
    @Override
    protected boolean hasVisuals() {
        return false;
    }


    @Override
    protected boolean needsBlending() {
        Blend.Mode mode = getNodeBlendMode();
        // TODO: If children are all SRC_OVER then we can pass on SRC_OVER too
        // (RT-26981)
        return (mode != null);
    }

    /***************************************************************************
     *                                                                         *
     *                     Culling Related Methods                             *
     *                                                                         *
     **************************************************************************/
    @Override
    protected NodePath computeRenderRoot(NodePath path, RectBounds dirtyRegion, int cullingIndex, BaseTransform tx,
                                       GeneralTransform3D pvTx) {

        // If the NGGroup is completely outside the culling area, then we don't have to traverse down
        // to the children yo.
        if (cullingIndex != -1) {
            final int bits = cullingBits >> (cullingIndex*2);
            if ((bits & 0x11) == 0) {
                return null;
            }
            if ((bits & 0x10) != 0) {
                cullingIndex = -1; // Do not check culling in children, 
                                   // as culling bits are not set for fully interior groups
            }
        }
        
        if (!isVisible()) {
            return null;
        }

        if (getOpacity() != 1.0 || (getEffect() != null && getEffect().reducesOpaquePixels()) || needsBlending()) {
            return null;
        }

        if (getClipNode() != null) {
            final NGNode clip = (NGNode)getClipNode();
            RectBounds clipBounds = clip.computeOpaqueRegion(TEMP_RECT_BOUNDS);
            if (clipBounds == null) {
                return null;
            }
            TEMP_TRANSFORM.deriveWithNewTransform(tx).deriveWithConcatenation(getTransform()).deriveWithConcatenation(clip.getTransform());
            if (!checkBoundsInQuad(clipBounds, dirtyRegion, TEMP_TRANSFORM, pvTx)) {
                return null;
            }
        }


        // An NGGroup itself never draws pixels, so we don't have to call super. Just visit
        // each child, starting with the top-most.
        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();
        final BaseTransform chTx = tx.deriveWithConcatenation(getTransform());
        NodePath childPath = null;
        for (int i=children.size()-1; i>=0; i--) {
            final NGNode child = (NGNode) children.get(i);
            childPath = child.computeRenderRoot(path, dirtyRegion, cullingIndex, chTx, pvTx);
            if (childPath != null) break;
        }
        // restore previous transform state
        tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt);
        if (childPath != null) {
            childPath.add(this);
        }
        return childPath;
    }

    @Override
    protected void markCullRegions(
            DirtyRegionContainer drc,
            int cullingRegionsBitsOfParent,
            BaseTransform tx,
            GeneralTransform3D pvTx) {

        //set culling bits for this group first.
        super.markCullRegions(drc, cullingRegionsBitsOfParent, tx, pvTx);
        
        //cullingRegionsBits == 0 group is outside all dirty regions
        // we can cull all children otherwise check children.
        // If none of the regions intersect this group, skip pre-culling
        if (cullingBits == -1 || (cullingBits != 0 && (cullingBits & REGION_INTERSECTS_MASK) != 0)) {
            //save current transform
            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 chTx = tx.deriveWithConcatenation(getTransform());

            NGNode child;
            for (int chldIdx = 0; chldIdx < children.size(); chldIdx++) {
                child = (NGNode)children.get(chldIdx);
                child.markCullRegions(
                        drc,
                        cullingBits,
                        chTx,
                        pvTx);
            }
            // restore previous transform state
            tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt);
        }
    }

    @Override
    public void drawCullBits(Graphics g) {
        if (getParent() != null) {
            super.drawCullBits(g);
        }
        if (cullingBits != 0) {
            // save current transform state
            BaseTransform prevXform = g.getTransformNoClone();

            double mxx = prevXform.getMxx();
            double mxy = prevXform.getMxy();
            double mxz = prevXform.getMxz();
            double mxt = prevXform.getMxt();

            double myx = prevXform.getMyx();
            double myy = prevXform.getMyy();
            double myz = prevXform.getMyz();
            double myt = prevXform.getMyt();

            double mzx = prevXform.getMzx();
            double mzy = prevXform.getMzy();
            double mzz = prevXform.getMzz();
            double mzt = prevXform.getMzt();

            g.transform(getTransform());
            NGNode child;
            for (int chldIdx = 0; chldIdx < children.size(); chldIdx++) {
                child = (NGNode)children.get(chldIdx);
                child.drawCullBits(g);
            }
            // restore previous transform state
            g.setTransform3D(mxx, mxy, mxz, mxt,
                             myx, myy, myz, myt,
                             mzx, mzy, mzz, mzt);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy