commit 4c1dd6e96d9bdeb116348d884a4f784641e0e909
Author: Leszek Koltunski <leszek@distorted.org>
Date:   Sat Dec 17 01:20:20 2016 +0000

    Beginnings of support for postprocessing Effects.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index 26a950c..8758b53 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -382,6 +382,17 @@ public class Distorted
     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.
@@ -444,4 +455,25 @@ public class Distorted
     {
     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 #onSurfaceCreated}. 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);
+    }
   }
diff --git a/src/main/java/org/distorted/library/EffectNames.java b/src/main/java/org/distorted/library/EffectNames.java
index 148d51d..87b5061 100644
--- a/src/main/java/org/distorted/library/EffectNames.java
+++ b/src/main/java/org/distorted/library/EffectNames.java
@@ -236,9 +236,21 @@ public enum EffectNames
    * <p>
    * Unity: contrastLevel = 1
    */
-  SMOOTH_CONTRAST  ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false );
+  SMOOTH_CONTRAST  ( EffectTypes.FRAGMENT,   new float[] {1.0f}           , 1, true, false ),
   // add new Fragment effects here...
 
+  /////////////////////////////////////////////////////////////////////////////////
+  // POSTPROCESSING EFFECTS.
+  // Always 3 Uniforms: 1 per-effect interpolated value + 2 dimensional center.
+ /**
+   * Blur the area around the center.
+   * <p>
+   * Uniforms: (radius,centerX,centerY)
+   * <p>
+   * Unity: radius==0
+   */
+  BLUR             ( EffectTypes.POSTPROCESS,new float[] {0.0f}          , 1, false, true );
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
   
   private static final int MAXDIM = 4;  // maximum supported dimension of an effect  
diff --git a/src/main/java/org/distorted/library/EffectQueue.java b/src/main/java/org/distorted/library/EffectQueue.java
index 20063ad..f131950 100644
--- a/src/main/java/org/distorted/library/EffectQueue.java
+++ b/src/main/java/org/distorted/library/EffectQueue.java
@@ -62,21 +62,23 @@ abstract class EffectQueue
     mMaxIndex     = index;
     mObjectID     = id;
 
-    if( mMax[mMaxIndex]>0 )
+    int max = mMax[mMaxIndex];
+
+    if( max>0 )
       {
-      mName            = new int[mMax[mMaxIndex]];
-      mUniforms        = new float[numUniforms*mMax[mMaxIndex]];
-      mInter           = new Dynamic[3][mMax[mMaxIndex]];
-      mCurrentDuration = new long[mMax[mMaxIndex]];
-      mID              = new long[mMax[mMaxIndex]];
-      mIDIndex         = new byte[mMax[mMaxIndex]];
-      mFreeIndexes     = new byte[mMax[mMaxIndex]];
+      mName            = new int[max];
+      mUniforms        = new float[numUniforms*max];
+      mInter           = new Dynamic[3][max];
+      mCurrentDuration = new long[max];
+      mID              = new long[max];
+      mIDIndex         = new byte[max];
+      mFreeIndexes     = new byte[max];
      
-      for(byte i=0; i<mMax[mMaxIndex]; i++) mFreeIndexes[i] = i;
+      for(byte i=0; i<max; i++) mFreeIndexes[i] = i;
 
       if( numCache>0 )
         {
-        mCache = new float[numCache*mMax[mMaxIndex]];
+        mCache = new float[numCache*max];
         }
       }
    
