commit 13687207bddb02b0bcbb378cb0b372fe8949567b
Author: leszek <leszek@koltunski.pl>
Date:   Thu Apr 13 00:55:04 2017 +0100

    Separate the Postprocessing Effects to their own DistortedEffectsPostprocess queue.
    This partly breaks Multiblur (to be debugged)

diff --git a/src/main/java/org/distorted/library/DistortedEffects.java b/src/main/java/org/distorted/library/DistortedEffects.java
index 3123cc9..2515a77 100644
--- a/src/main/java/org/distorted/library/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/DistortedEffects.java
@@ -44,7 +44,8 @@ import java.nio.FloatBuffer;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Class containing {@link EffectTypes#LENGTH} queues, each a class derived from EffectQueue.
+ * Class containing Matrix,Vertex and Fragment effect queues. Postprocessing queue is held in a separate
+ * class.
  * <p>
  * The queues hold actual effects to be applied to a given (DistortedTexture,MeshObject) combo.
  */
@@ -98,9 +99,8 @@ public class DistortedEffects
   private EffectQueueMatrix      mM;
   private EffectQueueFragment    mF;
   private EffectQueueVertex      mV;
-  private EffectQueuePostprocess mP;
 
-  private boolean matrixCloned, vertexCloned, fragmentCloned, postprocessCloned;
+  private boolean matrixCloned, vertexCloned, fragmentCloned;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -229,17 +229,6 @@ public class DistortedEffects
       mF = new EffectQueueFragment(mID);
       fragmentCloned = false;
       }
-
-    if( (flags & Distorted.CLONE_POSTPROCESS) != 0 )
-      {
-      mP = d.mP;
-      postprocessCloned = true;
-      }
-    else
-      {
-      mP = new EffectQueuePostprocess(mID);
-      postprocessCloned = false;
-      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -295,20 +284,6 @@ public class DistortedEffects
     GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  EffectQueuePostprocess getPostprocess()
-    {
-    return mP;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int postprocessPriv(long currTime, DistortedOutputSurface surface)
-    {
-    return mP.postprocess(currTime,surface);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void drawPriv(float halfW, float halfH, MeshObject mesh, DistortedOutputSurface surface, long currTime)
@@ -356,12 +331,10 @@ public class DistortedEffects
     if( !matrixCloned     ) mM.abortAll(false);
     if( !vertexCloned     ) mV.abortAll(false);
     if( !fragmentCloned   ) mF.abortAll(false);
-    if( !postprocessCloned) mP.abortAll(false);
 
     mM = null;
     mV = null;
     mF = null;
-    mP = null;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -410,6 +383,7 @@ public class DistortedEffects
 /**
  * Releases all resources. After this call, the queue should not be used anymore.
  */
+  @SuppressWarnings("unused")
   public synchronized void delete()
     {
     releasePriv();
@@ -433,12 +407,12 @@ public class DistortedEffects
  * 
  * @param el A class implementing the EffectListener interface that wants to get notifications.
  */
+  @SuppressWarnings("unused")
   public void registerForMessages(EffectListener el)
     {
     mV.registerForMessages(el);
     mF.registerForMessages(el);
     mM.registerForMessages(el);
-    mP.registerForMessages(el);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -447,12 +421,12 @@ public class DistortedEffects
  * 
  * @param el A class implementing the EffectListener interface that no longer wants to get notifications.
  */
+  @SuppressWarnings("unused")
   public void deregisterForMessages(EffectListener el)
     {
     mV.deregisterForMessages(el);
     mF.deregisterForMessages(el);
     mM.deregisterForMessages(el);
-    mP.deregisterForMessages(el);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -461,9 +435,9 @@ public class DistortedEffects
  * @return Number of effects aborted.
  */
   public int abortAllEffects()
-      {
-      return mM.abortAll(true) + mV.abortAll(true) + mF.abortAll(true) + mP.abortAll(true);
-      }
+    {
+    return mM.abortAll(true) + mV.abortAll(true) + mF.abortAll(true);
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
@@ -479,7 +453,6 @@ public class DistortedEffects
       case MATRIX     : return mM.abortAll(true);
       case VERTEX     : return mV.abortAll(true);
       case FRAGMENT   : return mF.abortAll(true);
-      case POSTPROCESS: return mP.abortAll(true);
       default         : return 0;
       }
     }
@@ -498,7 +471,6 @@ public class DistortedEffects
     if( type==EffectTypes.MATRIX.type      ) return mM.removeByID(id>>EffectTypes.LENGTH);
     if( type==EffectTypes.VERTEX.type      ) return mV.removeByID(id>>EffectTypes.LENGTH);
     if( type==EffectTypes.FRAGMENT.type    ) return mF.removeByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.POSTPROCESS.type ) return mP.removeByID(id>>EffectTypes.LENGTH);
 
     return 0;
     }
@@ -517,7 +489,6 @@ public class DistortedEffects
       case MATRIX     : return mM.removeByType(name);
       case VERTEX     : return mV.removeByType(name);
       case FRAGMENT   : return mF.removeByType(name);
-      case POSTPROCESS: return mP.removeByType(name);
       default         : return 0;
       }
     }
@@ -529,15 +500,14 @@ public class DistortedEffects
  * @param id Effect ID we want to print info about
  * @return <code>true</code> if a single Effect of type effectType has been found.
  */
-    
+  @SuppressWarnings("unused")
   public boolean printEffect(long id)
     {
     int type = (int)(id&EffectTypes.MASK);
 
-    if( type==EffectTypes.MATRIX.type      )  return mM.printByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.VERTEX.type      )  return mV.printByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.FRAGMENT.type    )  return mF.printByID(id>>EffectTypes.LENGTH);
-    if( type==EffectTypes.POSTPROCESS.type )  return mP.printByID(id>>EffectTypes.LENGTH);
+    if( type==EffectTypes.MATRIX.type  )  return mM.printByID(id>>EffectTypes.LENGTH);
+    if( type==EffectTypes.VERTEX.type  )  return mV.printByID(id>>EffectTypes.LENGTH);
+    if( type==EffectTypes.FRAGMENT.type)  return mF.printByID(id>>EffectTypes.LENGTH);
 
     return false;
     }
