commit 241d3e5ff27840107d98c92f99f4ee88009bcb9c
Author: leszek <leszek@koltunski.pl>
Date:   Wed Apr 23 00:26:07 2025 +0200

    Rename .java to .kt

diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueue.java b/src/main/java/org/distorted/library/effectqueue/EffectQueue.java
deleted file mode 100644
index 2f5eb06..0000000
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueue.java
+++ /dev/null
@@ -1,526 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// This library is free software; you can redistribute it and/or                                 //
-// modify it under the terms of the GNU Lesser General Public                                    //
-// License as published by the Free Software Foundation; either                                  //
-// version 2.1 of the License, or (at your option) any later version.                            //
-//                                                                                               //
-// This library 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                             //
-// Lesser General Public License for more details.                                               //
-//                                                                                               //
-// You should have received a copy of the GNU Lesser General Public                              //
-// License along with this library; if not, write to the Free Software                           //
-// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library.effectqueue;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.EffectName;
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.library.main.InternalMaster;
-import org.distorted.library.main.InternalStackFrameList;
-import org.distorted.library.uniformblock.UniformBlockFloatUniforms;
-import org.distorted.library.uniformblock.UniformBlockIntUniforms;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document
- *
- * @y.exclude
- */
-public abstract class EffectQueue implements InternalMaster.Slave
-  {
-  public static final int MAIN_VARIANTS = 4; // Number of Main program variants (ATM 4: MAIN, MAIN OIT, PREPROCESS, FULL)
-
-  static final int VERT_INT_UBO_BINDING = 5;
-  static final int VERT_FLO_UBO_BINDING = 6;
-
-  private static final int CREATE = 0;
-  private static final int ATTACH = 1;
-  private static final int DETACH = 2;
-  private static final int DETALL = 3;
-
-  int mNumEffects;              // 'ToBe' will be more than mNumEffects if doWork() hasn't
-  private int mNumEffectsToBe;  // added them yet (or less if it hasn't removed some yet)
-  Effect[] mEffects;
-
-  UniformBlockFloatUniforms mUBF;
-  UniformBlockIntUniforms mUBI;
-
-  private long mID;
-  private final int mIndex;
-  private boolean mCreated;
-
-  private static class Job
-    {
-    int type;
-    int num1, num2;
-    boolean bool;
-    Effect effect;
-
-    Job(int t, int m1, int m2, boolean n, Effect e)
-      {
-      type  = t;
-      num1  = m1;
-      num2  = m2;
-      bool  = n;
-      effect= e;
-      }
-    }
-
-  private final ArrayList<Job> mJobs;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  EffectQueue(int numFloatUniforms, int numIntUniforms, boolean useUBO, int index)
-    {
-    mCreated        = false;
-    mID             = 0;
-    mNumEffects     = 0;
-    mNumEffectsToBe = 0;
-    mIndex          = index;
-
-    mJobs = new ArrayList<>();
-
-    mJobs.add(new Job(CREATE,numFloatUniforms,numIntUniforms,useUBO,null)); // create the stuff that depends on max number
-    InternalMaster.newSlave(this);                                          // of uniforms later, on first render.
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this is not thread safe! The 'source' might change while we're copying it.
-
-  EffectQueue(EffectQueue source)
-    {
-    if( !source.mCreated )
-      {
-      mCreated        = false;
-      mID             = 0;
-      mNumEffects     = 0;
-      mNumEffectsToBe = 0;
-      mIndex          = source.mIndex;
-
-      mJobs = new ArrayList<>();
-
-      int numJobs = source.mJobs.size();
-
-      for(int i=0; i<numJobs; i++)
-        {
-        Job job = source.mJobs.get(i);
-        mJobs.add(job);
-        }
-
-      InternalMaster.newSlave(this);
-      }
-    else
-      {
-      mCreated        = true;
-      mID             = source.mID;
-      mNumEffects     = source.mNumEffects;
-      mNumEffectsToBe = source.mNumEffectsToBe;
-      mIndex          = source.mIndex;
-
-      mJobs = new ArrayList<>();
-
-      int max = InternalStackFrameList.getMax(mIndex);
-
-      if( max>0 )
-        {
-        mEffects= new Effect[max];
-        mUBI  = new UniformBlockIntUniforms(source.mUBI);
-        mUBF  = new UniformBlockFloatUniforms(source.mUBF);
-
-        if( mNumEffects>=0 )
-          {
-          System.arraycopy(source.mEffects, 0, mEffects, 0, mNumEffects);
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void allocateQueues(EffectQueue[] queues, EffectQueue[] from, int flags)
-    {
-    queues[0] = (flags & DistortedLibrary.CLONE_MATRIX     ) != 0 ? from[0] : new EffectQueueMatrix();
-    queues[1] = (flags & DistortedLibrary.CLONE_VERTEX     ) != 0 ? from[1] : new EffectQueueVertex();
-    queues[2] = (flags & DistortedLibrary.CLONE_FRAGMENT   ) != 0 ? from[2] : new EffectQueueFragment();
-    queues[3] = (flags & DistortedLibrary.CLONE_POSTPROCESS) != 0 ? from[3] : new EffectQueuePostprocess();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void compute(EffectQueue[] queues, long currTime, long step)
-    {
-    ((EffectQueueMatrix     )queues[0]).compute(currTime,step);
-    ((EffectQueueVertex     )queues[1]).compute(currTime,step);
-    ((EffectQueueFragment   )queues[2]).compute(currTime,step);
-    ((EffectQueuePostprocess)queues[3]).compute(currTime,step);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void send(EffectQueue[] queues, int programH, float distance, float mipmap,
-                          float[] projection, float inflate, int variant )
-    {
-    ((EffectQueueMatrix  )queues[0]).send(distance, mipmap, projection, variant);
-    ((EffectQueueVertex  )queues[1]).send(inflate, programH, variant);
-    ((EffectQueueFragment)queues[2]).send(variant);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// variant: 0 --> MAIN  1 --> OIT  2 --> prePOST  3 --> FULL
-
-  public static void getUniforms(int programH, int variant)
-    {
-    EffectQueueFragment.uniforms(programH,variant);
-    EffectQueueVertex  .uniforms(programH,variant);
-    EffectQueueMatrix  .uniforms(programH,variant);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Every effect queue has an ID, which should be the same iff two queues hold the same effects.
-// (this is a speedup: then both queues can be applied once, which seriously speeds up stuff -
-// especially important in case of postprocessing)
-
-  private void regenerateID()
-    {
-    if( mNumEffects>0 )
-      {
-      HashMap<ArrayList<Long>,Long> map = InternalStackFrameList.getMap();
-      ArrayList<Long> list = new ArrayList<>();
-      for (int i=0; i<mNumEffects; i++) list.add(mEffects[i].getID());
-      Long id = map.get(list);
-
-      if( id!=null )
-        {
-        mID = id;
-        }
-      else
-        {
-        mID = InternalStackFrameList.getNextQueueID();
-        map.put(list,mID);
-        }
-      }
-    else
-      {
-      mID = 0;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public long getID()
-    {
-    return mID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static boolean setMax(int index, int m)
-    {
-    return InternalStackFrameList.setMax(index, Math.max(m,0));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static int getMax(int index)
-    {
-    return InternalStackFrameList.getMax(index);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this assumes 0<=effect
-
-  private void removeNow(int pos)
-    {
-    if( mNumEffects>pos )
-      {
-      mNumEffects--;
-      mEffects[pos].remQueue(this);
-      System.arraycopy(mEffects, pos+1, mEffects, pos, mNumEffects-pos);
-      mUBI.remove(pos, mNumEffects);
-      mEffects[mNumEffects] = null;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addNow(int pos, Effect effect)
-    {
-    mEffects[pos] = effect;
-    mUBI.addOrdinal(pos, effect.getName().ordinal() );
-    effect.addQueue(this);
-
-    if( mIndex==EffectType.VERTEX.ordinal() )
-      {
-      mUBI.addAssociations(pos,effect);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public synchronized int removeByName(EffectName name)
-    {
-    int ret = 0;
-
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mEffects[i].getName() == name )
-        {
-        mJobs.add(new Job(DETACH,0,0,true,mEffects[i]));
-        ret++;
-        }
-      }
-
-    if( ret>0 )
-      {
-      InternalMaster.newSlave(this);
-      mNumEffectsToBe-=ret;
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public synchronized int removeById(long id)
-    {
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mEffects[i].getID() == id )
-        {
-        mJobs.add(new Job(DETACH,0,0,true,mEffects[i]));
-        InternalMaster.newSlave(this);
-        mNumEffectsToBe--;
-        return 1;
-        }
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public synchronized int removeEffect(Effect effect)
-    {
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mEffects[i]==effect )
-        {
-        mJobs.add(new Job(DETACH,0,0,true,mEffects[i]));
-        InternalMaster.newSlave(this);
-        mNumEffectsToBe--;
-        return 1;
-        }
-      }
-   
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// we do want to notify Listeners if they called 'abortAll' themselves but don't want to notify
-// them if it is the library itself which is releasing resources.
-
-  public synchronized int removeAll(boolean notify)
-    {
-    mJobs.add(new Job(DETALL,0,0,notify,null));
-    InternalMaster.newSlave(this);
-    mNumEffectsToBe = 0;
-    return mNumEffects;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  public boolean add(Effect effect)
-    {
-    if( InternalStackFrameList.getMax(mIndex)>mNumEffectsToBe || !mCreated )
-      {
-      mJobs.add(new Job(ATTACH,-1,0,false,effect));
-      InternalMaster.newSlave(this);
-      mNumEffectsToBe++;
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean add(Effect effect, int position)
-    {
-    if( InternalStackFrameList.getMax(mIndex)>mNumEffectsToBe || !mCreated )
-      {
-      mJobs.add(new Job(ATTACH,position,0,false,effect));
-      InternalMaster.newSlave(this);
-      mNumEffectsToBe++;
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean exists(long id)
-    {
-    for(int i=0; i<mNumEffects; i++)
-      {
-      if( mEffects[i].getID() == id ) return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getNumEffects()
-    {
-    return mNumEffects;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getNumEffectsToBe()
-    {
-    return mNumEffectsToBe;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public Effect getEffect(int position)
-    {
-    if( position>=0 && position< mNumEffects )
-      {
-      return mEffects[position];
-      }
-    else
-      {
-      DistortedLibrary.logMessage("EffectQueue: getEffect: out of range "+position);
-      return null;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void setAssociation(long effectID)
-    {
-    for(int j=0; j<mNumEffects; j++)
-      {
-      if (mEffects[j].getID() == effectID)
-        {
-        mUBI.addAssociations(j,mEffects[j]);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void markForDeletion()
-    {
-    mUBI.markForDeletion();
-    mUBF.markForDeletion();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String retEffects()
-    {
-    String dbg="";
-
-    for(int i=0; i<mNumEffects; i++)
-      {
-      dbg += (i+": "+mEffects[i].getString()+" ");
-      }
-
-    return dbg;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void doWork()
-    {
-    boolean changed = false;
-    int num = mJobs.size();
-    Job job;
-
-    for(int i=0; i<num; i++)
-      {
-      job = mJobs.remove(0);
-
-      switch(job.type)
-        {
-        case CREATE: int max = InternalStackFrameList.getMax(mIndex);
-                     if( max>0 )
-                       {
-                       mEffects= new Effect[max];
-                       mUBF    = new UniformBlockFloatUniforms(job.num1, max, job.bool);
-                       mUBI    = new UniformBlockIntUniforms  (job.num2, max, job.bool);
-                       }
-                     mCreated = true;
-
-                     break;
-        case ATTACH: if( InternalStackFrameList.getMax(mIndex)>mNumEffects ) // it is possible that we have first
-                       {                                                     // added effects and then lowered mMax
-                       int position = job.num1;
-
-                       if( position<0 )
-                         {
-                         addNow(mNumEffects,job.effect);
-                         mNumEffects++;
-                         changed = true;
-                         }
-                       else if( position<=mNumEffects )
-                         {
-                         System.arraycopy(mEffects, position, mEffects, position+1, mNumEffects-position);
-                         mUBI.makeHole(position, mNumEffects);
-                         addNow(position,job.effect);
-                         mNumEffects++;
-                         changed = true;
-                         }
-                       }
-                     else
-                       {
-                       DistortedLibrary.logMessage("EffectQueue: failed to add effect "+job.effect.getName());
-                       }
-                     break;
-        case DETACH: for(int j=0; j<mNumEffects; j++)
-                       {
-                       if (mEffects[j] == job.effect)
-                         {
-                         removeNow(j);
-                         changed = true;
-                         break;
-                         }
-                       }
-                     break;
-        case DETALL: for(int j=0; j<mNumEffects; j++ )
-                       {
-                       changed = true;
-                       mEffects[j] = null;
-                       }
-
-                     // TODO: notify listeners?
-                     /* if( job.bool )
-                          {
-                          // ...
-                          }
-                      */
-
-                     mNumEffects= 0;
-                     break;
-        }
-      }
-
-    if( changed && mIndex==EffectType.POSTPROCESS.ordinal() ) regenerateID();
-    }
-  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueue.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueue.kt
new file mode 100644
index 0000000..2f5eb06
--- /dev/null
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueue.kt
@@ -0,0 +1,526 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// This library is free software; you can redistribute it and/or                                 //
+// modify it under the terms of the GNU Lesser General Public                                    //
+// License as published by the Free Software Foundation; either                                  //
+// version 2.1 of the License, or (at your option) any later version.                            //
+//                                                                                               //
+// This library 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                             //
+// Lesser General Public License for more details.                                               //
+//                                                                                               //
+// You should have received a copy of the GNU Lesser General Public                              //
+// License along with this library; if not, write to the Free Software                           //
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effectqueue;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.EffectName;
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.InternalMaster;
+import org.distorted.library.main.InternalStackFrameList;
+import org.distorted.library.uniformblock.UniformBlockFloatUniforms;
+import org.distorted.library.uniformblock.UniformBlockIntUniforms;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document
+ *
+ * @y.exclude
+ */
+public abstract class EffectQueue implements InternalMaster.Slave
+  {
+  public static final int MAIN_VARIANTS = 4; // Number of Main program variants (ATM 4: MAIN, MAIN OIT, PREPROCESS, FULL)
+
+  static final int VERT_INT_UBO_BINDING = 5;
+  static final int VERT_FLO_UBO_BINDING = 6;
+
+  private static final int CREATE = 0;
+  private static final int ATTACH = 1;
+  private static final int DETACH = 2;
+  private static final int DETALL = 3;
+
+  int mNumEffects;              // 'ToBe' will be more than mNumEffects if doWork() hasn't
+  private int mNumEffectsToBe;  // added them yet (or less if it hasn't removed some yet)
+  Effect[] mEffects;
+
+  UniformBlockFloatUniforms mUBF;
+  UniformBlockIntUniforms mUBI;
+
+  private long mID;
+  private final int mIndex;
+  private boolean mCreated;
+
+  private static class Job
+    {
+    int type;
+    int num1, num2;
+    boolean bool;
+    Effect effect;
+
+    Job(int t, int m1, int m2, boolean n, Effect e)
+      {
+      type  = t;
+      num1  = m1;
+      num2  = m2;
+      bool  = n;
+      effect= e;
+      }
+    }
+
+  private final ArrayList<Job> mJobs;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  EffectQueue(int numFloatUniforms, int numIntUniforms, boolean useUBO, int index)
+    {
+    mCreated        = false;
+    mID             = 0;
+    mNumEffects     = 0;
+    mNumEffectsToBe = 0;
+    mIndex          = index;
+
+    mJobs = new ArrayList<>();
+
+    mJobs.add(new Job(CREATE,numFloatUniforms,numIntUniforms,useUBO,null)); // create the stuff that depends on max number
+    InternalMaster.newSlave(this);                                          // of uniforms later, on first render.
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this is not thread safe! The 'source' might change while we're copying it.
+
+  EffectQueue(EffectQueue source)
+    {
+    if( !source.mCreated )
+      {
+      mCreated        = false;
+      mID             = 0;
+      mNumEffects     = 0;
+      mNumEffectsToBe = 0;
+      mIndex          = source.mIndex;
+
+      mJobs = new ArrayList<>();
+
+      int numJobs = source.mJobs.size();
+
+      for(int i=0; i<numJobs; i++)
+        {
+        Job job = source.mJobs.get(i);
+        mJobs.add(job);
+        }
+
+      InternalMaster.newSlave(this);
+      }
+    else
+      {
+      mCreated        = true;
+      mID             = source.mID;
+      mNumEffects     = source.mNumEffects;
+      mNumEffectsToBe = source.mNumEffectsToBe;
+      mIndex          = source.mIndex;
+
+      mJobs = new ArrayList<>();
+
+      int max = InternalStackFrameList.getMax(mIndex);
+
+      if( max>0 )
+        {
+        mEffects= new Effect[max];
+        mUBI  = new UniformBlockIntUniforms(source.mUBI);
+        mUBF  = new UniformBlockFloatUniforms(source.mUBF);
+
+        if( mNumEffects>=0 )
+          {
+          System.arraycopy(source.mEffects, 0, mEffects, 0, mNumEffects);
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void allocateQueues(EffectQueue[] queues, EffectQueue[] from, int flags)
+    {
+    queues[0] = (flags & DistortedLibrary.CLONE_MATRIX     ) != 0 ? from[0] : new EffectQueueMatrix();
+    queues[1] = (flags & DistortedLibrary.CLONE_VERTEX     ) != 0 ? from[1] : new EffectQueueVertex();
+    queues[2] = (flags & DistortedLibrary.CLONE_FRAGMENT   ) != 0 ? from[2] : new EffectQueueFragment();
+    queues[3] = (flags & DistortedLibrary.CLONE_POSTPROCESS) != 0 ? from[3] : new EffectQueuePostprocess();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void compute(EffectQueue[] queues, long currTime, long step)
+    {
+    ((EffectQueueMatrix     )queues[0]).compute(currTime,step);
+    ((EffectQueueVertex     )queues[1]).compute(currTime,step);
+    ((EffectQueueFragment   )queues[2]).compute(currTime,step);
+    ((EffectQueuePostprocess)queues[3]).compute(currTime,step);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void send(EffectQueue[] queues, int programH, float distance, float mipmap,
+                          float[] projection, float inflate, int variant )
+    {
+    ((EffectQueueMatrix  )queues[0]).send(distance, mipmap, projection, variant);
+    ((EffectQueueVertex  )queues[1]).send(inflate, programH, variant);
+    ((EffectQueueFragment)queues[2]).send(variant);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// variant: 0 --> MAIN  1 --> OIT  2 --> prePOST  3 --> FULL
+
+  public static void getUniforms(int programH, int variant)
+    {
+    EffectQueueFragment.uniforms(programH,variant);
+    EffectQueueVertex  .uniforms(programH,variant);
+    EffectQueueMatrix  .uniforms(programH,variant);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Every effect queue has an ID, which should be the same iff two queues hold the same effects.
+// (this is a speedup: then both queues can be applied once, which seriously speeds up stuff -
+// especially important in case of postprocessing)
+
+  private void regenerateID()
+    {
+    if( mNumEffects>0 )
+      {
+      HashMap<ArrayList<Long>,Long> map = InternalStackFrameList.getMap();
+      ArrayList<Long> list = new ArrayList<>();
+      for (int i=0; i<mNumEffects; i++) list.add(mEffects[i].getID());
+      Long id = map.get(list);
+
+      if( id!=null )
+        {
+        mID = id;
+        }
+      else
+        {
+        mID = InternalStackFrameList.getNextQueueID();
+        map.put(list,mID);
+        }
+      }
+    else
+      {
+      mID = 0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long getID()
+    {
+    return mID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static boolean setMax(int index, int m)
+    {
+    return InternalStackFrameList.setMax(index, Math.max(m,0));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getMax(int index)
+    {
+    return InternalStackFrameList.getMax(index);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this assumes 0<=effect
+
+  private void removeNow(int pos)
+    {
+    if( mNumEffects>pos )
+      {
+      mNumEffects--;
+      mEffects[pos].remQueue(this);
+      System.arraycopy(mEffects, pos+1, mEffects, pos, mNumEffects-pos);
+      mUBI.remove(pos, mNumEffects);
+      mEffects[mNumEffects] = null;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void addNow(int pos, Effect effect)
+    {
+    mEffects[pos] = effect;
+    mUBI.addOrdinal(pos, effect.getName().ordinal() );
+    effect.addQueue(this);
+
+    if( mIndex==EffectType.VERTEX.ordinal() )
+      {
+      mUBI.addAssociations(pos,effect);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public synchronized int removeByName(EffectName name)
+    {
+    int ret = 0;
+
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mEffects[i].getName() == name )
+        {
+        mJobs.add(new Job(DETACH,0,0,true,mEffects[i]));
+        ret++;
+        }
+      }
+
+    if( ret>0 )
+      {
+      InternalMaster.newSlave(this);
+      mNumEffectsToBe-=ret;
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public synchronized int removeById(long id)
+    {
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mEffects[i].getID() == id )
+        {
+        mJobs.add(new Job(DETACH,0,0,true,mEffects[i]));
+        InternalMaster.newSlave(this);
+        mNumEffectsToBe--;
+        return 1;
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public synchronized int removeEffect(Effect effect)
+    {
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mEffects[i]==effect )
+        {
+        mJobs.add(new Job(DETACH,0,0,true,mEffects[i]));
+        InternalMaster.newSlave(this);
+        mNumEffectsToBe--;
+        return 1;
+        }
+      }
+   
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// we do want to notify Listeners if they called 'abortAll' themselves but don't want to notify
+// them if it is the library itself which is releasing resources.
+
+  public synchronized int removeAll(boolean notify)
+    {
+    mJobs.add(new Job(DETALL,0,0,notify,null));
+    InternalMaster.newSlave(this);
+    mNumEffectsToBe = 0;
+    return mNumEffects;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  public boolean add(Effect effect)
+    {
+    if( InternalStackFrameList.getMax(mIndex)>mNumEffectsToBe || !mCreated )
+      {
+      mJobs.add(new Job(ATTACH,-1,0,false,effect));
+      InternalMaster.newSlave(this);
+      mNumEffectsToBe++;
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean add(Effect effect, int position)
+    {
+    if( InternalStackFrameList.getMax(mIndex)>mNumEffectsToBe || !mCreated )
+      {
+      mJobs.add(new Job(ATTACH,position,0,false,effect));
+      InternalMaster.newSlave(this);
+      mNumEffectsToBe++;
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean exists(long id)
+    {
+    for(int i=0; i<mNumEffects; i++)
+      {
+      if( mEffects[i].getID() == id ) return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumEffects()
+    {
+    return mNumEffects;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumEffectsToBe()
+    {
+    return mNumEffectsToBe;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public Effect getEffect(int position)
+    {
+    if( position>=0 && position< mNumEffects )
+      {
+      return mEffects[position];
+      }
+    else
+      {
+      DistortedLibrary.logMessage("EffectQueue: getEffect: out of range "+position);
+      return null;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setAssociation(long effectID)
+    {
+    for(int j=0; j<mNumEffects; j++)
+      {
+      if (mEffects[j].getID() == effectID)
+        {
+        mUBI.addAssociations(j,mEffects[j]);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void markForDeletion()
+    {
+    mUBI.markForDeletion();
+    mUBF.markForDeletion();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String retEffects()
+    {
+    String dbg="";
+
+    for(int i=0; i<mNumEffects; i++)
+      {
+      dbg += (i+": "+mEffects[i].getString()+" ");
+      }
+
+    return dbg;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void doWork()
+    {
+    boolean changed = false;
+    int num = mJobs.size();
+    Job job;
+
+    for(int i=0; i<num; i++)
+      {
+      job = mJobs.remove(0);
+
+      switch(job.type)
+        {
+        case CREATE: int max = InternalStackFrameList.getMax(mIndex);
+                     if( max>0 )
+                       {
+                       mEffects= new Effect[max];
+                       mUBF    = new UniformBlockFloatUniforms(job.num1, max, job.bool);
+                       mUBI    = new UniformBlockIntUniforms  (job.num2, max, job.bool);
+                       }
+                     mCreated = true;
+
+                     break;
+        case ATTACH: if( InternalStackFrameList.getMax(mIndex)>mNumEffects ) // it is possible that we have first
+                       {                                                     // added effects and then lowered mMax
+                       int position = job.num1;
+
+                       if( position<0 )
+                         {
+                         addNow(mNumEffects,job.effect);
+                         mNumEffects++;
+                         changed = true;
+                         }
+                       else if( position<=mNumEffects )
+                         {
+                         System.arraycopy(mEffects, position, mEffects, position+1, mNumEffects-position);
+                         mUBI.makeHole(position, mNumEffects);
+                         addNow(position,job.effect);
+                         mNumEffects++;
+                         changed = true;
+                         }
+                       }
+                     else
+                       {
+                       DistortedLibrary.logMessage("EffectQueue: failed to add effect "+job.effect.getName());
+                       }
+                     break;
+        case DETACH: for(int j=0; j<mNumEffects; j++)
+                       {
+                       if (mEffects[j] == job.effect)
+                         {
+                         removeNow(j);
+                         changed = true;
+                         break;
+                         }
+                       }
+                     break;
+        case DETALL: for(int j=0; j<mNumEffects; j++ )
+                       {
+                       changed = true;
+                       mEffects[j] = null;
+                       }
+
+                     // TODO: notify listeners?
+                     /* if( job.bool )
+                          {
+                          // ...
+                          }
+                      */
+
+                     mNumEffects= 0;
+                     break;
+        }
+      }
+
+    if( changed && mIndex==EffectType.POSTPROCESS.ordinal() ) regenerateID();
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.java b/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.java
deleted file mode 100644
index b40c35e..0000000
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.java
+++ /dev/null
@@ -1,98 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// This library is free software; you can redistribute it and/or                                 //
-// modify it under the terms of the GNU Lesser General Public                                    //
-// License as published by the Free Software Foundation; either                                  //
-// version 2.1 of the License, or (at your option) any later version.                            //
-//                                                                                               //
-// This library 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                             //
-// Lesser General Public License for more details.                                               //
-//                                                                                               //
-// You should have received a copy of the GNU Lesser General Public                              //
-// License along with this library; if not, write to the Free Software                           //
-// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library.effectqueue;
-
-import android.opengl.GLES30;
-
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.FragmentEffect;
-import org.distorted.library.message.EffectMessageSender;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectQueueFragment extends EffectQueue
-  {
-  private static final int NUM_FLOAT_UNIFORMS = FragmentEffect.NUM_FLOAT_UNIFORMS;
-  private static final int NUM_INT_UNIFORMS   = FragmentEffect.NUM_INT_UNIFORMS;
-  private static final boolean USE_UBO        = false;
-  private static final int INDEX = EffectType.FRAGMENT.ordinal();
-
-  private final static int[] mNumEffectsH  = new int[MAIN_VARIANTS];
-  private final static int[] mIntUniformsH = new int[MAIN_VARIANTS];
-  private final static int[] mFloUniformsH = new int[MAIN_VARIANTS];
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  EffectQueueFragment()
-    { 
-    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  EffectQueueFragment(EffectQueueFragment source)
-    {
-    super(source);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void uniforms(int mProgramH, int variant)
-    {
-    mNumEffectsH[variant]   = GLES30.glGetUniformLocation  ( mProgramH, "fNumEffects");
-    mIntUniformsH[variant]  = GLES30.glGetUniformLocation  ( mProgramH, "fProperties");
-    mFloUniformsH[variant]  = GLES30.glGetUniformLocation  ( mProgramH, "fUniforms");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  void compute(long currTime, long step)
-    {
-    if( mNumEffects>0 )
-      {
-      float[] array = mUBF.getBackingArray();
-
-      for(int i=0; i<mNumEffects; i++)
-        {
-        if( mEffects[i].compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
-          {
-          EffectMessageSender.newMessage(mEffects[i]);
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  
-  void send(int variant)
-    {
-    GLES30.glUniform1i( mNumEffectsH[variant], mNumEffects);
-
-    if( mNumEffects>0 )
-      {
-      int[]   arrayI = mUBI.getBackingArray();
-      float[] arrayF = mUBF.getBackingArray();
-
-      GLES30.glUniform4iv( mIntUniformsH[variant],                       mNumEffects, arrayI, 0);
-      GLES30.glUniform4fv( mFloUniformsH[variant],(NUM_FLOAT_UNIFORMS/4)*mNumEffects, arrayF, 0);
-      }  
-    }
-  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.kt
new file mode 100644
index 0000000..b40c35e
--- /dev/null
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueueFragment.kt
@@ -0,0 +1,98 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// This library is free software; you can redistribute it and/or                                 //
+// modify it under the terms of the GNU Lesser General Public                                    //
+// License as published by the Free Software Foundation; either                                  //
+// version 2.1 of the License, or (at your option) any later version.                            //
+//                                                                                               //
+// This library 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                             //
+// Lesser General Public License for more details.                                               //
+//                                                                                               //
+// You should have received a copy of the GNU Lesser General Public                              //
+// License along with this library; if not, write to the Free Software                           //
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effectqueue;
+
+import android.opengl.GLES30;
+
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.FragmentEffect;
+import org.distorted.library.message.EffectMessageSender;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueFragment extends EffectQueue
+  {
+  private static final int NUM_FLOAT_UNIFORMS = FragmentEffect.NUM_FLOAT_UNIFORMS;
+  private static final int NUM_INT_UNIFORMS   = FragmentEffect.NUM_INT_UNIFORMS;
+  private static final boolean USE_UBO        = false;
+  private static final int INDEX = EffectType.FRAGMENT.ordinal();
+
+  private final static int[] mNumEffectsH  = new int[MAIN_VARIANTS];
+  private final static int[] mIntUniformsH = new int[MAIN_VARIANTS];
+  private final static int[] mFloUniformsH = new int[MAIN_VARIANTS];
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  EffectQueueFragment()
+    { 
+    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  EffectQueueFragment(EffectQueueFragment source)
+    {
+    super(source);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void uniforms(int mProgramH, int variant)
+    {
+    mNumEffectsH[variant]   = GLES30.glGetUniformLocation  ( mProgramH, "fNumEffects");
+    mIntUniformsH[variant]  = GLES30.glGetUniformLocation  ( mProgramH, "fProperties");
+    mFloUniformsH[variant]  = GLES30.glGetUniformLocation  ( mProgramH, "fUniforms");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  void compute(long currTime, long step)
+    {
+    if( mNumEffects>0 )
+      {
+      float[] array = mUBF.getBackingArray();
+
+      for(int i=0; i<mNumEffects; i++)
+        {
+        if( mEffects[i].compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
+          {
+          EffectMessageSender.newMessage(mEffects[i]);
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  
+  void send(int variant)
+    {
+    GLES30.glUniform1i( mNumEffectsH[variant], mNumEffects);
+
+    if( mNumEffects>0 )
+      {
+      int[]   arrayI = mUBI.getBackingArray();
+      float[] arrayF = mUBF.getBackingArray();
+
+      GLES30.glUniform4iv( mIntUniformsH[variant],                       mNumEffects, arrayI, 0);
+      GLES30.glUniform4fv( mFloUniformsH[variant],(NUM_FLOAT_UNIFORMS/4)*mNumEffects, arrayF, 0);
+      }  
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.java b/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.java
deleted file mode 100644
index 014516b..0000000
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.java
+++ /dev/null
@@ -1,125 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// This library is free software; you can redistribute it and/or                                 //
-// modify it under the terms of the GNU Lesser General Public                                    //
-// License as published by the Free Software Foundation; either                                  //
-// version 2.1 of the License, or (at your option) any later version.                            //
-//                                                                                               //
-// This library 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                             //
-// Lesser General Public License for more details.                                               //
-//                                                                                               //
-// You should have received a copy of the GNU Lesser General Public                              //
-// License along with this library; if not, write to the Free Software                           //
-// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library.effectqueue;
-
-import android.opengl.GLES30;
-
-import org.distorted.library.helpers.MatrixHelper;
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.MatrixEffect;
-import org.distorted.library.message.EffectMessageSender;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class EffectQueueMatrix extends EffectQueue
-  {
-  private static final int NUM_FLOAT_UNIFORMS = MatrixEffect.NUM_FLOAT_UNIFORMS;
-  private static final int NUM_INT_UNIFORMS   = MatrixEffect.NUM_INT_UNIFORMS;
-  private static final boolean USE_UBO        = false;
-  private static final int INDEX = EffectType.MATRIX.ordinal();
-
-  private static final float[] mMVPMatrix       = new float[16];
-  private static final float[] mModelViewMatrixP= new float[16];
-  private static final float[] mModelViewMatrixV= new float[16];
-
-  private static final int[] mMVPMatrixH = new int[MAIN_VARIANTS];
-  private static final int[] mMVMatrixPH = new int[MAIN_VARIANTS];
-  private static final int[] mMVMatrixVH = new int[MAIN_VARIANTS];
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  EffectQueueMatrix()
-    { 
-    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  EffectQueueMatrix(EffectQueueMatrix source)
-    {
-    super(source);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void uniforms(int mProgramH, int variant)
-    {
-    mMVPMatrixH[variant]= GLES30.glGetUniformLocation(mProgramH, "u_MVPMatrix");
-    mMVMatrixPH[variant]= GLES30.glGetUniformLocation(mProgramH, "u_MVMatrixP");
-    mMVMatrixVH[variant]= GLES30.glGetUniformLocation(mProgramH, "u_MVMatrixV");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void compute(long currTime, long step)
-    {
-    if( mNumEffects>0 )
-      {
-      float[] array = mUBF.getBackingArray();
-
-      for(int i=0; i<mNumEffects; i++)
-        {
-        if( mEffects[i].compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
-          {
-          EffectMessageSender.newMessage(mEffects[i]);
-          }
-        }
-      }
-    }  
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void send(float distance, float mipmap, float[] projection, int variant)
-    {
-    // i.e. setIdentity(); translate(0,0,-distance); scale(mipmap,mipmap,mipmap)
-    mModelViewMatrixV[ 0] = mModelViewMatrixP[ 0] = mipmap;
-    mModelViewMatrixV[ 1] = mModelViewMatrixP[ 1] = 0;
-    mModelViewMatrixV[ 2] = mModelViewMatrixP[ 2] = 0;
-    mModelViewMatrixV[ 3] = mModelViewMatrixP[ 3] = 0;
-    mModelViewMatrixV[ 4] = mModelViewMatrixP[ 4] = 0;
-    mModelViewMatrixV[ 5] = mModelViewMatrixP[ 5] = mipmap;
-    mModelViewMatrixV[ 6] = mModelViewMatrixP[ 6] = 0;
-    mModelViewMatrixV[ 7] = mModelViewMatrixP[ 7] = 0;
-    mModelViewMatrixV[ 8] = mModelViewMatrixP[ 8] = 0;
-    mModelViewMatrixV[ 9] = mModelViewMatrixP[ 9] = 0;
-    mModelViewMatrixV[10] = mModelViewMatrixP[10] = mipmap;
-    mModelViewMatrixV[11] = mModelViewMatrixP[11] = 0;
-    mModelViewMatrixV[12] = mModelViewMatrixP[12] = 0;
-    mModelViewMatrixV[13] = mModelViewMatrixP[13] = 0;
-    mModelViewMatrixV[14] = mModelViewMatrixP[14] = -distance;
-    mModelViewMatrixV[15] = mModelViewMatrixP[15] = 1;
-
-    float[] array = mUBF.getBackingArray();
-
-    // the 'Model' part of the MV matrix
-    for(int i=mNumEffects-1; i>=0; i--)
-      {
-      ((MatrixEffect)mEffects[i]).apply(mModelViewMatrixP,mModelViewMatrixV,array,i);
-      }
-
-    // combined Model-View-Projection matrix
-    MatrixHelper.multiply(mMVPMatrix, projection, mModelViewMatrixP);
-
-    GLES30.glUniformMatrix4fv(mMVMatrixVH[variant], 1, false, mModelViewMatrixV, 0);
-    GLES30.glUniformMatrix4fv(mMVMatrixPH[variant], 1, false, mModelViewMatrixP, 0);
-    GLES30.glUniformMatrix4fv(mMVPMatrixH[variant], 1, false, mMVPMatrix       , 0);
-    }
-  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.kt
new file mode 100644
index 0000000..014516b
--- /dev/null
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueueMatrix.kt
@@ -0,0 +1,125 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// This library is free software; you can redistribute it and/or                                 //
+// modify it under the terms of the GNU Lesser General Public                                    //
+// License as published by the Free Software Foundation; either                                  //
+// version 2.1 of the License, or (at your option) any later version.                            //
+//                                                                                               //
+// This library 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                             //
+// Lesser General Public License for more details.                                               //
+//                                                                                               //
+// You should have received a copy of the GNU Lesser General Public                              //
+// License along with this library; if not, write to the Free Software                           //
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effectqueue;
+
+import android.opengl.GLES30;
+
+import org.distorted.library.helpers.MatrixHelper;
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.MatrixEffect;
+import org.distorted.library.message.EffectMessageSender;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class EffectQueueMatrix extends EffectQueue
+  {
+  private static final int NUM_FLOAT_UNIFORMS = MatrixEffect.NUM_FLOAT_UNIFORMS;
+  private static final int NUM_INT_UNIFORMS   = MatrixEffect.NUM_INT_UNIFORMS;
+  private static final boolean USE_UBO        = false;
+  private static final int INDEX = EffectType.MATRIX.ordinal();
+
+  private static final float[] mMVPMatrix       = new float[16];
+  private static final float[] mModelViewMatrixP= new float[16];
+  private static final float[] mModelViewMatrixV= new float[16];
+
+  private static final int[] mMVPMatrixH = new int[MAIN_VARIANTS];
+  private static final int[] mMVMatrixPH = new int[MAIN_VARIANTS];
+  private static final int[] mMVMatrixVH = new int[MAIN_VARIANTS];
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  EffectQueueMatrix()
+    { 
+    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  EffectQueueMatrix(EffectQueueMatrix source)
+    {
+    super(source);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void uniforms(int mProgramH, int variant)
+    {
+    mMVPMatrixH[variant]= GLES30.glGetUniformLocation(mProgramH, "u_MVPMatrix");
+    mMVMatrixPH[variant]= GLES30.glGetUniformLocation(mProgramH, "u_MVMatrixP");
+    mMVMatrixVH[variant]= GLES30.glGetUniformLocation(mProgramH, "u_MVMatrixV");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void compute(long currTime, long step)
+    {
+    if( mNumEffects>0 )
+      {
+      float[] array = mUBF.getBackingArray();
+
+      for(int i=0; i<mNumEffects; i++)
+        {
+        if( mEffects[i].compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
+          {
+          EffectMessageSender.newMessage(mEffects[i]);
+          }
+        }
+      }
+    }  
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void send(float distance, float mipmap, float[] projection, int variant)
+    {
+    // i.e. setIdentity(); translate(0,0,-distance); scale(mipmap,mipmap,mipmap)
+    mModelViewMatrixV[ 0] = mModelViewMatrixP[ 0] = mipmap;
+    mModelViewMatrixV[ 1] = mModelViewMatrixP[ 1] = 0;
+    mModelViewMatrixV[ 2] = mModelViewMatrixP[ 2] = 0;
+    mModelViewMatrixV[ 3] = mModelViewMatrixP[ 3] = 0;
+    mModelViewMatrixV[ 4] = mModelViewMatrixP[ 4] = 0;
+    mModelViewMatrixV[ 5] = mModelViewMatrixP[ 5] = mipmap;
+    mModelViewMatrixV[ 6] = mModelViewMatrixP[ 6] = 0;
+    mModelViewMatrixV[ 7] = mModelViewMatrixP[ 7] = 0;
+    mModelViewMatrixV[ 8] = mModelViewMatrixP[ 8] = 0;
+    mModelViewMatrixV[ 9] = mModelViewMatrixP[ 9] = 0;
+    mModelViewMatrixV[10] = mModelViewMatrixP[10] = mipmap;
+    mModelViewMatrixV[11] = mModelViewMatrixP[11] = 0;
+    mModelViewMatrixV[12] = mModelViewMatrixP[12] = 0;
+    mModelViewMatrixV[13] = mModelViewMatrixP[13] = 0;
+    mModelViewMatrixV[14] = mModelViewMatrixP[14] = -distance;
+    mModelViewMatrixV[15] = mModelViewMatrixP[15] = 1;
+
+    float[] array = mUBF.getBackingArray();
+
+    // the 'Model' part of the MV matrix
+    for(int i=mNumEffects-1; i>=0; i--)
+      {
+      ((MatrixEffect)mEffects[i]).apply(mModelViewMatrixP,mModelViewMatrixV,array,i);
+      }
+
+    // combined Model-View-Projection matrix
+    MatrixHelper.multiply(mMVPMatrix, projection, mModelViewMatrixP);
+
+    GLES30.glUniformMatrix4fv(mMVMatrixVH[variant], 1, false, mModelViewMatrixV, 0);
+    GLES30.glUniformMatrix4fv(mMVMatrixPH[variant], 1, false, mModelViewMatrixP, 0);
+    GLES30.glUniformMatrix4fv(mMVPMatrixH[variant], 1, false, mMVPMatrix       , 0);
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
deleted file mode 100644
index 05c4d6e..0000000
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.java
+++ /dev/null
@@ -1,236 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// This library is free software; you can redistribute it and/or                                 //
-// modify it under the terms of the GNU Lesser General Public                                    //
-// License as published by the Free Software Foundation; either                                  //
-// version 2.1 of the License, or (at your option) any later version.                            //
-//                                                                                               //
-// This library 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                             //
-// Lesser General Public License for more details.                                               //
-//                                                                                               //
-// You should have received a copy of the GNU Lesser General Public                              //
-// License along with this library; if not, write to the Free Software                           //
-// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library.effectqueue;
-
-import android.opengl.GLES30;
-
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.PostprocessEffect;
-import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.library.main.DistortedFramebuffer;
-import org.distorted.library.main.DistortedNode;
-import org.distorted.library.main.InternalOutputSurface;
-import org.distorted.library.main.InternalRenderState;
-import org.distorted.library.mesh.MeshBase;
-import org.distorted.library.message.EffectMessageSender;
-import org.distorted.library.program.DistortedProgram;
-
-import java.io.InputStream;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document
- *
- * @y.exclude
- */
-public class EffectQueuePostprocess extends EffectQueue
-  {
-  private static final int NUM_FLOAT_UNIFORMS = PostprocessEffect.NUM_FLOAT_UNIFORMS;
-  private static final int NUM_INT_UNIFORMS   = PostprocessEffect.NUM_INT_UNIFORMS;
-  private static final boolean USE_UBO        = false;
-  private static final int INDEX = EffectType.POSTPROCESS.ordinal();
-
-  private int mHalo;
-  private boolean mUseHaloDepth;
-  private float mR, mG, mB, mA;
-
-  private static DistortedProgram mPreProgram;
-  private static int mPreColorH;
-  private static int mPreTextureH;
-  private static int mPreProgramH;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  EffectQueuePostprocess()
-    { 
-    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  EffectQueuePostprocess(EffectQueuePostprocess source)
-    {
-    super(source);
-
-    mHalo = source.mHalo;
-    mUseHaloDepth = source.mUseHaloDepth;
-    mR = source.mR;
-    mG = source.mG;
-    mB = source.mB;
-    mA = source.mA;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void compute(long currTime, long step)
-    {
-    mR = mG = mB = mA = 0.0f;
-    mHalo = 0;
-    int halo;
-    float[] array = mUBF.getBackingArray();
-
-    for(int i=0; i<mNumEffects; i++)
-      {
-      // first zero out the 'alpha' because BLUR effect will not overwrite this (it is a 1D effect)
-      // and if previously there was a GLOW effect here then mA would be non-zero and we don't want
-      // that (see preprocess())
-      array[NUM_FLOAT_UNIFORMS*i+5]=0.0f;
-
-      PostprocessEffect effect = (PostprocessEffect)mEffects[i];
-
-      if( effect.compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
-        {
-        EffectMessageSender.newMessage(mEffects[i]);
-        }
-
-      halo = (int)array[NUM_FLOAT_UNIFORMS*i];
-      if( halo>mHalo ) mHalo = halo;
-
-      // TODO  (now only really works in case of 1 effect!)
-      mUseHaloDepth = effect.getHaloDepth();
-      }
-
-    // TODO  (now only really works in case of 1 effect!)
-    if( mNumEffects>0 )
-      {
-      mR = array[2];
-      mG = array[3];
-      mB = array[4];
-      mA = array[5];
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void createPrograms(InputStream vert, InputStream frag, int GLSL)
-    {
-    int numV = VertexEffect.getNumEnabled();
-
-    String version = "#version "+GLSL+" es\n";
-    String mainVertHeader= version + ("#define NUM_VERTEX " + ( numV>0 ? DistortedLibrary.getMax(EffectType.VERTEX  ) : 0 ) + "\n");
-    String mainFragHeader= version + "\n";
-
-    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
-    if( MeshBase.getUseCenters() )      mainVertHeader += "#define COMP_CENTERS\n";
-    if( DistortedLibrary.isUBOBuggy() ) mainVertHeader += "#define BUGGY_UBOS\n";
-    mainVertHeader += "#define POSTPROCESS\n";
-
-    String enabledEffectV= VertexEffect.getGLSL();
-
-    try
-      {
-      mPreProgram = new DistortedProgram(vert, frag, mainVertHeader, mainFragHeader,
-                                         enabledEffectV, null, GLSL, null);
-      }
-    catch(Exception e)
-      {
-      DistortedLibrary.logMessage("EffectQueuePostprocess "+ e.getClass().getSimpleName()+" trying to compile PRE program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    mPreProgramH = mPreProgram.getProgramHandle();
-    EffectQueue.getUniforms( mPreProgramH,2 );
-    MeshBase.getUniforms( mPreProgramH,2 );
-    mPreColorH  = GLES30.glGetUniformLocation( mPreProgramH, "u_Color"  );
-    mPreTextureH= GLES30.glGetUniformLocation( mPreProgramH, "u_Texture");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO  (now only really works in case of 1 effect!)
-
-  public int getQuality()
-    {
-    return mNumEffects>0 ? ((PostprocessEffect)mEffects[0]).getQuality() : 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO  (now only really works in case of 1 effect!)
-
-  public boolean getRenderDirectly()
-    {
-    return mNumEffects>0 && ((PostprocessEffect) mEffects[0]).getRenderDirectly();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int preprocess(InternalOutputSurface buffer, DistortedNode node, float distance, float mipmap, float[] projection)
-    {
-    MeshBase mesh = node.getMesh();
-    DistortedEffects effects = node.getEffects();
-
-    int width   = buffer.getWidth();
-    int height  = buffer.getHeight();
-
-    InternalRenderState.setUpStencilMark(mA!=0.0f);
-    InternalRenderState.disableBlending();
-    if( !mUseHaloDepth ) GLES30.glDepthMask(false);
-
-    GLES30.glViewport(0, 0, width, height );
-
-    mPreProgram.useProgram();
-
-    mesh.bindVertexAttribs(mPreProgram);
-    mesh.send(mPreProgramH,2);
-
-    EffectQueue[] queues = effects.getQueues();
-    EffectQueueMatrix matrix = (EffectQueueMatrix)queues[0];
-    EffectQueueVertex vertex = (EffectQueueVertex)queues[1];
-
-    matrix.send(distance, mipmap, projection, 2);
-    vertex.send(mHalo*0.01f,mPreProgramH,2);
-
-    if( mA!=0.0f )
-      {
-      GLES30.glUniform4f(mPreColorH, mR, mG, mB, mA);
-      GLES30.glUniform1i(mPreTextureH, 0);
-      }
-
-    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
-    mPreProgram.stopUsingProgram();
-
-    InternalRenderState.restoreBlending();
-    InternalRenderState.unsetUpStencilMark();
-    if( !mUseHaloDepth ) GLES30.glDepthMask(true);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int postprocess(DistortedFramebuffer buffer)
-    {
-    int numRenders = 0;
-    float[] array = mUBF.getBackingArray();
-
-    GLES30.glDisable(GLES30.GL_BLEND);
-
-    for(int i=0; i<mNumEffects; i++)
-      {
-      numRenders += ((PostprocessEffect)mEffects[i]).postprocess(array,NUM_FLOAT_UNIFORMS*i, buffer);
-      }
-
-    GLES30.glEnable(GLES30.GL_BLEND);
-
-    return numRenders;
-    }
-  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.kt
new file mode 100644
index 0000000..05c4d6e
--- /dev/null
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueuePostprocess.kt
@@ -0,0 +1,236 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// This library is free software; you can redistribute it and/or                                 //
+// modify it under the terms of the GNU Lesser General Public                                    //
+// License as published by the Free Software Foundation; either                                  //
+// version 2.1 of the License, or (at your option) any later version.                            //
+//                                                                                               //
+// This library 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                             //
+// Lesser General Public License for more details.                                               //
+//                                                                                               //
+// You should have received a copy of the GNU Lesser General Public                              //
+// License along with this library; if not, write to the Free Software                           //
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effectqueue;
+
+import android.opengl.GLES30;
+
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.PostprocessEffect;
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.DistortedFramebuffer;
+import org.distorted.library.main.DistortedNode;
+import org.distorted.library.main.InternalOutputSurface;
+import org.distorted.library.main.InternalRenderState;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.message.EffectMessageSender;
+import org.distorted.library.program.DistortedProgram;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document
+ *
+ * @y.exclude
+ */
+public class EffectQueuePostprocess extends EffectQueue
+  {
+  private static final int NUM_FLOAT_UNIFORMS = PostprocessEffect.NUM_FLOAT_UNIFORMS;
+  private static final int NUM_INT_UNIFORMS   = PostprocessEffect.NUM_INT_UNIFORMS;
+  private static final boolean USE_UBO        = false;
+  private static final int INDEX = EffectType.POSTPROCESS.ordinal();
+
+  private int mHalo;
+  private boolean mUseHaloDepth;
+  private float mR, mG, mB, mA;
+
+  private static DistortedProgram mPreProgram;
+  private static int mPreColorH;
+  private static int mPreTextureH;
+  private static int mPreProgramH;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  EffectQueuePostprocess()
+    { 
+    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  EffectQueuePostprocess(EffectQueuePostprocess source)
+    {
+    super(source);
+
+    mHalo = source.mHalo;
+    mUseHaloDepth = source.mUseHaloDepth;
+    mR = source.mR;
+    mG = source.mG;
+    mB = source.mB;
+    mA = source.mA;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void compute(long currTime, long step)
+    {
+    mR = mG = mB = mA = 0.0f;
+    mHalo = 0;
+    int halo;
+    float[] array = mUBF.getBackingArray();
+
+    for(int i=0; i<mNumEffects; i++)
+      {
+      // first zero out the 'alpha' because BLUR effect will not overwrite this (it is a 1D effect)
+      // and if previously there was a GLOW effect here then mA would be non-zero and we don't want
+      // that (see preprocess())
+      array[NUM_FLOAT_UNIFORMS*i+5]=0.0f;
+
+      PostprocessEffect effect = (PostprocessEffect)mEffects[i];
+
+      if( effect.compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
+        {
+        EffectMessageSender.newMessage(mEffects[i]);
+        }
+
+      halo = (int)array[NUM_FLOAT_UNIFORMS*i];
+      if( halo>mHalo ) mHalo = halo;
+
+      // TODO  (now only really works in case of 1 effect!)
+      mUseHaloDepth = effect.getHaloDepth();
+      }
+
+    // TODO  (now only really works in case of 1 effect!)
+    if( mNumEffects>0 )
+      {
+      mR = array[2];
+      mG = array[3];
+      mB = array[4];
+      mA = array[5];
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void createPrograms(InputStream vert, InputStream frag, int GLSL)
+    {
+    int numV = VertexEffect.getNumEnabled();
+
+    String version = "#version "+GLSL+" es\n";
+    String mainVertHeader= version + ("#define NUM_VERTEX " + ( numV>0 ? DistortedLibrary.getMax(EffectType.VERTEX  ) : 0 ) + "\n");
+    String mainFragHeader= version + "\n";
+
+    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
+    if( MeshBase.getUseCenters() )      mainVertHeader += "#define COMP_CENTERS\n";
+    if( DistortedLibrary.isUBOBuggy() ) mainVertHeader += "#define BUGGY_UBOS\n";
+    mainVertHeader += "#define POSTPROCESS\n";
+
+    String enabledEffectV= VertexEffect.getGLSL();
+
+    try
+      {
+      mPreProgram = new DistortedProgram(vert, frag, mainVertHeader, mainFragHeader,
+                                         enabledEffectV, null, GLSL, null);
+      }
+    catch(Exception e)
+      {
+      DistortedLibrary.logMessage("EffectQueuePostprocess "+ e.getClass().getSimpleName()+" trying to compile PRE program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    mPreProgramH = mPreProgram.getProgramHandle();
+    EffectQueue.getUniforms( mPreProgramH,2 );
+    MeshBase.getUniforms( mPreProgramH,2 );
+    mPreColorH  = GLES30.glGetUniformLocation( mPreProgramH, "u_Color"  );
+    mPreTextureH= GLES30.glGetUniformLocation( mPreProgramH, "u_Texture");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO  (now only really works in case of 1 effect!)
+
+  public int getQuality()
+    {
+    return mNumEffects>0 ? ((PostprocessEffect)mEffects[0]).getQuality() : 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO  (now only really works in case of 1 effect!)
+
+  public boolean getRenderDirectly()
+    {
+    return mNumEffects>0 && ((PostprocessEffect) mEffects[0]).getRenderDirectly();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int preprocess(InternalOutputSurface buffer, DistortedNode node, float distance, float mipmap, float[] projection)
+    {
+    MeshBase mesh = node.getMesh();
+    DistortedEffects effects = node.getEffects();
+
+    int width   = buffer.getWidth();
+    int height  = buffer.getHeight();
+
+    InternalRenderState.setUpStencilMark(mA!=0.0f);
+    InternalRenderState.disableBlending();
+    if( !mUseHaloDepth ) GLES30.glDepthMask(false);
+
+    GLES30.glViewport(0, 0, width, height );
+
+    mPreProgram.useProgram();
+
+    mesh.bindVertexAttribs(mPreProgram);
+    mesh.send(mPreProgramH,2);
+
+    EffectQueue[] queues = effects.getQueues();
+    EffectQueueMatrix matrix = (EffectQueueMatrix)queues[0];
+    EffectQueueVertex vertex = (EffectQueueVertex)queues[1];
+
+    matrix.send(distance, mipmap, projection, 2);
+    vertex.send(mHalo*0.01f,mPreProgramH,2);
+
+    if( mA!=0.0f )
+      {
+      GLES30.glUniform4f(mPreColorH, mR, mG, mB, mA);
+      GLES30.glUniform1i(mPreTextureH, 0);
+      }
+
+    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
+    mPreProgram.stopUsingProgram();
+
+    InternalRenderState.restoreBlending();
+    InternalRenderState.unsetUpStencilMark();
+    if( !mUseHaloDepth ) GLES30.glDepthMask(true);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int postprocess(DistortedFramebuffer buffer)
+    {
+    int numRenders = 0;
+    float[] array = mUBF.getBackingArray();
+
+    GLES30.glDisable(GLES30.GL_BLEND);
+
+    for(int i=0; i<mNumEffects; i++)
+      {
+      numRenders += ((PostprocessEffect)mEffects[i]).postprocess(array,NUM_FLOAT_UNIFORMS*i, buffer);
+      }
+
+    GLES30.glEnable(GLES30.GL_BLEND);
+
+    return numRenders;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.java b/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.java
deleted file mode 100644
index e4e620f..0000000
--- a/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.java
+++ /dev/null
@@ -1,116 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// This library is free software; you can redistribute it and/or                                 //
-// modify it under the terms of the GNU Lesser General Public                                    //
-// License as published by the Free Software Foundation; either                                  //
-// version 2.1 of the License, or (at your option) any later version.                            //
-//                                                                                               //
-// This library 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                             //
-// Lesser General Public License for more details.                                               //
-//                                                                                               //
-// You should have received a copy of the GNU Lesser General Public                              //
-// License along with this library; if not, write to the Free Software                           //
-// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library.effectqueue;
-
-import android.opengl.GLES30;
-
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.message.EffectMessageSender;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used in Meshes)
- *
- * @y.exclude
- */
-public class EffectQueueVertex extends EffectQueue
-  {
-  private static final int NUM_FLOAT_UNIFORMS = VertexEffect.NUM_FLOAT_UNIFORMS;
-  private static final int NUM_INT_UNIFORMS   = VertexEffect.NUM_INT_UNIFORMS;
-  private static final boolean USE_UBO        = true;
-
-  private static final int INDEX = EffectType.VERTEX.ordinal();
-
-  private final static int[] mNumEffectsH  = new int[MAIN_VARIANTS];
-  private final static int[] mInflateH     = new int[MAIN_VARIANTS];
-  private final static int[] mIntBlockIndex= new int[MAIN_VARIANTS];
-  private final static int[] mFloBlockIndex= new int[MAIN_VARIANTS];
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-   
-  public EffectQueueVertex()
-    { 
-    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public EffectQueueVertex(EffectQueueVertex source)
-    {
-    super(source);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void uniforms(int mProgramH, int variant)
-    {
-    mNumEffectsH[variant]   = GLES30.glGetUniformLocation  ( mProgramH, "vNumEffects");
-    mInflateH[variant]      = GLES30.glGetUniformLocation  ( mProgramH, "u_Inflate");
-    mIntBlockIndex[variant] = GLES30.glGetUniformBlockIndex( mProgramH, "vUniformProperties");
-    mFloBlockIndex[variant] = GLES30.glGetUniformBlockIndex( mProgramH, "vUniformFloats");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used in Meshes)
- *
- * @y.exclude
- */
-  public void compute(long currTime, long step)
-    {
-    if( mNumEffects>0 )
-      {
-      float[] array = mUBF.getBackingArray();
-
-      for(int i=0; i<mNumEffects; i++)
-        {
-        if( mEffects[i].compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
-          {
-          EffectMessageSender.newMessage(mEffects[i]);
-          }
-        }
-
-      mUBF.invalidate();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of public API, do not document (public only because has to be used in Meshes)
- *
- * @y.exclude
- */
-  public void send(float inflate, int programH, int variant)
-    {
-    GLES30.glUniform1i( mNumEffectsH[variant], mNumEffects);
-    GLES30.glUniform1f( mInflateH[variant]   , inflate    );
-
-    if( mNumEffects>0 )
-      {
-      GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, VERT_INT_UBO_BINDING, mUBI.getIndex());
-      GLES30.glUniformBlockBinding(programH, mIntBlockIndex[variant], VERT_INT_UBO_BINDING);
-
-      GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, VERT_FLO_UBO_BINDING, mUBF.getIndex());
-      GLES30.glUniformBlockBinding(programH, mFloBlockIndex[variant], VERT_FLO_UBO_BINDING);
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.kt b/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.kt
new file mode 100644
index 0000000..e4e620f
--- /dev/null
+++ b/src/main/java/org/distorted/library/effectqueue/EffectQueueVertex.kt
@@ -0,0 +1,116 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2016 Leszek Koltunski  leszek@koltunski.pl                                          //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// This library is free software; you can redistribute it and/or                                 //
+// modify it under the terms of the GNU Lesser General Public                                    //
+// License as published by the Free Software Foundation; either                                  //
+// version 2.1 of the License, or (at your option) any later version.                            //
+//                                                                                               //
+// This library 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                             //
+// Lesser General Public License for more details.                                               //
+//                                                                                               //
+// You should have received a copy of the GNU Lesser General Public                              //
+// License along with this library; if not, write to the Free Software                           //
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA                //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.library.effectqueue;
+
+import android.opengl.GLES30;
+
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.message.EffectMessageSender;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used in Meshes)
+ *
+ * @y.exclude
+ */
+public class EffectQueueVertex extends EffectQueue
+  {
+  private static final int NUM_FLOAT_UNIFORMS = VertexEffect.NUM_FLOAT_UNIFORMS;
+  private static final int NUM_INT_UNIFORMS   = VertexEffect.NUM_INT_UNIFORMS;
+  private static final boolean USE_UBO        = true;
+
+  private static final int INDEX = EffectType.VERTEX.ordinal();
+
+  private final static int[] mNumEffectsH  = new int[MAIN_VARIANTS];
+  private final static int[] mInflateH     = new int[MAIN_VARIANTS];
+  private final static int[] mIntBlockIndex= new int[MAIN_VARIANTS];
+  private final static int[] mFloBlockIndex= new int[MAIN_VARIANTS];
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+   
+  public EffectQueueVertex()
+    { 
+    super(NUM_FLOAT_UNIFORMS, NUM_INT_UNIFORMS, USE_UBO, INDEX);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public EffectQueueVertex(EffectQueueVertex source)
+    {
+    super(source);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void uniforms(int mProgramH, int variant)
+    {
+    mNumEffectsH[variant]   = GLES30.glGetUniformLocation  ( mProgramH, "vNumEffects");
+    mInflateH[variant]      = GLES30.glGetUniformLocation  ( mProgramH, "u_Inflate");
+    mIntBlockIndex[variant] = GLES30.glGetUniformBlockIndex( mProgramH, "vUniformProperties");
+    mFloBlockIndex[variant] = GLES30.glGetUniformBlockIndex( mProgramH, "vUniformFloats");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used in Meshes)
+ *
+ * @y.exclude
+ */
+  public void compute(long currTime, long step)
+    {
+    if( mNumEffects>0 )
+      {
+      float[] array = mUBF.getBackingArray();
+
+      for(int i=0; i<mNumEffects; i++)
+        {
+        if( mEffects[i].compute(array, NUM_FLOAT_UNIFORMS*i, currTime, step) )
+          {
+          EffectMessageSender.newMessage(mEffects[i]);
+          }
+        }
+
+      mUBF.invalidate();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of public API, do not document (public only because has to be used in Meshes)
+ *
+ * @y.exclude
+ */
+  public void send(float inflate, int programH, int variant)
+    {
+    GLES30.glUniform1i( mNumEffectsH[variant], mNumEffects);
+    GLES30.glUniform1f( mInflateH[variant]   , inflate    );
+
+    if( mNumEffects>0 )
+      {
+      GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, VERT_INT_UBO_BINDING, mUBI.getIndex());
+      GLES30.glUniformBlockBinding(programH, mIntBlockIndex[variant], VERT_INT_UBO_BINDING);
+
+      GLES30.glBindBufferBase(GLES30.GL_UNIFORM_BUFFER, VERT_FLO_UBO_BINDING, mUBF.getIndex());
+      GLES30.glUniformBlockBinding(programH, mFloBlockIndex[variant], VERT_FLO_UBO_BINDING);
+      }
+    }
+  }