diff --git a/src/main/java/org/distorted/library/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/EffectQueuePostprocess.java
new file mode 100644
index 0000000..da635a5
--- /dev/null
+++ b/src/main/java/org/distorted/library/EffectQueuePostprocess.java
@@ -0,0 +1,159 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 android.opengl.GLES20;
+
+import org.distorted.library.message.EffectMessage;
+import org.distorted.library.type.Data1D;
+import org.distorted.library.type.Data2D;
+import org.distorted.library.type.Dynamic1D;
+import org.distorted.library.type.Dynamic2D;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static2D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueuePostprocess extends EffectQueue
+  {
+  private static final int NUM_UNIFORMS = 3;
+  private static final int NUM_CACHE    = 0;
+  private static final int INDEX = EffectTypes.POSTPROCESS.ordinal();
+
+  private static int mNumEffectsH;
+  private static int mTypeH;
+  private static int mUniformsH;
+  private static int mObjDH;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  EffectQueuePostprocess(long id)
+    { 
+    super(id,NUM_UNIFORMS,NUM_CACHE,INDEX );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void getUniforms(int mProgramH)
+    {
+    mNumEffectsH= GLES20.glGetUniformLocation( mProgramH, "pNumEffects");
+    mTypeH      = GLES20.glGetUniformLocation( mProgramH, "pType");
+    mUniformsH  = GLES20.glGetUniformLocation( mProgramH, "pUniforms");
+    mObjDH      = GLES20.glGetUniformLocation( mProgramH, "u_objD");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  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( mInter[0][i]!=null && mInter[0][i].interpolateMain(mUniforms ,NUM_UNIFORMS*i, mCurrentDuration[i], step) )
+        {
+        for(int j=0; j<mNumListeners; j++)
+          EffectMessageSender.newMessage( mListeners.elementAt(j),
+                                          EffectMessage.EFFECT_FINISHED,
+                                         (mID[i]<<EffectTypes.LENGTH)+EffectTypes.POSTPROCESS.type,
+                                          mName[i],
+                                          mObjectID);
+
+        if( EffectNames.isUnity(mName[i], mUniforms, NUM_UNIFORMS*i) )
+          {
+          remove(i);
+          i--;
+          continue;
+          }
+        else mInter[0][i] = null;
+        }
+
+      if( mInter[1][i]!=null )
+        {
+        mInter[1][i].interpolateMain(mUniforms, NUM_UNIFORMS*i+1, mCurrentDuration[i], step);
+        }
+
+      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];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  synchronized void send(float objX, float objY)
+    {
+    GLES20.glUniform1i( mNumEffectsH, mNumEffects);
+    GLES20.glUniform2f( mObjDH , objX, objY);
+
+    if( mNumEffects>0 )
+      {
+      GLES20.glUniform1iv( mTypeH    ,  mNumEffects, mName    ,0);
+      GLES20.glUniform4fv( mUniformsH,2*mNumEffects, mUniforms,0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// blur
+
+  synchronized long add(EffectNames eln, Data1D degree, Data2D center)
+    {
+    if( mMax[INDEX]>mNumEffects )
+      {
+      if( degree instanceof Dynamic1D)
+        {
+        mInter[0][mNumEffects] = (Dynamic1D)degree;
+        }
+      else if( degree instanceof Static1D)
+        {
+        mInter[0][mNumEffects] = null;
+        mUniforms[NUM_UNIFORMS*mNumEffects] = ((Static1D)degree).getX();
+        }
+      else return -1;
+
+      if( center instanceof Dynamic2D)
+        {
+        mInter[1][mNumEffects] = (Dynamic2D)center;
+        }
+      else if( center instanceof Static2D)
+        {
+        mInter[1][mNumEffects] = null;
+        mUniforms[NUM_UNIFORMS*mNumEffects+1] = ((Static2D)center).getX();
+        mUniforms[NUM_UNIFORMS*mNumEffects+2] = ((Static2D)center).getY();
+        }
+      else return -1;
+
+      return addBase(eln);
+      }
+      
+    return -1;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/EffectTypes.java b/src/main/java/org/distorted/library/EffectTypes.java
index db14ae5..d584554 100644
--- a/src/main/java/org/distorted/library/EffectTypes.java
+++ b/src/main/java/org/distorted/library/EffectTypes.java
@@ -31,15 +31,20 @@ public enum EffectTypes
   /**
    * Effects that change the ModelView matrix: Rotations, Moves, Shears, Scales.
    */
-  MATRIX   ( 0x1 ),   // we will need to perform bitwise operations on those - so keep the values 1,2,4,8...
+  MATRIX      ( 0x1 ),  // we will need to perform bitwise operations on those - so keep the values 1,2,4,8...
   /**
    * Effects that get executed in the Vertex shader: various distortions of the vertices.
    */
-  VERTEX   ( 0x2 ),
+  VERTEX      ( 0x2 ),
   /**
    * Effects executed in the Fragment shader: changes of color, hue, transparency levels, etc.
    */
-  FRAGMENT ( 0x4 );
+  FRAGMENT    ( 0x4 ),
+  /**
+   * Postprocessing done to the texture the first stage fragment shader created (if this queue is
+   * empty, the first stage skips this intermediate texture)
+   */
+  POSTPROCESS ( 0x8 );
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -63,7 +68,8 @@ public enum EffectTypes
                       // to EffectQueueMatrix.setMax(int)
 
     maxtable[1] = 5;  // Max 5 VERTEX Effects
-    maxtable[2] = 5;  // Max 3 FRAGMENT Effects
+    maxtable[2] = 5;  // Max 5 FRAGMENT Effects
+    maxtable[3] = 5;  // Max 5 POSTPROCESSING Effects
     }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
   }
diff --git a/src/main/res/raw/post_fragment_shader.glsl b/src/main/res/raw/post_fragment_shader.glsl
new file mode 100644
index 0000000..87e32e4
--- /dev/null
+++ b/src/main/res/raw/post_fragment_shader.glsl
@@ -0,0 +1,61 @@
+//////////////////////////////////////////////////////////////////////////////////////////////
+// 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/>.                       //
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+varying vec2 vTexCoordinate;
+uniform sampler2D u_Texture;
+uniform vec2 u_objD;
+uniform int fNumEffects;                   // total number of postprocessing effects
+
+#if NUM_POSTPROCESS>0
+uniform int pType[NUM_POSTPROCESS];        // their types.
+uniform vec4 pUniforms[2*NUM_POSTPROCESS]; // i-th effect is 2 consecutive vec4's: [2*i], [2*i+1].
+                                           //
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// BLUR EFFECT
+
+void blur(out vec4 color)
+  {
+  color = vec4(0.0);
+
+  float blurSizeH = 1.0 / u_objD.x;
+  float blurSizeV = 1.0 / u_objD.y;
+
+  for (int x = -4; x <= 4; x++)
+    for (int y = -4; y <= 4; y++)
+      color += texture( u_Texture, vec2(vTexCoord.x + x * blurSizeH, vTexCoord.y + y * blurSizeV) ) / 81.0;
+  }
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+void main()
+  {
+  vec4 pixel = texture2D(u_Texture,v_TexCoordinate);
+
+#if NUM_POSTPROCESS>0
+  for(int i=0; i<fNumEffects; i++)
+    {
+    if( pType[i]==BLUR ) blur(pixel);
+    }
+#endif
+
+  gl_FragColor = pixel;
+  }
\ No newline at end of file
diff --git a/src/main/res/raw/post_vertex_shader.glsl b/src/main/res/raw/post_vertex_shader.glsl
new file mode 100644
index 0000000..c25d32d
--- /dev/null
+++ b/src/main/res/raw/post_vertex_shader.glsl
@@ -0,0 +1,31 @@
+//////////////////////////////////////////////////////////////////////////////////////////////
+// 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/>.                       //
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+attribute vec2 a_Position;       // Per-vertex position information we will pass in.
+attribute vec2 a_TexCoordinate;  // Per-vertex texture coordinate information we will pass in.
+varying   vec2 v_TexCoordinate;  //
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// empty 2D vertex shader for postprocessing
+
+void main()
+  {
+  v_TexCoordinate = a_TexCoordinate;
+  gl_Position = vec4(a_Position, 0.0, 1.0);
+  }
\ No newline at end of file