@@ -563,6 +533,7 @@ public class DistortedEffects
  *
  * @return The maximum number of Matrix effects
  */
+  @SuppressWarnings("unused")
   public static int getMaxMatrix()
     {
     return EffectQueue.getMax(EffectTypes.MATRIX.ordinal());
@@ -574,6 +545,7 @@ public class DistortedEffects
  *
  * @return The maximum number of Vertex effects
  */
+  @SuppressWarnings("unused")
   public static int getMaxVertex()
     {
     return EffectQueue.getMax(EffectTypes.VERTEX.ordinal());
@@ -585,22 +557,12 @@ public class DistortedEffects
  *
  * @return The maximum number of Fragment effects
  */
+  @SuppressWarnings("unused")
   public static int getMaxFragment()
     {
     return EffectQueue.getMax(EffectTypes.FRAGMENT.ordinal());
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the maximum number of Postprocess effects.
- *
- * @return The maximum number of Postprocess effects
- */
-  public static int getMaxPostprocess()
-    {
-    return EffectQueue.getMax(EffectTypes.POSTPROCESS.ordinal());
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Sets the maximum number of Matrix effects that can be stored in a single EffectQueue at one time.
@@ -617,6 +579,7 @@ public class DistortedEffects
  *            than Byte.MAX_VALUE
  * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
  */
+  @SuppressWarnings("unused")
   public static boolean setMaxMatrix(int max)
     {
     return EffectQueue.setMax(EffectTypes.MATRIX.ordinal(),max);
@@ -638,6 +601,7 @@ public class DistortedEffects
  *            than Byte.MAX_VALUE
  * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
  */
+  @SuppressWarnings("unused")
   public static boolean setMaxVertex(int max)
     {
     return EffectQueue.setMax(EffectTypes.VERTEX.ordinal(),max);
@@ -659,32 +623,12 @@ public class DistortedEffects
  *            than Byte.MAX_VALUE
  * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
  */
+  @SuppressWarnings("unused")
   public static boolean setMaxFragment(int max)
     {
     return EffectQueue.setMax(EffectTypes.FRAGMENT.ordinal(),max);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Sets the maximum number of Postprocess effects that can be stored in a single EffectQueue at one time.
- * This can fail if:
- * <ul>
- * <li>the value of 'max' is outside permitted range (0 &le; max &le; Byte.MAX_VALUE)
- * <li>We try to increase the value of 'max' when it is too late to do so already. It needs to be called
- *     before the Fragment Shader gets compiled, i.e. before the call to {@link Distorted#onCreate}. After this
- *     time only decreasing the value of 'max' is permitted.
- * <li>Furthermore, this needs to be called before any instances of the DistortedEffects class get created.
- * </ul>
- *
- * @param max new maximum number of simultaneous Postprocess Effects. Has to be a non-negative number not greater
- *            than Byte.MAX_VALUE
- * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
- */
-  public static boolean setMaxPostprocess(int max)
-    {
-    return EffectQueue.setMax(EffectTypes.POSTPROCESS.ordinal(),max);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Individual effect functions.
@@ -1126,19 +1070,4 @@ public class DistortedEffects
     {
     return mV.add(EffectNames.WAVE, wave, center, region);
     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Postprocess-based effects
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Blur the object.
- *
- * @param radius The 'strength' if the effect, in pixels. 0 = no blur, 10 = when blurring a given pixel,
- *               take into account 10 pixels in each direction.
- * @return ID of the effect added, or -1 if we failed to add one.
- */
-  public long blur(Data1D radius)
-    {
-    return mP.add(EffectNames.BLUR, radius);
-    }
   }
diff --git a/src/main/java/org/distorted/library/DistortedEffectsPostprocess.java b/src/main/java/org/distorted/library/DistortedEffectsPostprocess.java
new file mode 100644
index 0000000..3df9997
--- /dev/null
+++ b/src/main/java/org/distorted/library/DistortedEffectsPostprocess.java
@@ -0,0 +1,291 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted 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 for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library;
+
+import org.distorted.library.message.EffectListener;
+import org.distorted.library.type.Data1D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Class containing the queue of postprocessing effects.
+ * <p>
+ * This better be separate from the main DistortedEffects class, because we want to be able to apply
+ * the same postprocessing effects (i.e. not only the same effects, but the very same instance of this
+ * object) to several Children of a Node. Reason for that: if several children have the same Object,
+ * it is easy to group them into 'Buckets' where each bucket has the same postprocessing effects and
+ * is therefore rendered to the same buffer, which is then postprocessed in one go.
+ * <p>
+ * The queue holds actual effects to be applied to a given bucket of several (DistortedTexture,MeshObject) combos.
+ */
+public class DistortedEffectsPostprocess
+  {
+  private static long mNextID =0;
+  private long mID;
+
+  private EffectQueuePostprocess mP;
+
+  private boolean postprocessCloned;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void initializeEffectLists(DistortedEffectsPostprocess d, int flags)
+    {
+    if( (flags & Distorted.CLONE_POSTPROCESS) != 0 )
+      {
+      mP = d.mP;
+      postprocessCloned = true;
+      }
+    else
+      {
+      mP = new EffectQueuePostprocess(mID);
+      postprocessCloned = false;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  EffectQueuePostprocess getPostprocess()
+    {
+    return mP;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int postprocessPriv(long currTime, DistortedOutputSurface surface)
+    {
+    return mP.postprocess(currTime,surface);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void releasePriv()
+    {
+    if( !postprocessCloned) mP.abortAll(false);
+
+    mP = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create empty effect queue.
+ */
+  public DistortedEffectsPostprocess()
+    {
+    mID = ++mNextID;
+    initializeEffectLists(this,0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ * <p>
+ * Whatever we do not clone gets created just like in the default constructor.
+ *
+ * @param dc    Source object to create our object from
+ * @param flags A bitmask of values specifying what to copy.
+ *              Currently the only values possible are CLONE_NOTHING or CLONE_POSTPROCESS.
+ */
+  public DistortedEffectsPostprocess(DistortedEffectsPostprocess dc, int flags)
+    {
+    mID = ++mNextID;
+    initializeEffectLists(dc,flags);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Releases all resources. After this call, the queue should not be used anymore.
+ */
+  @SuppressWarnings("unused")
+  public synchronized void delete()
+    {
+    releasePriv();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns unique ID of this instance.
+ *
+ * @return ID of the object.
+ */
+  @SuppressWarnings("unused")
+  public long getID()
+      {
+      return mID;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds the calling class to the list of Listeners that get notified each time some event happens 
+ * to one of the Effects in the queues. Nothing will happen if 'el' is already in the list.
+ * 
+ * @param el A class implementing the EffectListener interface that wants to get notifications.
+ */
+  @SuppressWarnings("unused")
+  public void registerForMessages(EffectListener el)
+    {
+    mP.registerForMessages(el);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the calling class from the list of Listeners.
+ * 
+ * @param el A class implementing the EffectListener interface that no longer wants to get notifications.
+ */
+  @SuppressWarnings("unused")
+  public void deregisterForMessages(EffectListener el)
+    {
+    mP.deregisterForMessages(el);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts all Effects.
+ * @return Number of effects aborted.
+ */
+  @SuppressWarnings("unused")
+  public int abortAllEffects()
+      {
+      return mP.abortAll(true);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts all Effects of a given type (currently only POSTPROCESSING Effects).
+ * 
+ * @param type one of the constants defined in {@link EffectTypes}
+ * @return Number of effects aborted.
+ */
+  @SuppressWarnings("unused")
+  public int abortEffects(EffectTypes type)
+    {
+    switch(type)
+      {
+      case POSTPROCESS: return mP.abortAll(true);
+      default         : return 0;
+      }
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts a single Effect.
+ * 
+ * @param id ID of the Effect we want to abort.
+ * @return number of Effects aborted. Always either 0 or 1.
+ */
+  @SuppressWarnings("unused")
+  public int abortEffect(long id)
+    {
+    int type = (int)(id&EffectTypes.MASK);
+
+    if( type==EffectTypes.POSTPROCESS.type ) return mP.removeByID(id>>EffectTypes.LENGTH);
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Abort all Effects of a given name, for example all blurs.
+ * 
+ * @param name one of the constants defined in {@link EffectNames}
+ * @return number of Effects aborted.
+ */
+  @SuppressWarnings("unused")
+  public int abortEffects(EffectNames name)
+    {
+    switch(name.getType())
+      {
+      case POSTPROCESS: return mP.removeByType(name);
+      default         : return 0;
+      }
+    }
+    
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Print some info about a given Effect to Android's standard out. Used for debugging only.
+ * 
+ * @param id Effect ID we want to print info about
+ * @return <code>true</code> if a single Effect of type effectType has been found.
+ */
+  @SuppressWarnings("unused")
+  public boolean printEffect(long id)
+    {
+    int type = (int)(id&EffectTypes.MASK);
+
+    if( type==EffectTypes.POSTPROCESS.type )  return mP.printByID(id>>EffectTypes.LENGTH);
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the maximum number of Postprocess effects.
+ *
+ * @return The maximum number of Postprocess effects
+ */
+  @SuppressWarnings("unused")
+  public static int getMaxPostprocess()
+    {
+    return EffectQueue.getMax(EffectTypes.POSTPROCESS.ordinal());
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the maximum number of Postprocess effects that can be stored in a single EffectQueue at one time.
+ * This can fail if:
+ * <ul>
+ * <li>the value of 'max' is outside permitted range (0 &le; max &le; Byte.MAX_VALUE)
+ * <li>We try to increase the value of 'max' when it is too late to do so already. It needs to be called
+ *     before the Fragment Shader gets compiled, i.e. before the call to {@link Distorted#onCreate}. After this
+ *     time only decreasing the value of 'max' is permitted.
+ * <li>Furthermore, this needs to be called before any instances of the DistortedEffects class get created.
+ * </ul>
+ *
+ * @param max new maximum number of simultaneous Postprocess Effects. Has to be a non-negative number not greater
+ *            than Byte.MAX_VALUE
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  @SuppressWarnings("unused")
+  public static boolean setMaxPostprocess(int max)
+    {
+    return EffectQueue.setMax(EffectTypes.POSTPROCESS.ordinal(),max);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////   
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Individual effect functions.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Postprocess-based effects
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Blur the object.
+ *
+ * @param radius The 'strength' if the effect, in pixels. 0 = no blur, 10 = when blurring a given pixel,
+ *               take into account 10 pixels in each direction.
+ * @return ID of the effect added, or -1 if we failed to add one.
+ */
+  public long blur(Data1D radius)
+    {
+    return mP.add(EffectNames.BLUR, radius);
+    }
+  }
diff --git a/src/main/java/org/distorted/library/DistortedNode.java b/src/main/java/org/distorted/library/DistortedNode.java
index c50b3e7..ce9d8c4 100644
--- a/src/main/java/org/distorted/library/DistortedNode.java
+++ b/src/main/java/org/distorted/library/DistortedNode.java
@@ -43,6 +43,7 @@ public class DistortedNode implements DistortedAttacheable
   private DistortedNode mParent;
   private MeshObject mMesh;
   private DistortedEffects mEffects;
+  private DistortedEffectsPostprocess mPostprocess;
   private DistortedInputSurface mSurface;
   private DistortedRenderState mState;
   private NodeData mData;
@@ -211,18 +212,11 @@ public class DistortedNode implements DistortedAttacheable
     return 0;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int postprocess(long currTime, DistortedOutputSurface surface)
-    {
-    return mEffects.postprocessPriv(currTime,surface);
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   EffectQueuePostprocess getPostprocess()
     {
-    return mEffects.getPostprocess();
+    return mPostprocess==null ? null : mPostprocess.getPostprocess();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -269,6 +263,7 @@ public class DistortedNode implements DistortedAttacheable
     {
     mSurface       = surface;
     mEffects       = effects;
+    mPostprocess   = null;
     mMesh          = mesh;
     mState         = new DistortedRenderState();
     mChildren      = null;
@@ -305,10 +300,11 @@ public class DistortedNode implements DistortedAttacheable
  */
   public DistortedNode(DistortedNode node, int flags)
     {
-    mEffects= new DistortedEffects(node.mEffects,flags);
-    mMesh   = node.mMesh;
-    mState  = new DistortedRenderState();
-    mParent = null;
+    mEffects     = new DistortedEffects(node.mEffects,flags);
+    mPostprocess = null;
+    mMesh        = node.mMesh;
+    mState       = new DistortedRenderState();
+    mParent      = null;
 
     if( (flags & Distorted.CLONE_SURFACE) != 0 )
       {
@@ -521,6 +517,24 @@ public class DistortedNode implements DistortedAttacheable
       }
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the Postprocessing Effects we will apply to the temporary buffer this Node - and fellow siblings
+ * with the same Effects - will get rendered to.
+ * <p>
+ * For efficiency reasons, it is very important to assign the very same DistortedEffectsPostprocess
+ * object to all the DistortedNode siblings that are supposed to be postprocessed in the same way,
+ * because only then will the library assign all such siblings to the same 'Bucket' which gets rendered
+ * to the same offscreen buffer which then gets postprocessed in one go and subsequently merged to the
+ * target Surface.
+ */
+  public void setPostprocessEffects(DistortedEffectsPostprocess dep)
+    {
+    mPostprocess = dep;
+
+    // TODO: rearrange all the siblings so that all are sorted by the DistortedEffectsPostprocess' ID.
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Returns the DistortedEffects object that's in the Node.
@@ -564,6 +578,7 @@ public class DistortedNode implements DistortedAttacheable
  * @param b Write to the BLUE color channel when rendering this Node?
  * @param a Write to the ALPHA channel when rendering this Node?
  */
+  @SuppressWarnings("unused")
   public void glColorMask(boolean r, boolean g, boolean b, boolean a)
     {
     mState.glColorMask(r,g,b,a);
@@ -575,6 +590,7 @@ public class DistortedNode implements DistortedAttacheable
  *
  * @param mask Write to the Depth buffer when rendering this Node?
  */
+  @SuppressWarnings("unused")
   public void glDepthMask(boolean mask)
     {
     mState.glDepthMask(mask);
@@ -586,6 +602,7 @@ public class DistortedNode implements DistortedAttacheable
  *
  * @param mask Marks the bits of the Stencil buffer we will write to when rendering this Node.
  */
+  @SuppressWarnings("unused")
   public void glStencilMask(int mask)
     {
     mState.glStencilMask(mask);
@@ -597,6 +614,7 @@ public class DistortedNode implements DistortedAttacheable
  *
  * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
  */
+  @SuppressWarnings("unused")
   public void glEnable(int test)
     {
     mState.glEnable(test);
@@ -608,6 +626,7 @@ public class DistortedNode implements DistortedAttacheable
  *
  * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
  */
+  @SuppressWarnings("unused")
   public void glDisable(int test)
     {
     mState.glDisable(test);
@@ -621,6 +640,7 @@ public class DistortedNode implements DistortedAttacheable
  * @param ref  Reference valut to compare our stencil with.
  * @param mask Mask used when comparing.
  */
+  @SuppressWarnings("unused")
   public void glStencilFunc(int func, int ref, int mask)
     {
     mState.glStencilFunc(func,ref,mask);
@@ -636,6 +656,7 @@ public class DistortedNode implements DistortedAttacheable
  * @param dpfail What to do when Depth Test fails.
  * @param dppass What to do when Depth Test passes.
  */
+  @SuppressWarnings("unused")
   public void glStencilOp(int sfail, int dpfail, int dppass)
     {
     mState.glStencilOp(sfail,dpfail,dppass);
@@ -647,6 +668,7 @@ public class DistortedNode implements DistortedAttacheable
  *
  * @param func Valid values: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL
  */
+  @SuppressWarnings("unused")
   public void glDepthFunc(int func)
     {
     mState.glDepthFunc(func);
@@ -663,6 +685,7 @@ public class DistortedNode implements DistortedAttacheable
  * @param src Source Blend function
  * @param dst Destination Blend function
  */
+  @SuppressWarnings("unused")
   public void glBlendFunc(int src, int dst)
     {
     mState.glBlendFunc(src,dst);
diff --git a/src/main/java/org/distorted/library/DistortedOutputSurface.java b/src/main/java/org/distorted/library/DistortedOutputSurface.java
index 9957bf5..a4c0786 100644
--- a/src/main/java/org/distorted/library/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/DistortedOutputSurface.java
@@ -133,11 +133,19 @@ abstract class DistortedOutputSurface extends DistortedSurface implements Distor
       {
       child = children.get(i);
       currP = child.getPostprocess();
-      currB = currP.getBucket();
+      currB = currP==null ? 0 : currP.getBucket();
 
-      if( i>0 && currB!=lastB ) numRenders += lastP.postprocess(time,this);
+      if( lastB!=currB && lastB!=0 )
+        {
+        //android.util.Log.e("output", "i="+i+" postprocess and merge");
+        numRenders += lastP.postprocess(time,this);
+        }
 
-      if( currB==0 ) numRenders += child.draw(time,this);
+      if( currB==0 )
+        {
+        //android.util.Log.e("output", "i="+i+" draw to this");
+        numRenders += child.draw(time,this);
+        }
       else
         {
         if( mBuffer1==null )
@@ -146,8 +154,13 @@ abstract class DistortedOutputSurface extends DistortedSurface implements Distor
           mBuffer2 = new DistortedFramebuffer(false                     , DistortedSurface.TYPE_TREE, mWidth, mHeight);
           }
 
+        //android.util.Log.e("output", "i="+i+" draw to buffer");
         numRenders += child.draw(time,mBuffer1);
-        if( i==num-1 ) numRenders += currP.postprocess(time,this);
+        if( i==num-1 )
+          {
+          //android.util.Log.e("output", "i="+i+" postprocess and merge");
+          numRenders += currP.postprocess(time,this);
+          }
         }
 
       lastP = currP;
