commit b3618cb57e35dae40857aa79faa3c2b1165cb9a4
Author: Leszek Koltunski <leszek@distorted.org>
Date:   Thu Jun 9 02:14:04 2016 +0100

    Beginnings of support for PostShader effects (SavePNG, SaveMP4)

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index 874aef7..f1d9214 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -32,13 +32,13 @@ public class Distorted
    */
   public static final int CLONE_BITMAP  = 0x1;
   /**
-   * When creating an instance of a DistortedObject from another instance, clone the Matrix Effects.
+   * When creating an instance of a DistortedObject from another instance, clone the PreShader Effects.
    * <p>
    * This way we can have two different DistortedObjects with different Bitmaps behind them, both 
    * always displayed in exactly the same place on the screen. Applying any matrix-based effect to 
    * one of them automatically applies the effect to the other. Used in the copy constructor.
    */
-  public static final int CLONE_MATRIX  = 0x2;
+  public static final int CLONE_PRESHADER = 0x2;
   /**
    * When creating an instance of a DistortedObject from another instance, clone the Vertex Effects.
    * <p>
@@ -55,6 +55,11 @@ public class Distorted
    * applies the effect to the other. Used in the copy constructor.
    */
   public static final int CLONE_FRAGMENT= 0x8;
+
+  // Note: why no 'CLONE_POSTSHADER' constant? The only Postshader effects are the 'save to PNG file'
+  // 'save to MP4 file'. Sharing those effects across multiple DistortedObject objects does not make any
+  // sense - multiple Objects would then fight to get saved all to the same file.
+
   /**
    * When creating an instance of a DistortedNode from another instance, clone the children Nodes.
    * <p>
@@ -63,9 +68,9 @@ public class Distorted
    */
   public static final int CLONE_CHILDREN= 0x10;
   /**
-  * Constant used to represent a Matrix-based effect.
-  */
-  public static final int TYPE_MATR = 0x1;
+   * Constant used to represent a PreShader-based effect.
+   */
+  public static final int TYPE_PRE  = 0x1;
   /**
    * Constant used to represent a Vertex-based effect.
    */
@@ -74,7 +79,11 @@ public class Distorted
    * Constant used to represent a Fragment-based effect.
    */
   public static final int TYPE_FRAG = 0x4;
-   
+  /**
+   * Constant used to represent a PostShader-based effect.
+   */
+  public static final int TYPE_POST = 0x8;
+
   private static final String TAG = Distorted.class.getSimpleName();
   private static boolean mInitialized = false;
   
@@ -351,7 +360,7 @@ public class Distorted
     
     EffectListFragment.getUniforms(mProgramH);
     EffectListVertex.getUniforms(mProgramH);
-    EffectListMatrix.getUniforms(mProgramH);
+    EffectListPreShader.getUniforms(mProgramH);
     
     GLES20.glEnableVertexAttribArray(mPositionH);        
     GLES20.glEnableVertexAttribArray(mColorH);
@@ -388,7 +397,8 @@ public class Distorted
 
     EffectListVertex.reset();
     EffectListFragment.reset();
-    EffectListMatrix.reset();
+    EffectListPreShader.reset();  // no need to reset PostShader stuff
+
     EffectMessageSender.stopSending();
    
     mInitialized = false;
@@ -408,13 +418,13 @@ public class Distorted
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Returns the maximum number of Matrix effects.
+ * Returns the maximum number of PreShader effects.
  *    
- * @return The maximum number of Matrix effects
+ * @return The maximum number of PreShader effects
  */
-  public static int getMaxMatrix()
+  public static int getMaxPreShader()
     {
-    return EffectListMatrix.getMax();  
+    return EffectListPreShader.getMax();
     }
  
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -441,16 +451,16 @@ public class Distorted
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
- * Sets the maximum number of Matrix effects that can be applied to a single DistortedBitmap at one time.
+ * Sets the maximum number of PreShader effects that can be applied to a single DistortedBitmap at one time.
  * This can fail if the value of 'max' is outside permitted range. 
  * 
- * @param max new maximum number of simultaneous Matrix Effects. Has to be a non-negative number not greater
+ * @param max new maximum number of simultaneous PreShader 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 setMaxMatrix(int max)
+  public static boolean setMaxPreShader(int max)
     {
-    return EffectListMatrix.setMax(max);  
+    return EffectListPreShader.setMax(max);
     }
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/DistortedNode.java b/src/main/java/org/distorted/library/DistortedNode.java
index 0371908..8d5a57b 100644
--- a/src/main/java/org/distorted/library/DistortedNode.java
+++ b/src/main/java/org/distorted/library/DistortedNode.java
@@ -300,7 +300,7 @@ public class DistortedNode
  *     
  * @param node The DistortedNode to copy data from.
  * @param flags bit field composed of a subset of the following:
