1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.sg.prism;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import com.sun.javafx.geom.DirtyRegionContainer;
  32 import com.sun.javafx.geom.RectBounds;
  33 import com.sun.javafx.geom.Rectangle;
  34 import com.sun.javafx.geom.transform.BaseTransform;
  35 import com.sun.javafx.geom.transform.GeneralTransform3D;
  36 import com.sun.prism.Graphics;
  37 import com.sun.scenario.effect.Blend;
  38 import com.sun.scenario.effect.Blend.Mode;
  39 import com.sun.scenario.effect.FilterContext;
  40 import com.sun.scenario.effect.ImageData;
  41 import com.sun.scenario.effect.impl.prism.PrDrawable;
  42 import com.sun.scenario.effect.impl.prism.PrEffectHelper;
  43 
  44 /**
  45  */
  46 public class NGGroup extends NGNode {
  47     /**
  48      * The blend mode to use with this group.
  49      */
  50     private Blend.Mode blendMode = Blend.Mode.SRC_OVER;
  51     // NOTE I need a special array list here where all nodes added can have
  52     // their parent set correctly, and all nodes removed have it cleared correctly.
  53     // Actually, if a node is removed, I probably don't have to worry about
  54     // clearing it because as soon as it is added to another parent it will be set
  55     // and there is no magic listener foo going on here.
  56     private List<NGNode> children = new ArrayList<>(1);
  57     private List<NGNode> unmod = Collections.unmodifiableList(children);
  58     private List<NGNode> removed;
  59 
  60     /**
  61      * This mask has all bits that mark that a region intersects this group.
  62      * Which means it looks like this: 00010101010101010101010101010101 (first bit for sign)
  63      */
  64     private static final int REGION_INTERSECTS_MASK = 0x15555555;
  65 
  66     /***************************************************************************
  67      *                                                                         *
  68      * Implementation of the PGGroup interface                                 *
  69      *                                                                         *
  70      **************************************************************************/
  71 
  72     /**
  73      * Gets an unmodifiable list of the current children on this group
  74      */
  75     public List<NGNode> getChildren() { return unmod; }
  76 
  77     /**
  78      * Adds a node to the given index. An index of -1 means "append", for legacy
  79      * reasons (it was easier than asking for the number of children, iirc).
  80      * @param index -1, or <= node.size()
  81      * @param node
  82      */
  83     public void add(int index, NGNode node) {
  84         // Validate the arguments
  85         if ((index < -1) || (index > children.size())) {
  86             throw new IndexOutOfBoundsException("invalid index");
  87         }
  88 
  89         // NOTE: We used to do checks here to make sure that a node
  90         // being added didn't already have another parent listed as
  91         // its parent. Now we just silently accept them. The FX side
  92         // is already doing this check, and implementing this check
  93         // properly would require that the "clear" implementation visit
  94         // all nodes and clear this flag, which is really just wasted work.
  95         NGNode child = node;
  96 
  97         // When a new node is added, we need to make sure the new node has this
  98         // group registered as its parent. We also need to make sure I invalidate
  99         // this group's cache and mark it dirty. Note that we don't have to worry
 100         // about notifying the other parent that it has lost a node: the FX
 101         // scene graph will be sure to send a "remove" notification to the other
 102         // parent, so we don't have to be concerned with the other parent
 103         // having to be marked dirty or whatnot.
 104         child.setParent(this);
 105         childDirty = true;
 106         if (index == -1) {
 107             children.add(node);
 108         } else {
 109             children.add(index, node);
 110         }
 111         child.markDirty();
 112         markTreeDirtyNoIncrement();
 113         geometryChanged();
 114     }
 115 
 116     public void clearFrom(int fromIndex) {
 117         if (fromIndex < children.size()) {
 118             children.subList(fromIndex, children.size()).clear();
 119             geometryChanged();
 120             childDirty = true;
 121             markTreeDirtyNoIncrement();
 122         }
 123     }
 124 
 125     public List<NGNode> getRemovedChildren() {
 126         return removed;
 127     }
 128 
 129     public void addToRemoved(NGNode n) {
 130         if (removed == null) removed = new ArrayList<>();
 131         if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) {
 132             return;
 133         }
 134 
 135         removed.add(n);
 136         dirtyChildrenAccumulated++;
 137 
 138         if (dirtyChildrenAccumulated > DIRTY_CHILDREN_ACCUMULATED_THRESHOLD) {
 139             removed.clear(); //no need to store anything in this case
 140         }
 141     }
 142 
 143     @Override
 144     protected void clearDirty() {
 145         super.clearDirty();
 146         if (removed != null) removed.clear();
 147     }
 148 
 149     public void remove(NGNode node) {
 150         // We just remove the node and mark this group as being dirty. Really, if we
 151         // supported sub-regions within the group, we'd only have to mark the
 152         // sub-region that had been occupied by the node as dirty, but we do not
 153         // as yet have this optimization (mostly because we didn't have it in
 154         // Scenario, mostly because it was hard to optimize correctly).
 155         children.remove(node);
 156         geometryChanged();
 157         childDirty = true;
 158         markTreeDirtyNoIncrement();
 159     }
 160 
 161     public void remove(int index) {
 162         children.remove(index);
 163         geometryChanged();
 164         childDirty = true;
 165         markTreeDirtyNoIncrement();
 166     }
 167 
 168     public void clear() {
 169         children.clear();
 170         childDirty = false;
 171         geometryChanged();
 172         markTreeDirtyNoIncrement();
 173     }
 174 
 175     /**
 176      * Set by the FX scene graph.
 177      * @param blendMode cannot be null
 178      */
 179     public void setBlendMode(Object blendMode) {
 180         // Verify the arguments
 181         if (blendMode == null) {
 182             throw new IllegalArgumentException("Mode must be non-null");
 183         }
 184         // If the blend mode has changed, mark this node as dirty and
 185         // invalidate its cache
 186         if (this.blendMode != blendMode) {
 187             this.blendMode = (Blend.Mode)blendMode;
 188             visualsChanged();
 189         }
 190     }
 191 
 192     @Override
 193     public void renderForcedContent(Graphics gOptional) {
 194         if (children == null) {
 195             return;
 196         }
 197         for (int i = 0; i < children.size(); i++) {
 198             children.get(i).renderForcedContent(gOptional);
 199         }
 200     }
 201 
 202     @Override
 203     protected void renderContent(Graphics g) {
 204         if (children == null) {
 205             return;
 206         }
 207 
 208         NodePath renderRoot = g.getRenderRoot();
 209         int startPos = 0;
 210         if (renderRoot != null) {
 211             if (renderRoot.hasNext()) {
 212                 renderRoot.next();
 213                 startPos = children.indexOf(renderRoot.getCurrentNode());
 214 
 215                 for (int i = 0; i < startPos; ++i) {
 216                     children.get(i).clearDirtyTree();
 217                 }
 218             } else {
 219                 g.setRenderRoot(null);
 220             }
 221         }
 222 
 223         if (blendMode == Blend.Mode.SRC_OVER ||
 224                 children.size() < 2) {  // Blend modes only work "between" siblings
 225 
 226             for (int i = startPos; i < children.size(); i++) {
 227                 NGNode child;
 228                 try {
 229                     child = children.get(i);
 230                 } catch (Exception e) {
 231                     child = null;
 232                 }
 233                 // minimal protection against concurrent update of the list.
 234                 if (child != null) {
 235                     child.render(g);
 236                 }
 237             }
 238             return;
 239         }
 240 
 241         Blend b = new Blend(blendMode, null, null);
 242         FilterContext fctx = getFilterContext(g);
 243 
 244         ImageData bot = null;
 245         boolean idValid = true;
 246         do {
 247             // TODO: probably don't need to wrap the transform here... (RT-26981)
 248             BaseTransform transform = g.getTransformNoClone().copy();
 249             if (bot != null) {
 250                 bot.unref();
 251                 bot = null;
 252             }
 253             Rectangle rclip = PrEffectHelper.getGraphicsClipNoClone(g);
 254             for (int i = startPos; i < children.size(); i++) {
 255                 NGNode child = children.get(i);
 256                 ImageData top = NodeEffectInput.
 257                     getImageDataForNode(fctx, child, false, transform, rclip);
 258                 if (bot == null) {
 259                     bot = top;
 260                 } else {
 261                     ImageData newbot =
 262                         b.filterImageDatas(fctx, transform, rclip, null, bot, top);
 263                     bot.unref();
 264                     top.unref();
 265                     bot = newbot;
 266                 }
 267             }
 268             if (bot != null && (idValid = bot.validate(fctx))) {
 269                 Rectangle r = bot.getUntransformedBounds();
 270                 PrDrawable botimg = (PrDrawable)bot.getUntransformedImage();
 271                 g.setTransform(bot.getTransform());
 272                 g.drawTexture(botimg.getTextureObject(),
 273                         r.x, r.y, r.width, r.height);
 274             }
 275         } while (bot == null || !idValid);
 276 
 277         if (bot != null) {
 278             bot.unref();
 279         }
 280     }
 281 
 282     @Override
 283     protected boolean hasOverlappingContents() {
 284         if (blendMode != Mode.SRC_OVER) {
 285             // All other modes are flattened so there are no overlapping issues
 286             return false;
 287         }
 288         int n = (children == null ? 0 : children.size());
 289         if (n == 1) {
 290             return children.get(0).hasOverlappingContents();
 291         }
 292         return (n != 0);
 293     }
 294 
 295     public boolean isEmpty() {
 296         return children == null || children.isEmpty();
 297     }
 298 
 299     @Override
 300     protected boolean hasVisuals() {
 301         return false;
 302     }
 303 
 304 
 305     @Override
 306     protected boolean needsBlending() {
 307         Blend.Mode mode = getNodeBlendMode();
 308         // TODO: If children are all SRC_OVER then we can pass on SRC_OVER too
 309         // (RT-26981)
 310         return (mode != null);
 311     }
 312 
 313     /***************************************************************************
 314      *                                                                         *
 315      *                     Culling Related Methods                             *
 316      *                                                                         *
 317      **************************************************************************/
 318     @Override
 319     protected RenderRootResult computeRenderRoot(NodePath path, RectBounds dirtyRegion, int cullingIndex, BaseTransform tx,
 320                                        GeneralTransform3D pvTx) {
 321 
 322         // If the NGGroup is completely outside the culling area, then we don't have to traverse down
 323         // to the children yo.
 324         if (cullingIndex != -1) {
 325             final int bits = cullingBits >> (cullingIndex*2);
 326             if ((bits & DIRTY_REGION_CONTAINS_OR_INTERSECTS_NODE_BOUNDS) == 0) {
 327                 return RenderRootResult.NO_RENDER_ROOT;
 328             }
 329             if ((bits & DIRTY_REGION_CONTAINS_NODE_BOUNDS) != 0) {
 330                 cullingIndex = -1; // Do not check culling in children,
 331                                    // as culling bits are not set for fully interior groups
 332             }
 333         }
 334 
 335         if (!isVisible()) {
 336             return RenderRootResult.NO_RENDER_ROOT;
 337         }
 338 
 339         if (getOpacity() != 1.0 || (getEffect() != null && getEffect().reducesOpaquePixels()) || needsBlending()) {
 340             return RenderRootResult.NO_RENDER_ROOT;
 341         }
 342 
 343         if (getClipNode() != null) {
 344             final NGNode clip = getClipNode();
 345             RectBounds clipBounds = clip.getOpaqueRegion();
 346             if (clipBounds == null) {
 347                 return RenderRootResult.NO_RENDER_ROOT;
 348             }
 349             TEMP_TRANSFORM.deriveWithNewTransform(tx).deriveWithConcatenation(getTransform()).deriveWithConcatenation(clip.getTransform());
 350             if (!checkBoundsInQuad(clipBounds, dirtyRegion, TEMP_TRANSFORM, pvTx)) {
 351                 return RenderRootResult.NO_RENDER_ROOT;
 352             }
 353         }
 354 
 355         // An NGGroup itself never draws pixels, so we don't have to call super. Just visit
 356         // each child, starting with the top-most.
 357         double mxx = tx.getMxx();
 358         double mxy = tx.getMxy();
 359         double mxz = tx.getMxz();
 360         double mxt = tx.getMxt();
 361 
 362         double myx = tx.getMyx();
 363         double myy = tx.getMyy();
 364         double myz = tx.getMyz();
 365         double myt = tx.getMyt();
 366 
 367         double mzx = tx.getMzx();
 368         double mzy = tx.getMzy();
 369         double mzz = tx.getMzz();
 370         double mzt = tx.getMzt();
 371         final BaseTransform chTx = tx.deriveWithConcatenation(getTransform());
 372 
 373         // We need to keep a reference to the result of calling computeRenderRoot on each child
 374         RenderRootResult result = RenderRootResult.NO_RENDER_ROOT;
 375         // True if every child _after_ the the found render root is clean
 376         boolean followingChildrenClean = true;
 377         // Iterate over all children, looking for a render root.
 378         for (int resultIdx=children.size()-1; resultIdx>=0; resultIdx--) {
 379             // Get the render root result from the child
 380             final NGNode child = children.get(resultIdx);
 381             result = child.computeRenderRoot(path, dirtyRegion, cullingIndex, chTx, pvTx);
 382             // Update this flag, which if true means that this child and all subsequent children
 383             // of this group are all clean.
 384             followingChildrenClean &= child.isClean();
 385 
 386             if (result == RenderRootResult.HAS_RENDER_ROOT) {
 387                 // If we have a render root and it is dirty, then we don't really care whether
 388                 // followingChildrenClean is true or false, we just add this group to the
 389                 // path and we're done.
 390                 path.add(this);
 391                 break;
 392             } else if (result == RenderRootResult.HAS_RENDER_ROOT_AND_IS_CLEAN) {
 393                 path.add(this);
 394                 // If we have a result which is itself reporting that it is clean, but
 395                 // we have some following children which are dirty, then we need to
 396                 // switch the result for this Group to be HAS_RENDER_ROOT.
 397                 if (!followingChildrenClean) {
 398                     result = RenderRootResult.HAS_RENDER_ROOT;
 399                 }
 400                 break;
 401             }
 402         }
 403         // restore previous transform state
 404         tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt);
 405         return result;
 406     }
 407 
 408     @Override
 409     protected void markCullRegions(
 410             DirtyRegionContainer drc,
 411             int cullingRegionsBitsOfParent,
 412             BaseTransform tx,
 413             GeneralTransform3D pvTx) {
 414 
 415         //set culling bits for this group first.
 416         super.markCullRegions(drc, cullingRegionsBitsOfParent, tx, pvTx);
 417 
 418         //cullingRegionsBits == 0 group is outside all dirty regions
 419         // we can cull all children otherwise check children.
 420         // If none of the regions intersect this group, skip pre-culling
 421         if (cullingBits == -1 || (cullingBits != 0 && (cullingBits & REGION_INTERSECTS_MASK) != 0)) {
 422             //save current transform
 423             double mxx = tx.getMxx();
 424             double mxy = tx.getMxy();
 425             double mxz = tx.getMxz();
 426             double mxt = tx.getMxt();
 427 
 428             double myx = tx.getMyx();
 429             double myy = tx.getMyy();
 430             double myz = tx.getMyz();
 431             double myt = tx.getMyt();
 432 
 433             double mzx = tx.getMzx();
 434             double mzy = tx.getMzy();
 435             double mzz = tx.getMzz();
 436             double mzt = tx.getMzt();
 437             BaseTransform chTx = tx.deriveWithConcatenation(getTransform());
 438 
 439             NGNode child;
 440             for (int chldIdx = 0; chldIdx < children.size(); chldIdx++) {
 441                 child = children.get(chldIdx);
 442                 child.markCullRegions(
 443                         drc,
 444                         cullingBits,
 445                         chTx,
 446                         pvTx);
 447             }
 448             // restore previous transform state
 449             tx.restoreTransform(mxx, mxy, mxz, mxt, myx, myy, myz, myt, mzx, mzy, mzz, mzt);
 450         }
 451     }
 452 
 453     @Override
 454     public void drawDirtyOpts(final BaseTransform tx, final GeneralTransform3D pvTx,
 455                               Rectangle clipBounds, int[] countBuffer, int dirtyRegionIndex) {
 456         super.drawDirtyOpts(tx, pvTx, clipBounds, countBuffer, dirtyRegionIndex);
 457         // Not really efficient but this code is only executed during debug. This makes sure
 458         // that the source transform (tx) is not modified.
 459         BaseTransform clone = tx.copy();
 460         clone = clone.deriveWithConcatenation(getTransform());
 461         for (int childIndex = 0; childIndex < children.size(); childIndex++) {
 462             final NGNode child = children.get(childIndex);
 463             child.drawDirtyOpts(clone, pvTx, clipBounds, countBuffer, dirtyRegionIndex);
 464         }
 465     }
 466 
 467 }