- *        {@link Distorted#CLONE_BITMAP},  {@link Distorted#CLONE_MATRIX}, {@link Distorted#CLONE_VERTEX},
+ *        {@link Distorted#CLONE_BITMAP},  {@link Distorted#CLONE_PRESHADER}, {@link Distorted#CLONE_VERTEX},
  *        {@link Distorted#CLONE_FRAGMENT} and {@link Distorted#CLONE_CHILDREN}.
  *        For example flags = CLONE_BITMAP | CLONE_CHILDREN.
  */
diff --git a/src/main/java/org/distorted/library/DistortedObject.java b/src/main/java/org/distorted/library/DistortedObject.java
index e5332bd..db636d6 100644
--- a/src/main/java/org/distorted/library/DistortedObject.java
+++ b/src/main/java/org/distorted/library/DistortedObject.java
@@ -10,13 +10,14 @@ import android.opengl.GLUtils;
  */
 public abstract class DistortedObject 
 { 
-    static final int TYPE_NUM = 3;
+    static final int TYPE_NUM = 4;
     private static final int TYPE_MASK= (1<<TYPE_NUM)-1;
     private static float[] mViewMatrix = new float[16];
    
-    protected EffectListMatrix   mM;
-    protected EffectListFragment mF;
-    protected EffectListVertex   mV;
+    protected EffectListPreShader  mM;
+    protected EffectListFragment   mF;
+    protected EffectListVertex     mV;
+    protected EffectListPostShader mP;
 
     protected boolean matrixCloned, vertexCloned, fragmentCloned;
  
@@ -54,14 +55,14 @@ public abstract class DistortedObject
     
     protected void initializeEffectLists(DistortedObject d, int flags)
       {
-      if( (flags & Distorted.CLONE_MATRIX) != 0 )
+      if( (flags & Distorted.CLONE_PRESHADER) != 0 )
         {
         mM = d.mM;
         matrixCloned = true;
         } 
       else
         {
-        mM = new EffectListMatrix(d);
+        mM = new EffectListPreShader(d);
         matrixCloned = false;  
         }
     
@@ -86,6 +87,8 @@ public abstract class DistortedObject
         mF = new EffectListFragment(d);
         fragmentCloned = false;   
         }
+
+      mP = new EffectListPostShader(d); // PostShader effects are never cloned.
       }
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -130,6 +133,8 @@ public abstract class DistortedObject
       mF.send();
        
       mGrid.draw();
+
+      mP.send();
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -151,11 +156,14 @@ public abstract class DistortedObject
       if( vertexCloned  ==false) mV.abortAll();
       if( fragmentCloned==false) mF.abortAll();
 
+      mP.abortAll();
+
       mBmp          = null;
       mGrid         = null;
       mM            = null;
       mV            = null;
       mF            = null;
+      mP            = null;
       mTextureDataH = null;
       }
  
@@ -184,7 +192,7 @@ public abstract class DistortedObject
    *
    * @param dc    Source object to create our object from
    * @param flags A bitmask of values specifying what to copy.
-   *              For example, CLONE_BITMAP | CLONE_MATRIX.
+   *              For example, CLONE_BITMAP | CLONE_PRESHADER.
    */
     public DistortedObject(DistortedObject dc, int flags)
       {
@@ -283,6 +291,7 @@ public abstract class DistortedObject
      mV.addListener(el);
      mF.addListener(el);
      mM.addListener(el);
+     mP.addListener(el);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -296,6 +305,7 @@ public abstract class DistortedObject
      mV.removeListener(el);
      mF.removeListener(el);
      mM.removeListener(el);
+     mP.removeListener(el);
      }
    
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -351,19 +361,21 @@ public abstract class DistortedObject
       mM.abortAll();
       mV.abortAll();
       mF.abortAll();
+      mP.abortAll();
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Aborts a subset of Effects.
  * 
- * @param mask Bitmask of the types of effects we want to abort, e.g. TYPE_MATR | TYPE_VERT | TYPE_FRAG.
+ * @param mask Bitmask of the types of effects we want to abort, e.g. TYPE_PRE | TYPE_VERT | TYPE_FRAG.
  */
     public void abortAllEffects(int mask)
       {
-      if( (mask & Distorted.TYPE_MATR) != 0 ) mM.abortAll();
+      if( (mask & Distorted.TYPE_PRE ) != 0 ) mM.abortAll();
       if( (mask & Distorted.TYPE_VERT) != 0 ) mV.abortAll();
       if( (mask & Distorted.TYPE_FRAG) != 0 ) mF.abortAll();
+      if( (mask & Distorted.TYPE_POST) != 0 ) mP.abortAll();
       }
     
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -377,9 +389,10 @@ public abstract class DistortedObject
       {
       switch( (int)(id&TYPE_MASK) )
         {
-        case Distorted.TYPE_MATR: return mM.removeByID(id>>TYPE_NUM);
+        case Distorted.TYPE_PRE : return mM.removeByID(id>>TYPE_NUM);
         case Distorted.TYPE_VERT: return mV.removeByID(id>>TYPE_NUM);
         case Distorted.TYPE_FRAG: return mF.removeByID(id>>TYPE_NUM);
+        case Distorted.TYPE_POST: return mP.removeByID(id>>TYPE_NUM);
         default                 : return false;
         }
       }
@@ -395,9 +408,10 @@ public abstract class DistortedObject
       {
       switch(effectType.getType())
         {
-        case Distorted.TYPE_MATR: return mM.removeByType(effectType);
+        case Distorted.TYPE_PRE : return mM.removeByType(effectType);
         case Distorted.TYPE_VERT: return mV.removeByType(effectType);
         case Distorted.TYPE_FRAG: return mF.removeByType(effectType);
+        case Distorted.TYPE_POST: return mP.removeByType(effectType);
         default                 : return false;
         }
       }
@@ -414,9 +428,10 @@ public abstract class DistortedObject
       {
       switch( (int)(id&TYPE_MASK) )
         {
-        case Distorted.TYPE_MATR: return mM.printByID(id>>TYPE_NUM);
+        case Distorted.TYPE_PRE : return mM.printByID(id>>TYPE_NUM);
         case Distorted.TYPE_VERT: return mV.printByID(id>>TYPE_NUM);
         case Distorted.TYPE_FRAG: return mF.printByID(id>>TYPE_NUM);
+        case Distorted.TYPE_POST: return mP.printByID(id>>TYPE_NUM);
         default                 : return false;
         }
       }
diff --git a/src/main/java/org/distorted/library/EffectList.java b/src/main/java/org/distorted/library/EffectList.java
index d45be81..5a23ad1 100644
--- a/src/main/java/org/distorted/library/EffectList.java
+++ b/src/main/java/org/distorted/library/EffectList.java
@@ -8,10 +8,10 @@ abstract class EffectList
   {
   protected static final int DEFAULT_NUM_EFFECTS = 5;
   
-  protected static final int MATRIX  =0;
-  protected static final int VERTEX  =1;
-  protected static final int FRAGMENT=2;
-  
+  protected static final int PRESHADER =0;
+  protected static final int VERTEX    =1;
+  protected static final int FRAGMENT  =2;
+
   protected byte mNumEffects;   // number of effects at the moment
   protected long mTotalEffects; // total number of effects ever created
   
@@ -37,7 +37,7 @@ abstract class EffectList
   
   static
     {
-    mMax[MATRIX]   = DEFAULT_NUM_EFFECTS;
+    mMax[PRESHADER]= DEFAULT_NUM_EFFECTS;
     mMax[VERTEX]   = DEFAULT_NUM_EFFECTS;
     mMax[FRAGMENT] = DEFAULT_NUM_EFFECTS;
     }
@@ -84,7 +84,7 @@ abstract class EffectList
 
   void addListener(EffectListener el)
     {
-    if( mNumListeners==0 ) mListeners = new Vector<>(2,2);
+    if( mListeners==null ) mListeners = new Vector<>(2,2);
    
     mListeners.add(el);
     mNumListeners++;
@@ -105,7 +105,7 @@ abstract class EffectList
 
   static void reset()
     {
-    mMax[MATRIX]   = DEFAULT_NUM_EFFECTS;
+    mMax[PRESHADER]= DEFAULT_NUM_EFFECTS;
     mMax[VERTEX]   = DEFAULT_NUM_EFFECTS;
     mMax[FRAGMENT] = DEFAULT_NUM_EFFECTS;
    
diff --git a/src/main/java/org/distorted/library/EffectListFragment.java b/src/main/java/org/distorted/library/EffectListFragment.java
index 4f72ea7..6832f60 100644
--- a/src/main/java/org/distorted/library/EffectListFragment.java
+++ b/src/main/java/org/distorted/library/EffectListFragment.java
@@ -14,9 +14,9 @@ class EffectListFragment extends EffectList
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
    
-  public EffectListFragment(DistortedObject bmp) 
+  public EffectListFragment(DistortedObject obj)
     { 
-    super(bmp,NUM_UNIFORMS,FRAGMENT);
+    super(obj,NUM_UNIFORMS,FRAGMENT);
    
     if( mMax[FRAGMENT]>0 )
       {
diff --git a/src/main/java/org/distorted/library/EffectListMatrix.java b/src/main/java/org/distorted/library/EffectListMatrix.java
deleted file mode 100644
index 8f1eec3..0000000
--- a/src/main/java/org/distorted/library/EffectListMatrix.java
+++ /dev/null
@@ -1,345 +0,0 @@
-package org.distorted.library;
-
-import android.opengl.GLES20;
-import android.opengl.Matrix;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectListMatrix extends EffectList
-  {   
-  private static final int NUM_UNIFORMS = 7; 
-  private static float[] mMVPMatrix= new float[16];
-  private static float[] mTmpMatrix= new float[16];
-  
-  private static int mBmpDH;      // This is a handle to half a bitmap dimensions
-  private static int mDepthH;     // Handle to the max Depth, i.e (farplane-nearplane)/2
-  private static int mMVPMatrixH; // pass in the transformation matrix
-  private static int mMVMatrixH;  // pass in the modelview matrix.
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  public EffectListMatrix(DistortedObject bmp) 
-    { 
-    super(bmp,NUM_UNIFORMS,MATRIX);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void multiplyByQuat(float[] matrix, float X, float Y, float Z, float W)
-    {
-    float xx= X * X;
-    float xy= X * Y;
-    float xz= X * Z;
-    float xw= X * W;
-    float yy= Y * Y;
-    float yz= Y * Z;
-    float yw= Y * W;
-    float zz= Z * Z;
-    float zw= Z * W;
-
-    mTmpMatrix[0]  = 1 - 2 * ( yy + zz );
-    mTmpMatrix[1]  =     2 * ( xy - zw );
-    mTmpMatrix[2]  =     2 * ( xz + yw );
-    mTmpMatrix[4]  =     2 * ( xy + zw );
-    mTmpMatrix[5]  = 1 - 2 * ( xx + zz );
-    mTmpMatrix[6]  =     2 * ( yz - xw );
-    mTmpMatrix[8]  =     2 * ( xz - yw );
-    mTmpMatrix[9]  =     2 * ( yz + xw );
-    mTmpMatrix[10] = 1 - 2 * ( xx + yy );
-    mTmpMatrix[3]  = mTmpMatrix[7] = mTmpMatrix[11] = mTmpMatrix[12] = mTmpMatrix[13] = mTmpMatrix[14] = 0;
-    mTmpMatrix[15] = 1;
-    
-    Matrix.multiplyMM(mMVPMatrix, 0, matrix, 0, mTmpMatrix, 0);  
-    for(int j=0; j<16; j++) matrix[j] = mMVPMatrix[j];   
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Only max Byte.MAX_VALUE concurrent effects per bitmap.
-// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
-  
-  static boolean setMax(int m)
-    {
-    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[MATRIX] ) 
-      {
-           if( m<0              ) m = 0;
-      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
-      
-      mMax[MATRIX] = m;
-      return true;
-      }
-   
-    return false;
-    }
- 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static int getMax()
-    {
-    return mMax[MATRIX];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void getUniforms(int mProgramH)
-    {
-    mBmpDH     = GLES20.glGetUniformLocation(mProgramH, "u_bmpD");
-    mDepthH    = GLES20.glGetUniformLocation(mProgramH, "u_Depth");
-    mMVPMatrixH= GLES20.glGetUniformLocation(mProgramH, "u_MVPMatrix");
-    mMVMatrixH = GLES20.glGetUniformLocation(mProgramH, "u_MVMatrix"); 
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized void compute(long currTime) 
-    {
-    if( currTime==mTime ) return;
-    if( mTime==0 ) mTime = currTime;
-    long step = (currTime-mTime);
-   
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mInterI[i]==null ) continue;    
-           
-      if( mInterP[i]!=null ) 
-        {
-        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i, mCurrentDuration[i]);
-        }
-        
-      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i+3, mCurrentDuration[i], step) )      
-        {   
-        for(int j=0; j<mNumListeners; j++)   
-          EffectMessageSender.newMessage( mListeners.elementAt(j),
-                                          EffectMessage.EFFECT_FINISHED, 
-                                         (mID[i]<<DistortedObject.TYPE_NUM)+Distorted.TYPE_MATR, 
-                                          mType[i], 
-                                          mBitmapID); 
-       
-        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i+3) )
-          {  
-          remove(i);
-          i--;
-          continue;
-          }
-        }
-    
-      mCurrentDuration[i] += step;
-      }
-     
-    mTime = currTime;  
-    }  
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  protected void moveEffect(int index)
-    {
-    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
-    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
-    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
-    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
-    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
-    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
-    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// here construct the ModelView Matrix
-
-  synchronized void send(float[] viewMatrix, DistortedProjection dp) 
-    {
-    Matrix.setIdentityM(viewMatrix, 0);
-    Matrix.translateM(viewMatrix, 0, -dp.width/2, dp.height/2, -dp.distance);
-    
-    float x,y,z, sx,sy,sz=1.0f;
-   
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if (mType[i] == EffectNames.ROTATE.ordinal() )
-        {
-        x = mUniforms[NUM_UNIFORMS*i  ];
-        y = mUniforms[NUM_UNIFORMS*i+1];
-        z = mUniforms[NUM_UNIFORMS*i+2];
-     
-        Matrix.translateM(viewMatrix, 0, x,-y, z); 
-        Matrix.rotateM( viewMatrix, 0, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);  
-        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
-        }
-      else if(mType[i] == EffectNames.QUATERNION.ordinal() )
-        {
-        x = mUniforms[NUM_UNIFORMS*i  ];
-        y = mUniforms[NUM_UNIFORMS*i+1];
-        z = mUniforms[NUM_UNIFORMS*i+2];
-     	
-        Matrix.translateM(viewMatrix, 0, x,-y, z); 
-        multiplyByQuat(viewMatrix, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);
-        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
-        }
-      else if(mType[i] == EffectNames.MOVE.ordinal() )
-        {
-        sx = mUniforms[NUM_UNIFORMS*i+3];   
-        sy = mUniforms[NUM_UNIFORMS*i+4];   
-        sz = mUniforms[NUM_UNIFORMS*i+5];   
-        
-        Matrix.translateM(viewMatrix, 0, sx,-sy, sz);   
-        }
-      else if(mType[i] == EffectNames.SCALE.ordinal() )
-        {
-        sx = mUniforms[NUM_UNIFORMS*i+3];   
-        sy = mUniforms[NUM_UNIFORMS*i+4];   
-        sz = mUniforms[NUM_UNIFORMS*i+5];   
-
-        Matrix.scaleM(viewMatrix, 0, sx, sy, sz);  
-        }
-      else if(mType[i] == EffectNames.SHEAR.ordinal() )
-        {
-        x  = mUniforms[NUM_UNIFORMS*i  ];
-        y  = mUniforms[NUM_UNIFORMS*i+1];
-        z  = mUniforms[NUM_UNIFORMS*i+2];
-        
-        sx = mUniforms[NUM_UNIFORMS*i+3];   
-        sy = mUniforms[NUM_UNIFORMS*i+4];   
-        sz = mUniforms[NUM_UNIFORMS*i+5];   
-        
-        Matrix.translateM(viewMatrix, 0, x,-y, z); 
-      
-        viewMatrix[4] += sx*viewMatrix[0]; // Multiply viewMatrix by 1 x 0 0 , i.e. X-shear. TODO: change this so it is symmetric w respect to all the axis.
-        viewMatrix[5] += sx*viewMatrix[1]; //                        0 1 0 0 
-        viewMatrix[6] += sx*viewMatrix[2]; //                        0 0 1 0
-        viewMatrix[7] += sx*viewMatrix[3]; //                        0 0 0 1
-      
-        viewMatrix[0] += sy*viewMatrix[4]; // Multiply viewMatrix by 1 0 0 0 , i.e. Y-shear. TODO: change this so it is symmetric w respect to all the axis.
-        viewMatrix[1] += sy*viewMatrix[5]; //                        y 1 0 0
-        viewMatrix[2] += sy*viewMatrix[6]; //                        0 0 1 0
-        viewMatrix[3] += sy*viewMatrix[7]; //                        0 0 0 1      
-      
-        // TODO: implement Z-shear.
-        
-        Matrix.translateM(viewMatrix, 0,-x, y, -z);
-        }
-      }
-   
-    Matrix.translateM(viewMatrix, 0, mObjHalfX,-mObjHalfY, -mObjHalfZ);
-    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, viewMatrix, 0);
-    
-    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
-    GLES20.glUniform1f( mDepthH, dp.depth);   
-    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, viewMatrix, 0);
-    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// here construct the ModelView Matrix, but without any effects
-
-  synchronized void sendNoEffects(DistortedProjection dp) 
-    {
-    Matrix.setIdentityM(mTmpMatrix, 0);
-    Matrix.translateM(mTmpMatrix, 0, mObjHalfX-dp.width/2, dp.height/2-mObjHalfY, mObjHalfZ-dp.distance);
-    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, mTmpMatrix, 0);
-    
-    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
-    GLES20.glUniform1f( mDepthH, dp.depth);  
-    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, mTmpMatrix, 0);
-    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator i)
-    {
-    if( mMax[MATRIX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = p;
-      mInterI[mNumEffects] = i;
-      
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator i)
-    {
-    if( mMax[MATRIX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = null;
-      mInterI[mNumEffects] = i;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
-            
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator1D i, float aX, float aY, float aZ)
-    {
-    if( mMax[MATRIX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = null;
-      mInterI[mNumEffects] = i;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
-      
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator1D i, float aX, float aY, float aZ)
-    {
-    if( mMax[MATRIX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = p;
-      mInterI[mNumEffects] = i;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
-      
-      return addBase(eln);
-      }
-      
-    return -1;
-    }
-  
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  synchronized long add(EffectNames eln, float x, float y, float z, float aA, float aX, float aY, float aZ)
-    {
-    if( mMax[MATRIX]>mNumEffects )
-      {
-      mInterP[mNumEffects] = null; 
-      mInterI[mNumEffects] = null;
-      
-      mUniforms[NUM_UNIFORMS*mNumEffects  ] =  x;
-      mUniforms[NUM_UNIFORMS*mNumEffects+1] =  y;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+2] =  z;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+3] = aA;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
-      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
-      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
-      
-      return addBase(eln);   
-      }
-      
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// end of VertexEffect  
-  }
diff --git a/src/main/java/org/distorted/library/EffectListPostShader.java b/src/main/java/org/distorted/library/EffectListPostShader.java
new file mode 100644
index 0000000..5dbaad0
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectListPostShader.java
@@ -0,0 +1,97 @@
+package org.distorted.library;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import java.util.Vector;
+
+/**
+ * Do NOT base this on EffectList - this is an entirely different animal than the first 3 EffectLists.
+ * The Effects in here will be executed after all the shaders have been executed - thus there are no
+ * uniforms to send, no real queues to maintain.
+ * <p>
+ * Only 2 effects here ATM:
+ * - save current Surface to a PNG file
+ * - save current animation to a .MP4 file
+ *
+ * In contrast to the other EffectLists, only one instance of each allowed at any given moment - thus
+ * this is not even a real EffectList, it is named so only for consistency with the others.
+ */
+public class EffectListPostShader
+  {
+  private Vector<EffectListener> mListeners =null;
+  private int mNumListeners=0;  // ==mListeners.length(), but we only create mListeners if the first one gets added
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public EffectListPostShader(DistortedObject obj)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void send()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void abortAll()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void addListener(EffectListener el)
+    {
+    if( mListeners==null ) mListeners = new Vector<>(2,2);
+
+    mListeners.add(el);
+    mNumListeners++;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeListener(EffectListener el)
+    {
+    if( mNumListeners>0 )
+      {
+      mListeners.remove(el);
+      mNumListeners--;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByID(long id)
+    {
+    //....
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized boolean removeByType(EffectNames effect)
+    {
+    // ...
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected boolean printByID(long id)
+    {
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized long add(EffectNames eln, String filename)
+    {
+    return 0;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/EffectListPreShader.java b/src/main/java/org/distorted/library/EffectListPreShader.java
new file mode 100644
index 0000000..76907da
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectListPreShader.java
@@ -0,0 +1,344 @@
+package org.distorted.library;
+
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectListPreShader extends EffectList
+  {   
+  private static final int NUM_UNIFORMS = 7; 
+  private static float[] mMVPMatrix= new float[16];
+  private static float[] mTmpMatrix= new float[16];
+  
+  private static int mBmpDH;      // This is a handle to half a bitmap dimensions
+  private static int mDepthH;     // Handle to the max Depth, i.e (farplane-nearplane)/2
+  private static int mMVPMatrixH; // pass in the transformation matrix
+  private static int mMVMatrixH;  // pass in the modelview matrix.
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectListPreShader(DistortedObject obj)
+    { 
+    super(obj,NUM_UNIFORMS, PRESHADER);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void multiplyByQuat(float[] matrix, float X, float Y, float Z, float W)
+    {
+    float xx= X * X;
+    float xy= X * Y;
+    float xz= X * Z;
+    float xw= X * W;
+    float yy= Y * Y;
+    float yz= Y * Z;
+    float yw= Y * W;
+    float zz= Z * Z;
+    float zw= Z * W;
+
+    mTmpMatrix[0]  = 1 - 2 * ( yy + zz );
+    mTmpMatrix[1]  =     2 * ( xy - zw );
+    mTmpMatrix[2]  =     2 * ( xz + yw );
+    mTmpMatrix[4]  =     2 * ( xy + zw );
+    mTmpMatrix[5]  = 1 - 2 * ( xx + zz );
+    mTmpMatrix[6]  =     2 * ( yz - xw );
+    mTmpMatrix[8]  =     2 * ( xz - yw );
+    mTmpMatrix[9]  =     2 * ( yz + xw );
+    mTmpMatrix[10] = 1 - 2 * ( xx + yy );
+    mTmpMatrix[3]  = mTmpMatrix[7] = mTmpMatrix[11] = mTmpMatrix[12] = mTmpMatrix[13] = mTmpMatrix[14] = 0;
+    mTmpMatrix[15] = 1;
+    
+    Matrix.multiplyMM(mMVPMatrix, 0, matrix, 0, mTmpMatrix, 0);  
+    for(int j=0; j<16; j++) matrix[j] = mMVPMatrix[j];   
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Only max Byte.MAX_VALUE concurrent effects per bitmap.
+// If you want more, change type of the mNumEffects, mIDIndex and mFreeIndexes variables to shorts.
+  
+  static boolean setMax(int m)
+    {
+    if( (mCreated==false && !Distorted.isInitialized()) || m<=mMax[PRESHADER] )
+      {
+           if( m<0              ) m = 0;
+      else if( m>Byte.MAX_VALUE ) m = Byte.MAX_VALUE;
+      
+      mMax[PRESHADER] = m;
+      return true;
+      }
+   
+    return false;
+    }
+ 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getMax()
+    {
+    return mMax[PRESHADER];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mBmpDH     = GLES20.glGetUniformLocation(mProgramH, "u_bmpD");
+    mDepthH    = GLES20.glGetUniformLocation(mProgramH, "u_Depth");
+    mMVPMatrixH= GLES20.glGetUniformLocation(mProgramH, "u_MVPMatrix");
+    mMVMatrixH = GLES20.glGetUniformLocation(mProgramH, "u_MVMatrix"); 
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized void compute(long currTime) 
+    {
+    if( currTime==mTime ) return;
+    if( mTime==0 ) mTime = currTime;
+    long step = (currTime-mTime);
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mInterI[i]==null ) continue;    
+           
+      if( mInterP[i]!=null ) 
+        {
+        mInterP[i].interpolateMain(mUniforms, NUM_UNIFORMS*i, mCurrentDuration[i]);
+        }
+        
+      if( mInterI[i].interpolateMain(mUniforms ,NUM_UNIFORMS*i+3, mCurrentDuration[i], step) )      
+        {   
+        for(int j=0; j<mNumListeners; j++)   
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED, 
+                                         (mID[i]<<DistortedObject.TYPE_NUM)+Distorted.TYPE_PRE,
+                                          mType[i], 
+                                          mBitmapID); 
+       
+        if( EffectNames.isUnity(mType[i], mUniforms, NUM_UNIFORMS*i+3) )
+          {  
+          remove(i);
+          i--;
+          continue;
+          }
+        }
+    
+      mCurrentDuration[i] += step;
+      }
+     
+    mTime = currTime;  
+    }  
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  protected void moveEffect(int index)
+    {
+    mUniforms[NUM_UNIFORMS*index  ] = mUniforms[NUM_UNIFORMS*(index+1)  ];
+    mUniforms[NUM_UNIFORMS*index+1] = mUniforms[NUM_UNIFORMS*(index+1)+1];
+    mUniforms[NUM_UNIFORMS*index+2] = mUniforms[NUM_UNIFORMS*(index+1)+2];
+    mUniforms[NUM_UNIFORMS*index+3] = mUniforms[NUM_UNIFORMS*(index+1)+3];
+    mUniforms[NUM_UNIFORMS*index+4] = mUniforms[NUM_UNIFORMS*(index+1)+4];
+    mUniforms[NUM_UNIFORMS*index+5] = mUniforms[NUM_UNIFORMS*(index+1)+5];
+    mUniforms[NUM_UNIFORMS*index+6] = mUniforms[NUM_UNIFORMS*(index+1)+6];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// here construct the ModelView Matrix
+
+  synchronized void send(float[] viewMatrix, DistortedProjection dp) 
+    {
+    Matrix.setIdentityM(viewMatrix, 0);
+    Matrix.translateM(viewMatrix, 0, -dp.width/2, dp.height/2, -dp.distance);
+    
+    float x,y,z, sx,sy,sz=1.0f;
+   
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if (mType[i] == EffectNames.ROTATE.ordinal() )
+        {
+        x = mUniforms[NUM_UNIFORMS*i  ];
+        y = mUniforms[NUM_UNIFORMS*i+1];
+        z = mUniforms[NUM_UNIFORMS*i+2];
+     
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+        Matrix.rotateM( viewMatrix, 0, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);  
+        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
+        }
+      else if(mType[i] == EffectNames.QUATERNION.ordinal() )
+        {
+        x = mUniforms[NUM_UNIFORMS*i  ];
+        y = mUniforms[NUM_UNIFORMS*i+1];
+        z = mUniforms[NUM_UNIFORMS*i+2];
+     	
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+        multiplyByQuat(viewMatrix, mUniforms[NUM_UNIFORMS*i+3], mUniforms[NUM_UNIFORMS*i+4], mUniforms[NUM_UNIFORMS*i+5], mUniforms[NUM_UNIFORMS*i+6]);
+        Matrix.translateM(viewMatrix, 0,-x, y,-z);  
+        }
+      else if(mType[i] == EffectNames.MOVE.ordinal() )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+        
+        Matrix.translateM(viewMatrix, 0, sx,-sy, sz);   
+        }
+      else if(mType[i] == EffectNames.SCALE.ordinal() )
+        {
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+
+        Matrix.scaleM(viewMatrix, 0, sx, sy, sz);  
+        }
+      else if(mType[i] == EffectNames.SHEAR.ordinal() )
+        {
+        x  = mUniforms[NUM_UNIFORMS*i  ];
+        y  = mUniforms[NUM_UNIFORMS*i+1];
+        z  = mUniforms[NUM_UNIFORMS*i+2];
+        
+        sx = mUniforms[NUM_UNIFORMS*i+3];   
+        sy = mUniforms[NUM_UNIFORMS*i+4];   
+        sz = mUniforms[NUM_UNIFORMS*i+5];   
+        
+        Matrix.translateM(viewMatrix, 0, x,-y, z); 
+      
+        viewMatrix[4] += sx*viewMatrix[0]; // Multiply viewMatrix by 1 x 0 0 , i.e. X-shear. TODO: change this so it is symmetric w respect to all the axis.
+        viewMatrix[5] += sx*viewMatrix[1]; //                        0 1 0 0 
+        viewMatrix[6] += sx*viewMatrix[2]; //                        0 0 1 0
+        viewMatrix[7] += sx*viewMatrix[3]; //                        0 0 0 1
+      
+        viewMatrix[0] += sy*viewMatrix[4]; // Multiply viewMatrix by 1 0 0 0 , i.e. Y-shear. TODO: change this so it is symmetric w respect to all the axis.
+        viewMatrix[1] += sy*viewMatrix[5]; //                        y 1 0 0
+        viewMatrix[2] += sy*viewMatrix[6]; //                        0 0 1 0
+        viewMatrix[3] += sy*viewMatrix[7]; //                        0 0 0 1      
+      
+        // TODO: implement Z-shear.
+        
+        Matrix.translateM(viewMatrix, 0,-x, y, -z);
+        }
+      }
+   
+    Matrix.translateM(viewMatrix, 0, mObjHalfX,-mObjHalfY, -mObjHalfZ);
+    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, viewMatrix, 0);
+    
+    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
+    GLES20.glUniform1f( mDepthH, dp.depth);   
+    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, viewMatrix, 0);
+    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// here construct the ModelView Matrix, but without any effects
+
+  synchronized void sendNoEffects(DistortedProjection dp) 
+    {
+    Matrix.setIdentityM(mTmpMatrix, 0);
+    Matrix.translateM(mTmpMatrix, 0, mObjHalfX-dp.width/2, dp.height/2-mObjHalfY, mObjHalfZ-dp.distance);
+    Matrix.multiplyMM(mMVPMatrix, 0, dp.projectionMatrix, 0, mTmpMatrix, 0);
+    
+    GLES20.glUniform3f( mBmpDH , mObjHalfX, mObjHalfY, mObjHalfZ);
+    GLES20.glUniform1f( mDepthH, dp.depth);  
+    GLES20.glUniformMatrix4fv(mMVMatrixH , 1, false, mTmpMatrix, 0);
+    GLES20.glUniformMatrix4fv(mMVPMatrixH, 1, false, mMVPMatrix, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator i)
+    {
+    if( mMax[PRESHADER]>mNumEffects )
+      {
+      mInterP[mNumEffects] = p;
+      mInterI[mNumEffects] = i;
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator i)
+    {
+    if( mMax[PRESHADER]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
+            
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, Interpolator1D i, float aX, float aY, float aZ)
+    {
+    if( mMax[PRESHADER]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] = x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] = y;
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] = z;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, Interpolator3D p, Interpolator1D i, float aX, float aY, float aZ)
+    {
+    if( mMax[PRESHADER]>mNumEffects )
+      {
+      mInterP[mNumEffects] = p;
+      mInterI[mNumEffects] = i;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+  
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  synchronized long add(EffectNames eln, float x, float y, float z, float aA, float aX, float aY, float aZ)
+    {
+    if( mMax[PRESHADER]>mNumEffects )
+      {
+      mInterP[mNumEffects] = null; 
+      mInterI[mNumEffects] = null;
+      
+      mUniforms[NUM_UNIFORMS*mNumEffects  ] =  x;
+      mUniforms[NUM_UNIFORMS*mNumEffects+1] =  y;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+2] =  z;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+3] = aA;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+4] = aX;
+      mUniforms[NUM_UNIFORMS*mNumEffects+5] = aY;  
+      mUniforms[NUM_UNIFORMS*mNumEffects+6] = aZ;  
+      
+      return addBase(eln);   
+      }
+      
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  }
diff --git a/src/main/java/org/distorted/library/EffectListVertex.java b/src/main/java/org/distorted/library/EffectListVertex.java
index 98fe28d..063e3c6 100644
--- a/src/main/java/org/distorted/library/EffectListVertex.java
+++ b/src/main/java/org/distorted/library/EffectListVertex.java
@@ -13,9 +13,9 @@ class EffectListVertex extends EffectList
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
    
-  public EffectListVertex(DistortedObject bmp) 
+  public EffectListVertex(DistortedObject obj)
     { 
-    super(bmp,NUM_UNIFORMS,VERTEX);
+    super(obj,NUM_UNIFORMS,VERTEX);
     }
   
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/EffectNames.java b/src/main/java/org/distorted/library/EffectNames.java
index 17f0e40..ef46dde 100644
--- a/src/main/java/org/distorted/library/EffectNames.java
+++ b/src/main/java/org/distorted/library/EffectNames.java
@@ -6,12 +6,12 @@ enum EffectNames
   {
   // EFFECT NAME /////// EFFECT TYPE ////////////// UNITY /////////////////////////
    
-  ROTATE           ( Distorted.TYPE_MATR,   new float[] {0.0f}           ),
-  QUATERNION       ( Distorted.TYPE_MATR,   new float[] {0.0f,0.0f,0.0f} ),      // quaternion is a unity iff its axis (x,y,z) is (0,0,0) 
-  MOVE             ( Distorted.TYPE_MATR,   new float[] {0.0f,0.0f,0.0f} ),
-  SCALE            ( Distorted.TYPE_MATR,   new float[] {1.0f,1.0f,1.0f} ),
-  SHEAR            ( Distorted.TYPE_MATR,   new float[] {0.0f,0.0f,0.0f} ),
-  // add new Matrix effects here...
+  ROTATE           ( Distorted.TYPE_PRE ,   new float[] {0.0f}           ),
+  QUATERNION       ( Distorted.TYPE_PRE ,   new float[] {0.0f,0.0f,0.0f} ),      // quaternion is a unity iff its axis (x,y,z) is (0,0,0)
+  MOVE             ( Distorted.TYPE_PRE ,   new float[] {0.0f,0.0f,0.0f} ),
+  SCALE            ( Distorted.TYPE_PRE ,   new float[] {1.0f,1.0f,1.0f} ),
+  SHEAR            ( Distorted.TYPE_PRE ,   new float[] {0.0f,0.0f,0.0f} ),
+  // add new PreShader effects here...
   
   DISTORT          ( Distorted.TYPE_VERT,   new float[] {0.0f,0.0f,0.0f} ),      // keep this the first VERT effect (reason: getType)
   DEFORM           ( Distorted.TYPE_VERT,   new float[] {0.0f,0.0f}      ),
@@ -32,9 +32,13 @@ enum EffectNames
   CONTRAST         ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
   SMOOTH_CONTRAST  ( Distorted.TYPE_FRAG,   new float[] {1.0f}           ),
   HUE              ( Distorted.TYPE_FRAG,   new float[] {0.0f}           ),
-  SMOOTH_HUE       ( Distorted.TYPE_FRAG,   new float[] {0.0f}           );
+  SMOOTH_HUE       ( Distorted.TYPE_FRAG,   new float[] {0.0f}           ),
   // add new Fragment effects here...
 
+  SAVE_PNG         ( Distorted.TYPE_POST,   null                         ),      // PostShader Effects don't have Unities.
+  SAVE_MP4         ( Distorted.TYPE_POST,   null                         );      //
+  // add new PostShader effects here...
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
   
   private static final int MAXDIM = 4;  // maximum supported dimension of an effect  
@@ -55,7 +59,7 @@ enum EffectNames
     
     for(EffectNames name: EffectNames.values())
       {
-      dimensions[i] = name.unity.length;
+      dimensions[i] = (name.unity==null ? 0 : name.unity.length);
       
       switch(dimensions[i])
         {
@@ -89,10 +93,11 @@ enum EffectNames
   
   static int getType(int ordinal)
     {
-    if( ordinal<DISTORT.ordinal()     ) return Distorted.TYPE_MATR;
+    if( ordinal<DISTORT.ordinal()     ) return Distorted.TYPE_PRE;
     if( ordinal<MACROBLOCK.ordinal()  ) return Distorted.TYPE_VERT;
-   
-    return Distorted.TYPE_FRAG;
+    if( ordinal<SAVE_PNG.ordinal()    ) return Distorted.TYPE_FRAG;
+
+    return Distorted.TYPE_POST;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
