commit 7f92dd3a539b7d50670014bb1f5b07b9137791a0
Author: LeszekKoltunski <leszek@koltunski.pl>
Date:   Fri May 30 15:15:17 2025 +0200

    Rename .java to .kt

diff --git a/src/main/java/org/distorted/library/main/DistortedEffects.java b/src/main/java/org/distorted/library/main/DistortedEffects.java
deleted file mode 100644
index e30ae49..0000000
--- a/src/main/java/org/distorted/library/main/DistortedEffects.java
+++ /dev/null
@@ -1,235 +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.main;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.EffectName;
-import org.distorted.library.effectqueue.EffectQueue;
-import org.distorted.library.effect.EffectType;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Class containing Matrix, Vertex, Fragment and Postprocessing effect queues.
- * <p>
- * The queues hold actual effects to be applied to a given (InputSurface,MeshBase) combo.
- */
-public class DistortedEffects
-  {
-  private final long mID;
-  private final EffectQueue[] mQueues;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * @y.exclude
- */
-  public EffectQueue[] getQueues()
-    {
-    return mQueues;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create empty effect queue.
- */
- public DistortedEffects()
-    {
-    mID = InternalStackFrameList.getNextEffectsID();
-    mQueues = EffectQueue.createQueues();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Copy constructor.
- * <p>
- * Whatever we do not clone gets created just like in the default constructor.
- *
- * @param dc    Source object to create our object from
- * @param flags A bitmask of values specifying what to copy.
- *              For example, CLONE_VERTEX | CLONE_MATRIX.
- */
-  public DistortedEffects(DistortedEffects dc, int flags)
-    {
-    mID = InternalStackFrameList.getNextEffectsID();
-    mQueues = EffectQueue.createQueues(dc.getQueues(),flags);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns unique ID of this instance.
- *
- * @return ID of the object.
- */
-  public long getID()
-      {
-      return mID;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return if this queue contains effect with a given ID.
- */
-  public boolean exists(long id)
-    {
-    int num = (int)(id&EffectType.MASK);
-    return mQueues[num].exists(id);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts all Effects.
- * @return Number of effects aborted.
- */
-  public int abortAllEffects()
-    {
-    int aborted = 0;
-
-    for( int i=0; i<EffectType.LENGTH; i++)
-      {
-      aborted += mQueues[i].removeAll(true);
-      }
-
-    return aborted;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts all Effects of a given type, for example all MATRIX Effects.
- * 
- * @param type one of the constants defined in {@link EffectType}
- * @return Number of effects aborted.
- */
-  public int abortByType(EffectType type)
-    {
-    int num = type.ordinal();
-    return mQueues[num].removeAll(true);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts an Effect by its ID.
- *
- * @param id the Id of the Effect to be removed, as returned by getID().
- * @return Number of effects aborted.
- */
-  public int abortById(long id)
-    {
-    int num = (int)(id&EffectType.MASK);
-    return mQueues[num].removeById(id);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Aborts a single Effect.
- * 
- * @param effect the Effect we want to abort.
- * @return number of Effects aborted. Always either 0 or 1.
- */
-  public int abortEffect(Effect effect)
-    {
-    int num = effect.getType().ordinal();
-    return mQueues[num].removeEffect(effect);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Abort all Effects of a given name, for example all rotations.
- * 
- * @param name one of the constants defined in {@link EffectName}
- * @return number of Effects aborted.
- */
-  public int abortByName(EffectName name)
-    {
-    int num = name.getType().ordinal();
-    return mQueues[num].removeByName(name);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Add a new Effect to the tail of our queue.
- *
- * @param effect The Effect to add.
- * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
- */
-  public boolean apply(Effect effect)
-    {
-    int num = effect.getType().ordinal();
-    return mQueues[num].add(effect);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Add a new Effect to our queue at a specified position.
- *
- * @param effect The Effect to add.
- * @param position the place in the effects queue where to add the new effect.
- * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
- */
-  public boolean apply(Effect effect, int position)
-    {
-    int num = effect.getType().ordinal();
-    return mQueues[num].add(effect,position);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
- */
-  public void markForDeletion()
-    {
-    for( int i=0; i<EffectType.LENGTH; i++)
-      {
-      mQueues[i].markForDeletion();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return number of effects of the given type currently in the Queue.
- *
- * @param type The EffectType.
- * @return Number of effects of the given type currently in the Queue.
- */
-  public int getNumEffects(EffectType type)
-    {
-    int num = type.ordinal();
-    return mQueues[num].getNumEffects();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return a string describing all effects in the queues.
- */
-  public String debug(int depth)
-    {
-    StringBuilder s = new StringBuilder();
-    for(int i=0; i<depth; i++) s.append(" ");
-    String space = s.toString();
-
-    String mat = mQueues[0].retEffects();
-    String ver = mQueues[1].retEffects();
-    String fra = mQueues[2].retEffects();
-    String pos = mQueues[3].retEffects();
-
-    return space+"MAT: "+mat+"\n"+space+"VER: "+ver+"\n"+space+"FRA: "+fra+"\n"+space+"POS: "+pos;
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/DistortedEffects.kt b/src/main/java/org/distorted/library/main/DistortedEffects.kt
new file mode 100644
index 0000000..e30ae49
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/DistortedEffects.kt
@@ -0,0 +1,235 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.EffectName;
+import org.distorted.library.effectqueue.EffectQueue;
+import org.distorted.library.effect.EffectType;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class containing Matrix, Vertex, Fragment and Postprocessing effect queues.
+ * <p>
+ * The queues hold actual effects to be applied to a given (InputSurface,MeshBase) combo.
+ */
+public class DistortedEffects
+  {
+  private final long mID;
+  private final EffectQueue[] mQueues;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * @y.exclude
+ */
+  public EffectQueue[] getQueues()
+    {
+    return mQueues;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create empty effect queue.
+ */
+ public DistortedEffects()
+    {
+    mID = InternalStackFrameList.getNextEffectsID();
+    mQueues = EffectQueue.createQueues();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Copy constructor.
+ * <p>
+ * Whatever we do not clone gets created just like in the default constructor.
+ *
+ * @param dc    Source object to create our object from
+ * @param flags A bitmask of values specifying what to copy.
+ *              For example, CLONE_VERTEX | CLONE_MATRIX.
+ */
+  public DistortedEffects(DistortedEffects dc, int flags)
+    {
+    mID = InternalStackFrameList.getNextEffectsID();
+    mQueues = EffectQueue.createQueues(dc.getQueues(),flags);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns unique ID of this instance.
+ *
+ * @return ID of the object.
+ */
+  public long getID()
+      {
+      return mID;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return if this queue contains effect with a given ID.
+ */
+  public boolean exists(long id)
+    {
+    int num = (int)(id&EffectType.MASK);
+    return mQueues[num].exists(id);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts all Effects.
+ * @return Number of effects aborted.
+ */
+  public int abortAllEffects()
+    {
+    int aborted = 0;
+
+    for( int i=0; i<EffectType.LENGTH; i++)
+      {
+      aborted += mQueues[i].removeAll(true);
+      }
+
+    return aborted;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts all Effects of a given type, for example all MATRIX Effects.
+ * 
+ * @param type one of the constants defined in {@link EffectType}
+ * @return Number of effects aborted.
+ */
+  public int abortByType(EffectType type)
+    {
+    int num = type.ordinal();
+    return mQueues[num].removeAll(true);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts an Effect by its ID.
+ *
+ * @param id the Id of the Effect to be removed, as returned by getID().
+ * @return Number of effects aborted.
+ */
+  public int abortById(long id)
+    {
+    int num = (int)(id&EffectType.MASK);
+    return mQueues[num].removeById(id);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Aborts a single Effect.
+ * 
+ * @param effect the Effect we want to abort.
+ * @return number of Effects aborted. Always either 0 or 1.
+ */
+  public int abortEffect(Effect effect)
+    {
+    int num = effect.getType().ordinal();
+    return mQueues[num].removeEffect(effect);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Abort all Effects of a given name, for example all rotations.
+ * 
+ * @param name one of the constants defined in {@link EffectName}
+ * @return number of Effects aborted.
+ */
+  public int abortByName(EffectName name)
+    {
+    int num = name.getType().ordinal();
+    return mQueues[num].removeByName(name);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Add a new Effect to the tail of our queue.
+ *
+ * @param effect The Effect to add.
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  public boolean apply(Effect effect)
+    {
+    int num = effect.getType().ordinal();
+    return mQueues[num].add(effect);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Add a new Effect to our queue at a specified position.
+ *
+ * @param effect The Effect to add.
+ * @param position the place in the effects queue where to add the new effect.
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  public boolean apply(Effect effect, int position)
+    {
+    int num = effect.getType().ordinal();
+    return mQueues[num].add(effect,position);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
+ */
+  public void markForDeletion()
+    {
+    for( int i=0; i<EffectType.LENGTH; i++)
+      {
+      mQueues[i].markForDeletion();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return number of effects of the given type currently in the Queue.
+ *
+ * @param type The EffectType.
+ * @return Number of effects of the given type currently in the Queue.
+ */
+  public int getNumEffects(EffectType type)
+    {
+    int num = type.ordinal();
+    return mQueues[num].getNumEffects();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return a string describing all effects in the queues.
+ */
+  public String debug(int depth)
+    {
+    StringBuilder s = new StringBuilder();
+    for(int i=0; i<depth; i++) s.append(" ");
+    String space = s.toString();
+
+    String mat = mQueues[0].retEffects();
+    String ver = mQueues[1].retEffects();
+    String fra = mQueues[2].retEffects();
+    String pos = mQueues[3].retEffects();
+
+    return space+"MAT: "+mat+"\n"+space+"VER: "+ver+"\n"+space+"FRA: "+fra+"\n"+space+"POS: "+pos;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java b/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
deleted file mode 100644
index 674f884..0000000
--- a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
+++ /dev/null
@@ -1,366 +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.main;
-
-import android.opengl.GLES30;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Class which represents a OpenGL Framebuffer object.
- * <p>
- * User is able to create offscreen FBOs and both a) render to them b) use their COLOR0 attachment as
- * an input texture. Attaching Depths and/or Stencils is also possible.
- */
-public class DistortedFramebuffer extends InternalOutputSurface
-  {
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Must be called from a thread holding OpenGL Context
-
-  public void create()
-    {
-    if( mNumFBOs==DistortedLibrary.WAIT_FOR_FBO_QUEUE_SIZE )
-      {
-      // only now we know how many FBOs there should be
-      mNumFBOs = DistortedLibrary.getQueueSize();
-      allocateColor();
-      allocateStuffDependantOnNumFBOS();
-      }
-
-    //////////////////////////////////////////////////////////////
-    // COLOR
-
-    if( mColorCreated==NOT_CREATED_YET )
-      {
-      GLES30.glGenTextures( mNumFBOs*mNumColors, mColorH, 0);
-      GLES30.glGenFramebuffers(mNumFBOs, mFBOH, 0);
-
-      for(int i=0; i<mNumFBOs; i++)
-        {
-        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[i]);
-
-        for(int j=0; j<mNumColors; j++)
-          {
-          GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[i*mNumColors+j]);
-          GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT);
-          GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_REPEAT);
-          GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
-          GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
-          GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, mRealWidth, mRealHeight, 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
-          }
-
-        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[i*mNumColors], 0);
-        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-        }
-
-      // TODO
-      mColorCreated = checkStatus("color");
-      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
-      }
-
-    //////////////////////////////////////////////////////////////
-    // DEPTH / STENCIL
-
-    if( mDepthStencilCreated==NOT_CREATED_YET ) // we need to create a new DEPTH or STENCIL attachment
-      {
-      GLES30.glGenTextures(mNumFBOs, mDepthStencilH, 0);
-
-      for(int i=0; i<mNumFBOs; i++)
-        {
-        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mDepthStencilH[i]);
-        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT);
-        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_REPEAT);
-        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
-        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_NEAREST);
-
-        if (mDepthStencil == DEPTH_NO_STENCIL)
-          {
-          GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_DEPTH_COMPONENT, mRealWidth, mRealHeight, 0, GLES30.GL_DEPTH_COMPONENT, GLES30.GL_UNSIGNED_INT, null);
-          }
-        else if (mDepthStencil == BOTH_DEPTH_STENCIL)
-          {
-          GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_DEPTH24_STENCIL8, mRealWidth, mRealHeight, 0, GLES30.GL_DEPTH_STENCIL, GLES30.GL_UNSIGNED_INT_24_8, null);
-          }
-        }
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-
-      for(int i=0; i<mNumFBOs; i++)
-        {
-        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[i]);
-
-        if (mDepthStencil == DEPTH_NO_STENCIL)
-          {
-          GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_ATTACHMENT, GLES30.GL_TEXTURE_2D, mDepthStencilH[i], 0);
-          }
-        else if (mDepthStencil == BOTH_DEPTH_STENCIL)
-          {
-          GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_STENCIL_ATTACHMENT, GLES30.GL_TEXTURE_2D, mDepthStencilH[i], 0);
-          }
-        }
-
-      // TODO
-      mDepthStencilCreated = checkStatus("depth");
-      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
-      }
-
-    //////////////////////////////////////////////////////////////
-    // DETACH
-
-    // TODO
-    if( mDepthStencilCreated==DONT_CREATE && mDepthStencilH[0]>0 ) // we need to detach and recreate the DEPTH attachment.
-      {
-      // OpenGL ES 3.0.5 spec, chapter 4.4.2.4 :
-      // "Note that the texture image is specifically not detached from any other framebuffer objects.
-      //  Detaching the texture image from any other framebuffer objects is the responsibility of the application."
-
-      for(int i=0; i<mNumFBOs; i++)
-        {
-        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[i]);
-        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_ATTACHMENT, GLES30.GL_TEXTURE_2D, 0, 0);
-        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_STENCIL_ATTACHMENT, GLES30.GL_TEXTURE_2D, 0, 0);
-        mDepthStencilH[i]=0;
-        }
-
-      GLES30.glDeleteTextures(mNumFBOs, mDepthStencilH, 0);
-      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
-
-  private int checkStatus(String message)
-    {
-    int status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER);
-
-    if(status != GLES30.GL_FRAMEBUFFER_COMPLETE)
-      {
-      DistortedLibrary.logMessage("DistortedFramebuffer: FRAMEBUFFER INCOMPLETE, "+message+" error="+status);
-
-      GLES30.glDeleteTextures(1, mColorH, 0);
-      GLES30.glDeleteTextures(1, mDepthStencilH, 0);
-      GLES30.glDeleteFramebuffers(1, mFBOH, 0);
-      mFBOH[0]= 0;
-
-      return FAILED_TO_CREATE;
-      }
-
-    return CREATED;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Must be called from a thread holding OpenGL Context
-
-  public void delete()
-    {
-    if( mColorH[0]>0 )
-      {
-      GLES30.glDeleteTextures(mNumFBOs*mNumColors, mColorH, 0);
-      mColorCreated = NOT_CREATED_YET;
-
-      for(int i=0; i<mNumFBOs*mNumColors; i++) mColorH[i] = 0;
-      }
-
-    if( mDepthStencilH[0]>0 )
-      {
-      GLES30.glDeleteTextures(mNumFBOs, mDepthStencilH, 0);
-      mDepthStencilCreated = NOT_CREATED_YET;
-
-      for(int i=0; i<mNumFBOs; i++) mDepthStencilH[i] = 0;
-      }
-
-    if( mNumFBOs>0 && mFBOH[0]>0 )
-      {
-      GLES30.glDeleteFramebuffers(mNumFBOs, mFBOH, 0);
-      }
-
-    for(int i=0; i<mNumFBOs; i++)
-      {
-      mFBOH[i] = 0;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// called from onDestroy(); mark OpenGL assets as 'not created'
-
-  public void recreate()
-    {
-    if( mColorCreated!=DONT_CREATE )
-      {
-      mColorCreated = NOT_CREATED_YET;
-      mColorH[0] = 0;
-      }
-    if( mDepthStencilCreated!=DONT_CREATE )
-      {
-      mDepthStencilCreated = NOT_CREATED_YET;
-      mDepthStencilH[0] = 0;
-      }
-    for(int i=0; i<mNumFBOs; i++)
-      {
-      mFBOH[i] = 0;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean setAsInput(int fbo, int texture)
-    {
-    if( texture>=0 && texture<mNumColors && fbo>=0 && fbo<mNumFBOs && mColorH[mNumColors*fbo + texture]>0 )
-      {
-      GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[mNumColors*fbo + texture]);
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// create a multi-framebuffer (1 object containing multiple FBOs)
-
-  DistortedFramebuffer(int numfbos, int numcolors, int depthStencil, int type, int storage, int width, int height)
-    {
-    super(width,height,NOT_CREATED_YET,numfbos,numcolors,depthStencil,NOT_CREATED_YET, type, storage);
-    markForCreation();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// create SYSTEM or TREE framebuffers (those are just like normal FBOs, just hold information
-// that they were autocreated only for the Library's internal purposes (SYSTEM) or for using
-// inside a Tree of DistortedNodes (TREE)
-// SYSTEM surfaces do not get removed in onDestroy().
-
-  DistortedFramebuffer(int numcolors, int depthStencil, int type, int width, int height)
-    {
-    this(1,numcolors,depthStencil,type,STORAGE_PRIVATE,width,height);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create new offscreen Framebuffer with configurable number of COLOR, DEPTH and STENCIL attachments.
- *
- * @param width        Width of all the COLOR attachments.
- * @param height       Height of all the COLOR attachments.
- * @param numcolors    How many COLOR attachments to create?
- * @param depthStencil Add DEPTH or STENCIL attachment?
- *                     Valid values: NO_DEPTH_NO_STENCIL, DEPTH_NO_STENCIL, BOTH_DEPTH_STENCIL.
- */
-  @SuppressWarnings("unused")
-  public DistortedFramebuffer(int width, int height, int numcolors, int depthStencil)
-    {
-    this(1,numcolors,depthStencil,TYPE_USER,STORAGE_PRIVATE,width,height);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Bind the underlying rectangle of pixels as a OpenGL Texture.
- *
- * @param texture The Texture number to bind (and thus read from).
- * @return <code>true</code> if successful.
- */
-  public boolean setAsInput(int texture)
-    {
-    if( texture>=0 && texture<mNumColors && mColorH[texture]>0 )
-      {
-      GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[2*mCurrFBO+texture]);
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * Attach the texture'th Texture to COLOR0 attachment.
-   *
-   * @param texture The Texture number to attach (and subsequently use to render to)
-   */
-  public void bindForOutput(int texture)
-    {
-    if( texture>=0 && texture<mNumColors && mColorH[texture]>0 )
-      {
-      GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[2*mCurrFBO+texture], 0);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Enable.disable DEPTH and STENCIL buffers.
- *
- * @param depthStencil Valid values: NO_DEPTH_NO_STENCIL, DEPTH_NO_STENCIL, BOTH_DEPTH_STENCIL.
- */
-  public void enableDepthStencil(int depthStencil)
-    {
-    if( depthStencil != mDepthStencil )
-      {
-      mDepthStencil = depthStencil;
-
-      if( depthStencil!= NO_DEPTH_NO_STENCIL && mDepthStencilCreated==DONT_CREATE )
-        {
-        mDepthStencilCreated = NOT_CREATED_YET;
-        markForCreation();
-        }
-      if( depthStencil== NO_DEPTH_NO_STENCIL && mDepthStencilCreated!=DONT_CREATE )
-        {
-        mDepthStencilCreated = DONT_CREATE;
-        markForCreation();
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the ID of the Texture (COLOR attachment 0) that's backing this FBO.
- * <p>
- * Catch: this will only work if the library has had time to actually create the texture. Remember
- * that the texture gets created only on first render, thus creating a Texture object and immediately
- * calling this method will return an invalid (negative) result.
- *
- * @return If there was not a single render between creation of the Object and calling this method on
- *         it, return a negative value. Otherwise, return ID of COLOR attachment 0.
- */
-  public int getTextureID()
-    {
-    return mColorH[0];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Bind one of the underlying FBOs to GL_READ_FRAMEBUFFER.
- * Useful for a subsequent glReadBuffer / glReadPixels.
- *
- * @param fbo which of the underlying FBOs to bind.
- * @return <code>true</code> if successful.
- */
-  public boolean setAsReadFramebuffer(int fbo)
-    {
-    if( fbo>=0 && fbo<mNumFBOs )
-      {
-      GLES30.glBindFramebuffer(GLES30.GL_READ_FRAMEBUFFER, mFBOH[fbo]);
-      return true;
-      }
-
-    return false;
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/DistortedFramebuffer.kt b/src/main/java/org/distorted/library/main/DistortedFramebuffer.kt
new file mode 100644
index 0000000..674f884
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/DistortedFramebuffer.kt
@@ -0,0 +1,366 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main;
+
+import android.opengl.GLES30;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class which represents a OpenGL Framebuffer object.
+ * <p>
+ * User is able to create offscreen FBOs and both a) render to them b) use their COLOR0 attachment as
+ * an input texture. Attaching Depths and/or Stencils is also possible.
+ */
+public class DistortedFramebuffer extends InternalOutputSurface
+  {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Must be called from a thread holding OpenGL Context
+
+  public void create()
+    {
+    if( mNumFBOs==DistortedLibrary.WAIT_FOR_FBO_QUEUE_SIZE )
+      {
+      // only now we know how many FBOs there should be
+      mNumFBOs = DistortedLibrary.getQueueSize();
+      allocateColor();
+      allocateStuffDependantOnNumFBOS();
+      }
+
+    //////////////////////////////////////////////////////////////
+    // COLOR
+
+    if( mColorCreated==NOT_CREATED_YET )
+      {
+      GLES30.glGenTextures( mNumFBOs*mNumColors, mColorH, 0);
+      GLES30.glGenFramebuffers(mNumFBOs, mFBOH, 0);
+
+      for(int i=0; i<mNumFBOs; i++)
+        {
+        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[i]);
+
+        for(int j=0; j<mNumColors; j++)
+          {
+          GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[i*mNumColors+j]);
+          GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT);
+          GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_REPEAT);
+          GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
+          GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
+          GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, mRealWidth, mRealHeight, 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
+          }
+
+        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[i*mNumColors], 0);
+        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+        }
+
+      // TODO
+      mColorCreated = checkStatus("color");
+      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+      }
+
+    //////////////////////////////////////////////////////////////
+    // DEPTH / STENCIL
+
+    if( mDepthStencilCreated==NOT_CREATED_YET ) // we need to create a new DEPTH or STENCIL attachment
+      {
+      GLES30.glGenTextures(mNumFBOs, mDepthStencilH, 0);
+
+      for(int i=0; i<mNumFBOs; i++)
+        {
+        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mDepthStencilH[i]);
+        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT);
+        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_REPEAT);
+        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
+        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_NEAREST);
+
+        if (mDepthStencil == DEPTH_NO_STENCIL)
+          {
+          GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_DEPTH_COMPONENT, mRealWidth, mRealHeight, 0, GLES30.GL_DEPTH_COMPONENT, GLES30.GL_UNSIGNED_INT, null);
+          }
+        else if (mDepthStencil == BOTH_DEPTH_STENCIL)
+          {
+          GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_DEPTH24_STENCIL8, mRealWidth, mRealHeight, 0, GLES30.GL_DEPTH_STENCIL, GLES30.GL_UNSIGNED_INT_24_8, null);
+          }
+        }
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+
+      for(int i=0; i<mNumFBOs; i++)
+        {
+        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[i]);
+
+        if (mDepthStencil == DEPTH_NO_STENCIL)
+          {
+          GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_ATTACHMENT, GLES30.GL_TEXTURE_2D, mDepthStencilH[i], 0);
+          }
+        else if (mDepthStencil == BOTH_DEPTH_STENCIL)
+          {
+          GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_STENCIL_ATTACHMENT, GLES30.GL_TEXTURE_2D, mDepthStencilH[i], 0);
+          }
+        }
+
+      // TODO
+      mDepthStencilCreated = checkStatus("depth");
+      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+      }
+
+    //////////////////////////////////////////////////////////////
+    // DETACH
+
+    // TODO
+    if( mDepthStencilCreated==DONT_CREATE && mDepthStencilH[0]>0 ) // we need to detach and recreate the DEPTH attachment.
+      {
+      // OpenGL ES 3.0.5 spec, chapter 4.4.2.4 :
+      // "Note that the texture image is specifically not detached from any other framebuffer objects.
+      //  Detaching the texture image from any other framebuffer objects is the responsibility of the application."
+
+      for(int i=0; i<mNumFBOs; i++)
+        {
+        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[i]);
+        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_ATTACHMENT, GLES30.GL_TEXTURE_2D, 0, 0);
+        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_STENCIL_ATTACHMENT, GLES30.GL_TEXTURE_2D, 0, 0);
+        mDepthStencilH[i]=0;
+        }
+
+      GLES30.glDeleteTextures(mNumFBOs, mDepthStencilH, 0);
+      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  private int checkStatus(String message)
+    {
+    int status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER);
+
+    if(status != GLES30.GL_FRAMEBUFFER_COMPLETE)
+      {
+      DistortedLibrary.logMessage("DistortedFramebuffer: FRAMEBUFFER INCOMPLETE, "+message+" error="+status);
+
+      GLES30.glDeleteTextures(1, mColorH, 0);
+      GLES30.glDeleteTextures(1, mDepthStencilH, 0);
+      GLES30.glDeleteFramebuffers(1, mFBOH, 0);
+      mFBOH[0]= 0;
+
+      return FAILED_TO_CREATE;
+      }
+
+    return CREATED;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Must be called from a thread holding OpenGL Context
+
+  public void delete()
+    {
+    if( mColorH[0]>0 )
+      {
+      GLES30.glDeleteTextures(mNumFBOs*mNumColors, mColorH, 0);
+      mColorCreated = NOT_CREATED_YET;
+
+      for(int i=0; i<mNumFBOs*mNumColors; i++) mColorH[i] = 0;
+      }
+
+    if( mDepthStencilH[0]>0 )
+      {
+      GLES30.glDeleteTextures(mNumFBOs, mDepthStencilH, 0);
+      mDepthStencilCreated = NOT_CREATED_YET;
+
+      for(int i=0; i<mNumFBOs; i++) mDepthStencilH[i] = 0;
+      }
+
+    if( mNumFBOs>0 && mFBOH[0]>0 )
+      {
+      GLES30.glDeleteFramebuffers(mNumFBOs, mFBOH, 0);
+      }
+
+    for(int i=0; i<mNumFBOs; i++)
+      {
+      mFBOH[i] = 0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// called from onDestroy(); mark OpenGL assets as 'not created'
+
+  public void recreate()
+    {
+    if( mColorCreated!=DONT_CREATE )
+      {
+      mColorCreated = NOT_CREATED_YET;
+      mColorH[0] = 0;
+      }
+    if( mDepthStencilCreated!=DONT_CREATE )
+      {
+      mDepthStencilCreated = NOT_CREATED_YET;
+      mDepthStencilH[0] = 0;
+      }
+    for(int i=0; i<mNumFBOs; i++)
+      {
+      mFBOH[i] = 0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean setAsInput(int fbo, int texture)
+    {
+    if( texture>=0 && texture<mNumColors && fbo>=0 && fbo<mNumFBOs && mColorH[mNumColors*fbo + texture]>0 )
+      {
+      GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[mNumColors*fbo + texture]);
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// create a multi-framebuffer (1 object containing multiple FBOs)
+
+  DistortedFramebuffer(int numfbos, int numcolors, int depthStencil, int type, int storage, int width, int height)
+    {
+    super(width,height,NOT_CREATED_YET,numfbos,numcolors,depthStencil,NOT_CREATED_YET, type, storage);
+    markForCreation();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// create SYSTEM or TREE framebuffers (those are just like normal FBOs, just hold information
+// that they were autocreated only for the Library's internal purposes (SYSTEM) or for using
+// inside a Tree of DistortedNodes (TREE)
+// SYSTEM surfaces do not get removed in onDestroy().
+
+  DistortedFramebuffer(int numcolors, int depthStencil, int type, int width, int height)
+    {
+    this(1,numcolors,depthStencil,type,STORAGE_PRIVATE,width,height);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create new offscreen Framebuffer with configurable number of COLOR, DEPTH and STENCIL attachments.
+ *
+ * @param width        Width of all the COLOR attachments.
+ * @param height       Height of all the COLOR attachments.
+ * @param numcolors    How many COLOR attachments to create?
+ * @param depthStencil Add DEPTH or STENCIL attachment?
+ *                     Valid values: NO_DEPTH_NO_STENCIL, DEPTH_NO_STENCIL, BOTH_DEPTH_STENCIL.
+ */
+  @SuppressWarnings("unused")
+  public DistortedFramebuffer(int width, int height, int numcolors, int depthStencil)
+    {
+    this(1,numcolors,depthStencil,TYPE_USER,STORAGE_PRIVATE,width,height);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Bind the underlying rectangle of pixels as a OpenGL Texture.
+ *
+ * @param texture The Texture number to bind (and thus read from).
+ * @return <code>true</code> if successful.
+ */
+  public boolean setAsInput(int texture)
+    {
+    if( texture>=0 && texture<mNumColors && mColorH[texture]>0 )
+      {
+      GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[2*mCurrFBO+texture]);
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * Attach the texture'th Texture to COLOR0 attachment.
+   *
+   * @param texture The Texture number to attach (and subsequently use to render to)
+   */
+  public void bindForOutput(int texture)
+    {
+    if( texture>=0 && texture<mNumColors && mColorH[texture]>0 )
+      {
+      GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[2*mCurrFBO+texture], 0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Enable.disable DEPTH and STENCIL buffers.
+ *
+ * @param depthStencil Valid values: NO_DEPTH_NO_STENCIL, DEPTH_NO_STENCIL, BOTH_DEPTH_STENCIL.
+ */
+  public void enableDepthStencil(int depthStencil)
+    {
+    if( depthStencil != mDepthStencil )
+      {
+      mDepthStencil = depthStencil;
+
+      if( depthStencil!= NO_DEPTH_NO_STENCIL && mDepthStencilCreated==DONT_CREATE )
+        {
+        mDepthStencilCreated = NOT_CREATED_YET;
+        markForCreation();
+        }
+      if( depthStencil== NO_DEPTH_NO_STENCIL && mDepthStencilCreated!=DONT_CREATE )
+        {
+        mDepthStencilCreated = DONT_CREATE;
+        markForCreation();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the ID of the Texture (COLOR attachment 0) that's backing this FBO.
+ * <p>
+ * Catch: this will only work if the library has had time to actually create the texture. Remember
+ * that the texture gets created only on first render, thus creating a Texture object and immediately
+ * calling this method will return an invalid (negative) result.
+ *
+ * @return If there was not a single render between creation of the Object and calling this method on
+ *         it, return a negative value. Otherwise, return ID of COLOR attachment 0.
+ */
+  public int getTextureID()
+    {
+    return mColorH[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Bind one of the underlying FBOs to GL_READ_FRAMEBUFFER.
+ * Useful for a subsequent glReadBuffer / glReadPixels.
+ *
+ * @param fbo which of the underlying FBOs to bind.
+ * @return <code>true</code> if successful.
+ */
+  public boolean setAsReadFramebuffer(int fbo)
+    {
+    if( fbo>=0 && fbo<mNumFBOs )
+      {
+      GLES30.glBindFramebuffer(GLES30.GL_READ_FRAMEBUFFER, mFBOH[fbo]);
+      return true;
+      }
+
+    return false;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.java b/src/main/java/org/distorted/library/main/DistortedLibrary.java
deleted file mode 100644
index 4f5c52d..0000000
--- a/src/main/java/org/distorted/library/main/DistortedLibrary.java
+++ /dev/null
@@ -1,1361 +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.main;
-
-import android.opengl.GLES30;
-import android.opengl.GLES31;
-
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.distorted.library.R;
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effectqueue.EffectQueue;
-import org.distorted.library.effectqueue.EffectQueuePostprocess;
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.FragmentEffect;
-import org.distorted.library.effect.PostprocessEffect;
-import org.distorted.library.effect.VertexEffect;
-import org.distorted.library.effectqueue.EffectQueueVertex;
-import org.distorted.library.mesh.DeferredJobs;
-import org.distorted.library.mesh.MeshBase;
-import org.distorted.library.message.EffectMessageSender;
-import org.distorted.library.program.DistortedProgram;
-import org.distorted.library.program.VertexCompilationException;
-import org.distorted.library.type.Dynamic;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * A singleton class used to control various global dialog_settings.
- */
-public class DistortedLibrary
-  {
-  /**
-   * When creating an instance of a DistortedTexture from another instance, clone the Bitmap that's
-   * backing up our DistortedTexture.
-   * <p>
-   * This way we can have two DistortedTextures, both backed up by the same Bitmap, to which we can
-   * apply different effects. Used in the copy constructor.
-   */
-  public static final int CLONE_SURFACE = 0x1;
-  /**
-   * When creating an instance of a DistortedEffects from another instance, clone the Matrix Effects.
-   * <p>
-   * This way we can have two different DistortedEffects sharing the MATRIX queue.
-   */
-  public static final int CLONE_MATRIX = 0x2;
-  /**
-   * When creating an instance of a DistortedEffects from another instance, clone the Vertex Effects.
-   * <p>
-   * This way we can have two different DistortedEffects sharing the VERTEX queue.
-   */
-  public static final int CLONE_VERTEX  = 0x4;
-  /**
-   * When creating an instance of a DistortedEffects from another instance, clone the Fragment Effects.
-   * <p>
-   * This way we can have two different DistortedEffects sharing the FRAGMENT queue.
-   */
-  public static final int CLONE_FRAGMENT= 0x8;
-   /**
-   * When creating an instance of a DistortedEffects from another instance, clone the PostProcess Effects.
-   * <p>
-   * This way we can have two different DistortedEffects sharing the POSTPROCESS queue.
-   */
-  public static final int CLONE_POSTPROCESS= 0x10;
-  /**
-   * When creating an instance of a DistortedNode from another instance, clone the children Nodes.
-   * <p>
-   * This is mainly useful for creating many similar sub-trees and rendering then at different places
-   * on the screen with (optionally) different Effects.
-   */
-  public static final int CLONE_CHILDREN= 0x20;
-
-  /**
-   * When creating a DistortedScreen (which needs to have mFBOQueueSize FBOs attached), pass this
-   * constant for 'numOfFBOs' and the number of backing FBOs will be taken from 'mFBOQueueSize'
-   * (the value of which is most likely unknown at the time of creation of the Screen)
-   */
-  public static final int WAIT_FOR_FBO_QUEUE_SIZE = -1;
-  /**
-   * Work around bugs in ARM Mali driver by, instead to a single FBO, rendering to a circular queue
-   * of mFBOQueueSize FBOs. (otherwise we sometimes get a 'full pipeline flush' and the end result
-   * might be missing part of the Objects)
-   * <p>
-   * This bug only exists on Mali driver r12. (or more precisely it's there in r12 but fixed in r22)
-   * <p>
-   * <a href="https://community.arm.com/graphics/f/discussions/10285/opengl-es-3-1-on-mali-t880-flashes">...</a>
-   */
-  private static int mFBOQueueSize;
-  private static int mGLSL;
-  private static String mGLSL_VERSION;
-  private static boolean mOITCompilationAttempted, mNeedsTransformFeedback;
-
-  private static int mMaxTextureSize         = Integer.MAX_VALUE;
-  private static int mMaxNumberOfVerUniforms = Integer.MAX_VALUE;
-  private static int mMaxNumberOfFraUniforms = Integer.MAX_VALUE;
-
-  private static boolean mBuggyUBOs;
-  private static String mVendor, mVersion, mRenderer;
-  private static boolean mFastCompilationTF;
-
-  //////////////////////////////////////////////////////////////////////////////////////////////
-  /// MAIN PROGRAM ///
-  private static DistortedProgram mMainProgram;
-  private static int mMainTextureH;
-  private static int mTransformFeedbackH;
-
-  /// NORMAL PROGRAM /////
-  private static DistortedProgram mNormalProgram;
-  private static int mNormalProjectionH;
-
-  /// MAIN OIT PROGRAM ///
-  private static DistortedProgram mMainOITProgram;
-  private static int mMainOITTextureH;
-  private static int mMainOITSizeH;
-  private static int mMainOITNumRecordsH;
-
-  /// BLIT PROGRAM ///
-  private static DistortedProgram mBlitProgram;
-  private static int mBlitTextureH;
-  private static int mBlitDepthH;
-  private static final FloatBuffer mQuadPositions;
-
-  /// FULL PROGRAM ///
-  private static DistortedProgram mFullProgram;
-
-  static
-    {
-    float[] positionData= { -0.5f, -0.5f,  -0.5f, 0.5f,  0.5f,-0.5f,  0.5f, 0.5f };
-    mQuadPositions = ByteBuffer.allocateDirect(32).order(ByteOrder.nativeOrder()).asFloatBuffer();
-    mQuadPositions.put(positionData).position(0);
-    }
-
-  /// BLIT DEPTH PROGRAM ///
-  private static DistortedProgram mBlitDepthProgram;
-  private static int mBlitDepthTextureH;
-  private static int mBlitDepthDepthTextureH;
-  private static int mBlitDepthTexCorrH;
-
-  /// Program Handles ///
-  private static int mMainProgramH, mFullProgramH, mMainOITProgramH;
-
-  /// OIT SSBO BUFFER ///
-  private static final int[] mLinkedListSSBO = new int[1];
-  private static int[] mAtomicCounter;
-  private static int   mCurrBuffer;
-
-  static
-    {
-    mLinkedListSSBO[0]= -1;
-    mCurrBuffer       =  0;
-    }
-
-  ///////////////////////////////////////////////////////////////
-  // meaning: allocate 1.0 screenful of places for transparent
-  // fragments in the SSBO backing up the OIT render method.
-  private static float mBufferSize=1.0f;
-
-  /// OIT CLEAR PROGRAM ///
-  private static DistortedProgram mOITClearProgram;
-  private static int mOITClearDepthH;
-  private static int mOITClearTexCorrH;
-  private static int mOITClearSizeH;
-
-  /// OIT BUILD PROGRAM ///
-  private static DistortedProgram mOITBuildProgram;
-  private static int mOITBuildTextureH;
-  private static int mOITBuildDepthTextureH;
-  private static int mOITBuildDepthH;
-  private static int mOITBuildTexCorrH;
-  private static int mOITBuildSizeH;
-  private static int mOITBuildNumRecordsH;
-
-  /// OIT COLLAPSE PROGRAM ///
-  private static DistortedProgram mOITCollapseProgram;
-  private static int mOITCollapseDepthTextureH;
-  private static int mOITCollapseDepthH;
-  private static int mOITCollapseTexCorrH;
-  private static int mOITCollapseSizeH;
-
-  /// OIT RENDER PROGRAM ///
-  private static DistortedProgram mOITRenderProgram;
-  private static int mOITRenderDepthH;
-  private static int mOITRenderTexCorrH;
-  private static int mOITRenderSizeH;
-
-  /// END PROGRAMS //////
-
-  /**
-   * Every application using the library must implement this interface so that the library can send
-   * it exceptions that arise. The exceptions may come at any time, for example the library will
-   * compile its OIT problem only on the first attempt to use the OIT
-   * Those will mainly be hardware-related: shaders do not compile on particular hardware, the required
-   * OpenGL ES 3.0 is not supported, etc.
-   * <p>
-   * Additionally, the User must be able to provide version of the OpenGL supported by the underlying
-   * hardware and InputStreams to given local files.
-   */
-  public interface LibraryUser
-    {
-    void distortedException(Exception ex);
-    InputStream localFile(int fileID);
-    void logMessage(String message);
-    }
-
-  private static LibraryUser mUser;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// private: hide this from Javadoc
-
-  private DistortedLibrary()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createMainProgram()
-    {
-    // MAIN PROGRAM ////////////////////////////////////
-    final InputStream mainVertStream = mUser.localFile(R.raw.main_vertex_shader);
-    final InputStream mainFragStream = mUser.localFile(R.raw.main_fragment_shader);
-
-    int numF = FragmentEffect.getNumEnabled();
-    int numV = VertexEffect.getNumEnabled();
-
-    String mainVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n");
-    String mainFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n");
-
-    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
-    if( MeshBase.getUseCenters() ) mainVertHeader += "#define COMP_CENTERS\n";
-    if( mBuggyUBOs )               mainVertHeader += "#define BUGGY_UBOS\n";
-
-    String enabledEffectV= VertexEffect.getGLSL();
-    String enabledEffectF= FragmentEffect.getGLSL();
-
-    String[] feedback = { "v_Position", "v_endPosition" };
-
-    try
-      {
-      mMainProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader,
-                                          mainFragHeader, enabledEffectV, enabledEffectF,
-                                          mGLSL, mNeedsTransformFeedback ? feedback : null );
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile MAIN program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    mMainProgramH = mMainProgram.getProgramHandle();
-    EffectQueue.getUniforms(mMainProgramH,0);
-    MeshBase.getUniforms(mMainProgramH,0);
-    mMainTextureH= GLES30.glGetUniformLocation( mMainProgramH, "u_Texture");
-    mTransformFeedbackH= GLES30.glGetUniformLocation( mMainProgramH, "u_TransformFeedback");
-
-    // BLIT PROGRAM ////////////////////////////////////
-    final InputStream blitVertStream = mUser.localFile(R.raw.blit_vertex_shader);
-    final InputStream blitFragStream = mUser.localFile(R.raw.blit_fragment_shader);
-
-    try
-      {
-      mBlitProgram = new DistortedProgram(blitVertStream,blitFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile BLIT program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int blitProgramH = mBlitProgram.getProgramHandle();
-    mBlitTextureH  = GLES30.glGetUniformLocation( blitProgramH, "u_Texture");
-    mBlitDepthH    = GLES30.glGetUniformLocation( blitProgramH, "u_Depth");
-
-    // BLIT DEPTH PROGRAM ////////////////////////////////////
-    final InputStream blitDepthVertStream = mUser.localFile(R.raw.blit_depth_vertex_shader);
-    final InputStream blitDepthFragStream = mUser.localFile(R.raw.blit_depth_fragment_shader);
-
-    try
-      {
-      mBlitDepthProgram = new DistortedProgram(blitDepthVertStream,blitDepthFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile BLIT DEPTH program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int blitDepthProgramH   = mBlitDepthProgram.getProgramHandle();
-    mBlitDepthTextureH      = GLES30.glGetUniformLocation( blitDepthProgramH, "u_Texture");
-    mBlitDepthDepthTextureH = GLES30.glGetUniformLocation( blitDepthProgramH, "u_DepthTexture");
-    mBlitDepthTexCorrH      = GLES30.glGetUniformLocation( blitDepthProgramH, "u_TexCorr");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createNormalProgram()
-    {
-    // NORMAL PROGRAM //////////////////////////////////////
-    final InputStream normalVertexStream   = mUser.localFile(R.raw.normal_vertex_shader);
-    final InputStream normalFragmentStream = mUser.localFile(R.raw.normal_fragment_shader);
-
-    try
-      {
-      mNormalProgram = new DistortedProgram(normalVertexStream,normalFragmentStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile NORMAL program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int normalProgramH = mNormalProgram.getProgramHandle();
-    mNormalProjectionH = GLES30.glGetUniformLocation( normalProgramH, "u_Projection");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createFullProgram()
-    {
-    final InputStream fullVertStream = mUser.localFile(R.raw.main_vertex_shader);
-    final InputStream fullFragStream = mUser.localFile(R.raw.main_fragment_shader);
-
-    int numV = VertexEffect.getAllEnabled();
-
-    String fullVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX ) : 0 ) + "\n");
-    String fullFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " +                                         0   + "\n");
-
-    fullVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
-    if( MeshBase.getUseCenters() ) fullVertHeader += "#define COMP_CENTERS\n";
-    if( mBuggyUBOs )               fullVertHeader += "#define BUGGY_UBOS\n";
-
-    String enabledEffectV= VertexEffect.getAllGLSL();
-    String enabledEffectF= "{}";
-
-    fullVertHeader += "#define PREAPPLY\n";
-
-    String[] feedback = { "v_Position", "v_endPosition" };
-
-    try
-      {
-      mFullProgram = new DistortedProgram(fullVertStream, fullFragStream, fullVertHeader, fullFragHeader,
-                                          enabledEffectV, enabledEffectF, mGLSL, feedback);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile FULL program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    mFullProgramH = mFullProgram.getProgramHandle();
-    EffectQueue.getUniforms(mFullProgramH,3);
-    MeshBase.getUniforms(mFullProgramH,3);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createOITProgram()
-    {
-    // MAIN OIT PROGRAM ////////////////////////////////
-    final InputStream mainVertStream = mUser.localFile(R.raw.main_vertex_shader);
-    final InputStream mainFragStream = mUser.localFile(R.raw.main_fragment_shader);
-
-    int numF = FragmentEffect.getNumEnabled();
-    int numV = VertexEffect.getNumEnabled();
-
-    String mainVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n") + ("#define OIT\n");
-    String mainFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n") + ("#define OIT\n");
-
-    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
-    if( MeshBase.getUseCenters() ) mainVertHeader += "#define COMP_CENTERS\n";
-    if( mBuggyUBOs )               mainVertHeader += "#define BUGGY_UBOS\n";
-
-    String enabledEffectV= VertexEffect.getGLSL();
-    String enabledEffectF= FragmentEffect.getGLSL();
-
-    try
-      {
-      mMainOITProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader, mainFragHeader,
-                                             enabledEffectV, enabledEffectF, mGLSL, null);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile MAIN OIT program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    mMainOITProgramH = mMainOITProgram.getProgramHandle();
-    EffectQueue.getUniforms(mMainOITProgramH,1);
-    MeshBase.getUniforms(mMainOITProgramH,1);
-    mMainOITTextureH    = GLES30.glGetUniformLocation( mMainOITProgramH, "u_Texture");
-    mMainOITSizeH       = GLES30.glGetUniformLocation( mMainOITProgramH, "u_Size");
-    mMainOITNumRecordsH = GLES30.glGetUniformLocation( mMainOITProgramH, "u_numRecords");
-
-    // OIT CLEAR PROGRAM ////////////////////////////////////
-    final InputStream oitClearVertStream = mUser.localFile(R.raw.oit_vertex_shader);
-    final InputStream oitClearFragStream = mUser.localFile(R.raw.oit_clear_fragment_shader);
-
-    try
-      {
-      mOITClearProgram = new DistortedProgram(oitClearVertStream,oitClearFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT CLEAR program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitClearProgramH   = mOITClearProgram.getProgramHandle();
-    mOITClearDepthH        = GLES30.glGetUniformLocation( oitClearProgramH, "u_Depth");
-    mOITClearTexCorrH      = GLES30.glGetUniformLocation( oitClearProgramH, "u_TexCorr");
-    mOITClearSizeH         = GLES30.glGetUniformLocation( oitClearProgramH, "u_Size");
-
-    // OIT BUILD PROGRAM ////////////////////////////////////
-    final InputStream oitBuildVertStream = mUser.localFile(R.raw.oit_vertex_shader);
-    final InputStream oitBuildFragStream = mUser.localFile(R.raw.oit_build_fragment_shader);
-
-    try
-      {
-      mOITBuildProgram = new DistortedProgram(oitBuildVertStream,oitBuildFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT BUILD program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitBuildProgramH   = mOITBuildProgram.getProgramHandle();
-    mOITBuildTextureH      = GLES30.glGetUniformLocation( oitBuildProgramH, "u_Texture");
-    mOITBuildDepthTextureH = GLES30.glGetUniformLocation( oitBuildProgramH, "u_DepthTexture");
-    mOITBuildDepthH        = GLES30.glGetUniformLocation( oitBuildProgramH, "u_Depth");
-    mOITBuildTexCorrH      = GLES30.glGetUniformLocation( oitBuildProgramH, "u_TexCorr");
-    mOITBuildSizeH         = GLES30.glGetUniformLocation( oitBuildProgramH, "u_Size");
-    mOITBuildNumRecordsH   = GLES30.glGetUniformLocation( oitBuildProgramH, "u_numRecords");
-
-    // OIT COLLAPSE PROGRAM ///////////////////////////
-    final InputStream oitCollapseVertStream = mUser.localFile(R.raw.oit_vertex_shader);
-    final InputStream oitCollapseFragStream = mUser.localFile(R.raw.oit_collapse_fragment_shader);
-
-    try
-      {
-      mOITCollapseProgram = new DistortedProgram(oitCollapseVertStream,oitCollapseFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT COLLAPSE program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitCollapseProgramH   = mOITCollapseProgram.getProgramHandle();
-    mOITCollapseDepthTextureH = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_DepthTexture");
-    mOITCollapseDepthH        = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_Depth");
-    mOITCollapseTexCorrH      = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_TexCorr");
-    mOITCollapseSizeH         = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_Size");
-
-    // OIT RENDER PROGRAM ///////////////////////////
-    final InputStream oitRenderVertStream = mUser.localFile(R.raw.oit_vertex_shader);
-    final InputStream oitRenderFragStream = mUser.localFile(R.raw.oit_render_fragment_shader);
-
-    try
-      {
-      mOITRenderProgram = new DistortedProgram(oitRenderVertStream,oitRenderFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
-      }
-    catch(Exception e)
-      {
-      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT RENDER program: "+e.getMessage());
-      throw new RuntimeException(e.getMessage());
-      }
-
-    int oitRenderProgramH   = mOITRenderProgram.getProgramHandle();
-    mOITRenderDepthH        = GLES30.glGetUniformLocation( oitRenderProgramH, "u_Depth");
-    mOITRenderTexCorrH      = GLES30.glGetUniformLocation( oitRenderProgramH, "u_TexCorr");
-    mOITRenderSizeH         = GLES30.glGetUniformLocation( oitRenderProgramH, "u_Size");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void displayNormals(float[] projection, MeshBase mesh)
-    {
-    if( mNormalProgram==null )
-      {
-      try
-        {
-        createNormalProgram();
-        }
-      catch(Exception ex)
-        {
-        mUser.distortedException(ex);
-        return;
-        }
-      }
-
-    int num = mesh.getNumVertices();
-    int tfo = mesh.getTFO();
-
-    GLES30.glUniform1i(DistortedLibrary.mTransformFeedbackH, 1);
-    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
-    GLES30.glBeginTransformFeedback( GLES30.GL_POINTS);
-    InternalRenderState.switchOffDrawing();
-    GLES30.glDrawArrays( GLES30.GL_POINTS, 0, num );
-    InternalRenderState.restoreDrawing();
-    GLES30.glEndTransformFeedback();
-    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
-    GLES30.glUniform1i(DistortedLibrary.mTransformFeedbackH, 0);
-
-    mNormalProgram.useProgram();
-    GLES30.glUniformMatrix4fv(mNormalProjectionH, 1, false, projection, 0);
-    mesh.bindTransformAttribs(mNormalProgram);
-    GLES30.glLineWidth(8.0f);
-    GLES30.glDrawArrays(GLES30.GL_LINES, 0, 2*num);
-    mNormalProgram.stopUsingProgram();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Execute all VertexEffects and adjust all vertices
- */
-  public static void adjustVertices(MeshBase mesh, EffectQueueVertex queue)
-    {
-    if( mFullProgram==null )
-      {
-      try
-        {
-        createFullProgram();
-        }
-      catch(Exception ex)
-        {
-        mUser.distortedException(ex);
-        return;
-        }
-      }
-
-    int num = mesh.getNumVertices();
-    int tfo = mesh.getTFO();
-
-    mFullProgram.useProgram();
-    mesh.bindVertexAttribs(mFullProgram);
-    queue.compute(1,0);
-    queue.send(0.0f,mFullProgramH,3);
-    mesh.send(mFullProgramH,3);
-
-    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
-    GLES30.glBeginTransformFeedback( GLES30.GL_POINTS);
-    InternalRenderState.switchOffDrawing();
-    GLES30.glDrawArrays( GLES30.GL_POINTS, 0, num );
-    InternalRenderState.restoreDrawing();
-    GLES30.glEndTransformFeedback();
-    mesh.copyTransformToVertex();
-    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
-    mFullProgram.stopUsingProgram();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void drawPrivOIT(DistortedEffects effects, MeshBase mesh, InternalOutputSurface surface, long currTime, long step)
-    {
-    if( mMainOITProgram!=null )
-      {
-      int w = surface.getWidth();
-      int h = surface.getHeight();
-      EffectQueue[] queues = effects.getQueues();
-
-      EffectQueue.compute(queues, currTime, step);
-      GLES30.glViewport(0, 0, w, h );
-
-      mMainOITProgram.useProgram();
-      GLES30.glUniform1i(mMainOITTextureH, 0);
-      GLES30.glUniform2ui(mMainOITSizeH, w, h );
-      GLES30.glUniform1ui(mMainOITNumRecordsH, (int)(mBufferSize*w*h) );
-      mesh.bindVertexAttribs(mMainOITProgram);
-      mesh.send(mMainOITProgramH,1);
-
-      float inflate     = mesh.getInflate();
-      float distance    = surface.mDistance;
-      float mipmap      = surface.mMipmap;
-      float[] projection= surface.mProjectionMatrix;
-
-      EffectQueue.send(queues, mMainOITProgramH, distance, mipmap, projection, inflate, 1 );
-      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
-      if( mesh.getShowNormals() ) displayNormals(projection,mesh);
-      mMainOITProgram.stopUsingProgram();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void drawPriv(DistortedEffects effects, MeshBase mesh, InternalOutputSurface surface, long currTime, long step)
-    {
-    if( mMainProgram!=null )
-      {
-      EffectQueue[] queues = effects.getQueues();
-      int w = surface.getWidth();
-      int h = surface.getHeight();
-      EffectQueue.compute(queues, currTime, step);
-      GLES30.glViewport(0, 0, w, h);
-
-      mMainProgram.useProgram();
-      GLES30.glUniform1i(mMainTextureH, 0);
-      mesh.bindVertexAttribs(mMainProgram);
-      mesh.send(mMainProgramH,0);
-
-      float inflate     = mesh.getInflate();
-      float distance    = surface.mDistance;
-      float mipmap      = surface.mMipmap;
-      float[] projection= surface.mProjectionMatrix;
-
-      EffectQueue.send(queues, mMainProgramH, distance, mipmap, projection, inflate, 0 );
-      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
-      if( mesh.getShowNormals() ) displayNormals(projection,mesh);
-      mMainProgram.stopUsingProgram();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void blitPriv(InternalOutputSurface surface)
-    {
-    if( mBlitProgram!=null )
-      {
-      int w = surface.getWidth();
-      int h = surface.getHeight();
-      float n = surface.getNear();
-      mBlitProgram.useProgram();
-      GLES30.glViewport(0, 0, w, h);
-      GLES30.glUniform1i(mBlitTextureH, 0);
-      GLES30.glUniform1f( mBlitDepthH , 1.0f-n);
-      GLES30.glVertexAttribPointer(mBlitProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-      mBlitProgram.stopUsingProgram();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void blitDepthPriv(InternalOutputSurface surface, float corrW, float corrH)
-    {
-    if( mBlitDepthProgram!=null )
-      {
-      mBlitDepthProgram.useProgram();
-      int w = surface.getWidth();
-      int h = surface.getHeight();
-      GLES30.glViewport(0, 0, w, h);
-      GLES30.glUniform1i(mBlitDepthTextureH, 0);
-      GLES30.glUniform1i(mBlitDepthDepthTextureH, 1);
-      GLES30.glUniform2f(mBlitDepthTexCorrH, corrW, corrH );
-      GLES30.glVertexAttribPointer(mBlitDepthProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-      mBlitDepthProgram.stopUsingProgram();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// yes it is safe to be mixing 3.0 and 3.1 like that, senior members of the OpenGL discussions forum assert
-
-  private static int printPreviousBuffer()
-    {
-    int counter = 0;
-
-    ByteBuffer atomicBuf = (ByteBuffer)GLES30.glMapBufferRange( GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, 4,
-                                                                GLES30.GL_MAP_READ_BIT);
-    if( atomicBuf!=null )
-      {
-      IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
-      counter = atomicIntBuf.get(0);
-      }
-    else
-      {
-      mUser.logMessage("printPreviousBuffer: failed to map atomic buffer");
-      }
-
-    GLES30.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
-
-    return counter;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void zeroBuffer()
-    {
-    ByteBuffer atomicBuf = (ByteBuffer)GLES30.glMapBufferRange( GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, 4,
-                                                                GLES30.GL_MAP_WRITE_BIT|GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
-    if( atomicBuf!=null )
-      {
-      IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
-      atomicIntBuf.put(0,0);
-      }
-    else
-      {
-      mUser.logMessage("zeroBuffer: failed to map atomic buffer");
-      }
-
-    GLES30.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// reset atomic counter to 0
-
-  static int zeroOutAtomic()
-    {
-    int counter = 0;
-
-    if( mAtomicCounter==null )
-      {
-      mAtomicCounter = new int[mFBOQueueSize];
-
-      GLES30.glGenBuffers(mFBOQueueSize,mAtomicCounter,0);
-
-      for(int i=0; i<mFBOQueueSize; i++)
-        {
-        GLES30.glBindBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER, mAtomicCounter[i]);
-        GLES30.glBufferData(GLES31.GL_ATOMIC_COUNTER_BUFFER, 4, null, GLES30.GL_DYNAMIC_DRAW);
-        zeroBuffer();
-        }
-      }
-
-    // reading the value of the buffer on every frame would slow down rendering by
-    // about 3%; doing it only once every 5 frames affects speed by less than 1%.
-    if( mCurrBuffer==0 )
-      {
-      GLES30.glBindBufferBase(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, mAtomicCounter[mCurrBuffer]);
-      counter = printPreviousBuffer();
-      }
-
-    if( ++mCurrBuffer>=mFBOQueueSize ) mCurrBuffer = 0;
-
-    GLES30.glBindBufferBase(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, mAtomicCounter[mCurrBuffer]);
-    zeroBuffer();
-
-    return counter;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Pass1 of the OIT algorithm. Clear per-pixel head-pointers.
-
-  static void oitClear(InternalOutputSurface surface, int counter)
-    {
-    if( mOITClearProgram==null )
-      {
-      if( mGLSL>=310 && !mOITCompilationAttempted )
-        {
-        mOITCompilationAttempted = true;
-
-        try
-          {
-          createOITProgram();
-          }
-        catch(Exception ex)
-          {
-          mUser.distortedException(ex);
-          return;
-          }
-        }
-      else
-        {
-        return;
-        }
-      }
-
-
-    int w = surface.getWidth();
-    int h = surface.getHeight();
-
-    if( mLinkedListSSBO[0]<0 )
-      {
-      GLES30.glGenBuffers(1,mLinkedListSSBO,0);
-
-      int size = (int)(w*h*(3*mBufferSize+1)*4);
-      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, mLinkedListSSBO[0]);
-      GLES30.glBufferData(GLES31.GL_SHADER_STORAGE_BUFFER, size, null, GLES30.GL_DYNAMIC_READ|GLES30.GL_DYNAMIC_DRAW);
-      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, 0);
-
-      GLES30.glBindBufferBase(GLES31.GL_SHADER_STORAGE_BUFFER, 1, mLinkedListSSBO[0]);
-      }
-
-    // See if we have overflown the SSBO in one of the previous frames.
-    // If yes, assume we need to make the SSBO larger.
-    float overflow = counter/(mBufferSize*w*h);
-
-    if( overflow>1.0f )
-      {
-      mBufferSize *= (int)(overflow+1.0f);
-      int size = (int)(w*h*(3*mBufferSize+1)*4);
-      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, mLinkedListSSBO[0]);
-      GLES30.glBufferData(GLES31.GL_SHADER_STORAGE_BUFFER, size, null, GLES30.GL_DYNAMIC_READ|GLES30.GL_DYNAMIC_DRAW);
-      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, 0);
-      }
-
-    mOITClearProgram.useProgram();
-    GLES30.glViewport(0, 0, w, h);
-    GLES30.glUniform2f(mOITClearTexCorrH, 1.0f, 1.0f );   // corrections do not really matter here - only present because of common vertex shader.
-    GLES30.glUniform1f( mOITClearDepthH , 1.0f);          // likewise depth
-    GLES30.glUniform2ui(mOITClearSizeH, w, h);
-    GLES30.glVertexAttribPointer(mOITClearProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-    mOITClearProgram.stopUsingProgram();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Pass2 of the OIT algorithm - build per-pixel linked lists.
-
-  static void oitBuild(InternalOutputSurface surface, float corrW, float corrH)
-    {
-    if( mOITBuildProgram!=null )
-      {
-      int w = surface.getWidth();
-      int h = surface.getHeight();
-      float n = surface.getNear();
-      mOITBuildProgram.useProgram();
-      GLES30.glViewport(0, 0, w, h);
-      GLES30.glUniform1i(mOITBuildTextureH, 0);
-      GLES30.glUniform1i(mOITBuildDepthTextureH, 1);
-      GLES30.glUniform2f(mOITBuildTexCorrH, corrW, corrH );
-      GLES30.glUniform2ui(mOITBuildSizeH, w, h);
-      GLES30.glUniform1ui(mOITBuildNumRecordsH, (int)(mBufferSize*w*h) );
-      GLES30.glUniform1f(mOITBuildDepthH , 1.0f-n);
-      GLES30.glVertexAttribPointer(mOITBuildProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-      mOITBuildProgram.stopUsingProgram();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Pass3 of the OIT algorithm. Cut occluded parts of the linked list.
-
-  static void oitCollapse(InternalOutputSurface surface, float corrW, float corrH)
-    {
-    if( mOITCollapseProgram!=null )
-      {
-      int w = surface.getWidth();
-      int h = surface.getHeight();
-      float n = surface.getNear();
-      mOITCollapseProgram.useProgram();
-      GLES30.glViewport(0, 0, w, h);
-      GLES30.glUniform1i(mOITCollapseDepthTextureH, 1);
-      GLES30.glUniform2f(mOITCollapseTexCorrH, corrW, corrH );
-      GLES30.glUniform2ui(mOITCollapseSizeH, w, h);
-      GLES30.glUniform1f( mOITCollapseDepthH , 1.0f-n);
-      GLES30.glVertexAttribPointer(mOITCollapseProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-      mOITCollapseProgram.stopUsingProgram();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Pass4 of the OIT algorithm. Render all the transparent pixels from the per-pixel linked lists.
-
-  static void oitRender(InternalOutputSurface surface, float corrW, float corrH)
-    {
-    if( mOITRenderProgram!=null )
-      {
-      int w = surface.getWidth();
-      int h = surface.getHeight();
-      float n = surface.getNear();
-      mOITRenderProgram.useProgram();
-      GLES30.glViewport(0, 0, w, h);
-      GLES30.glUniform2f(mOITRenderTexCorrH, corrW, corrH );
-      GLES30.glUniform2ui(mOITRenderSizeH, w, h);
-      GLES30.glUniform1f( mOITRenderDepthH , 1.0f-n);
-      GLES30.glVertexAttribPointer(mOITRenderProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
-      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
-      mOITRenderProgram.stopUsingProgram();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void setSSBOSize(float size)
-    {
-    mBufferSize = size;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static int getQueueSize()
-    {
-    return mFBOQueueSize;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int parseQualcommDriverVersion(String version)
-    {
-    int at = version.indexOf('@');
-
-    if( at>=0 )
-      {
-      int dot = version.indexOf('.',at);
-
-      if( dot>at+1 )
-        {
-        String ver = version.substring(at+1,dot);
-        return Integer.parseInt(ver);
-        }
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// ARM Mali driver r12 has problems when we keep swapping many FBOs (fixed in r22)
-// PowerVR GE8100 / GE8300 compiler fails to compile OIT programs.
-
-  private static void detectBuggyDriversAndSetQueueSize(int queueSize)
-    {
-    mVendor  = GLES30.glGetString(GLES30.GL_VENDOR);
-    mVersion = GLES30.glGetString(GLES30.GL_VERSION);
-    mRenderer= GLES30.glGetString(GLES30.GL_RENDERER);
-
-    mFBOQueueSize = 1;
-    mFastCompilationTF = true;
-
-    if( mVendor.contains("ARM") )
-      {
-      try
-        {
-        String regex = ".*r(\\d+)p\\d.*";
-        Pattern pattern = Pattern.compile(regex);
-        Matcher matcher = pattern.matcher(mVersion);
-
-        if( matcher.find() )
-          {
-          String driverVersion = matcher.group(1);
-
-          if( driverVersion!=null )
-            {
-            int drvVersion = Integer.parseInt(driverVersion);
-
-            if( drvVersion<22 )
-              {
-              mUser.logMessage("You are running this on a ARM Mali driver r"+driverVersion+".\n" +
-                    "This is a buggy driver, please update to r22. Inserting workaround which uses a lot of memory.");
-
-              mFBOQueueSize = queueSize;
-              }
-            }
-          }
-        }
-      catch(Exception ex)
-        {
-        mUser.logMessage("DistortedLibrary: exception trying to pattern match version: "+ ex);
-        }
-      }
-    else if( mVendor.contains("Imagination") )
-      {
-      if( mRenderer.contains("GE8") )
-        {
-        mUser.logMessage("You are running this on a PowerVR GE8XXX.\nDue to a buggy compiler OIT rendering will not work");
-        mUser.logMessage("GLSL Version "+GLES30.glGetString(GLES31.GL_SHADING_LANGUAGE_VERSION));
-        }
-      }
-    else if( mVendor.contains("Qualcomm"))
-      {
-      int driverVersion = parseQualcommDriverVersion(mVersion);
-
-      if( mRenderer.contains("308") && (driverVersion==331 || driverVersion==415) )
-        {
-        mUser.logMessage("You are running this on an Adreno 308 driver 331 or 415.\nStrange shit might happen.");
-        mBuggyUBOs = true;
-        }
-      if( mGLSL<=300 && driverVersion<415 ) // This is only on old enough drivers, 84,95,100,104,140 and known bad, 415 is known good.
-        {
-        mUser.logMessage("Slow compilation of Transform Feedback programs!");
-        mFastCompilationTF = false;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static int getVersion()
-    {
-    int[] major = new int[1];
-    int[] minor = new int[1];
-    GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, major, 0);
-    GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, minor, 0);
-    return major[0] * 100 + minor[0] * 10;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  public static void logMessage(String message)
-    {
-    mUser.logMessage(message);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return OpenGL ES version supported by the hardware we are running on.
- * There are only three possibilities: 300 (OpenGL ES 3.0) or 310 (at least OpenGL ES 3.1)
- * or 200 (OpenGL ES 2.0)
- */
-  public static int getGLSL()
-    {
-    return mGLSL;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Are we running this on hardware where UBOs are buggy?
- */
-  public static boolean isUBOBuggy()
-    {
-    return mBuggyUBOs;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When OpenGL context gets created, call this method so that the library can initialise its internal data structures.
- * I.e. best called from GLSurfaceView.Renderer.onSurfaceCreated().
- * <p>
- * Needs to be called from a thread holding the OpenGL context.
- *
- * @param user The Code which will be using this library, which must implement the LibraryUser interface.
- */
-  public static void onSurfaceCreated(final LibraryUser user)
-    {
-    onSurfaceCreated(user,4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When OpenGL context gets created, call this method so that the library can initialise its internal data structures.
- * I.e. best called from GLSurfaceView.Renderer.onSurfaceCreated().
- * <p>
- * Needs to be called from a thread holding the OpenGL context.
- *   
- * @param user The Code which will be using this library, which must implement the LibraryUser interface.
- * @param queueSize the size of the FBO queue, a workaround for the bug on Mali drivers. Use a small integer - 1,...,4
- */
-  public static void onSurfaceCreated(final LibraryUser user, int queueSize)
-    {
-    mUser = user;
-    int version = getVersion();
-    int major = version/100;
-    int minor = ((version/10)%10);
-
-    if( major< 3 )
-      {
-      mGLSL = 100*major + 10*minor;
-      VertexCompilationException ex = new VertexCompilationException("at least OpenGL ES 3.0 required, this device supports only "+major+"."+minor);
-      mUser.distortedException(ex);
-      }
-    else
-      {
-      mGLSL = (major==3 && minor==0) ? 300 : 310;
-      }
-
-    int[] tmp = new int[1];
-    GLES30.glGetIntegerv(GLES30.GL_MAX_TEXTURE_SIZE, tmp, 0);
-    mMaxTextureSize = tmp[0];
-    GLES30.glGetIntegerv(GLES30.GL_MAX_VERTEX_UNIFORM_VECTORS  , tmp, 0);
-    mMaxNumberOfVerUniforms = tmp[0];
-    GLES30.glGetIntegerv(GLES30.GL_MAX_FRAGMENT_UNIFORM_VECTORS, tmp, 0);
-    mMaxNumberOfFraUniforms = tmp[0];
-
-    mUser.logMessage("Using OpenGL ES "+major+"."+minor+" texSize="+mMaxTextureSize+" maxVerUniforms: "+mMaxNumberOfVerUniforms+" maxFraUniforms: "+mMaxNumberOfFraUniforms);
-
-    mGLSL_VERSION = "#version "+mGLSL+" es\n";
-
-    InternalStackFrameList.setInitialized(true);
-    mOITCompilationAttempted = false;
-
-    detectBuggyDriversAndSetQueueSize(queueSize);
-    EffectMessageSender.startSending();
-
-    try
-      {
-      createMainProgram();
-      }
-    catch(Exception ex)
-      {
-      mUser.distortedException(ex);
-      }
-
-    try
-      {
-      final InputStream vertStream = mUser.localFile(R.raw.main_vertex_shader);
-      final InputStream fragStream = mUser.localFile(R.raw.preprocess_fragment_shader);
-      EffectQueuePostprocess.createPrograms(vertStream, fragStream, mGLSL);
-      }
-    catch(Exception ex)
-      {
-      mUser.distortedException(ex);
-      }
-
-    try
-      {
-      PostprocessEffect.createPrograms(mGLSL);
-      }
-    catch(Exception ex)
-      {
-      mUser.distortedException(ex);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Switch face culling on/off
- */
-  public static void setCull(boolean on)
-    {
-    if( on )
-      {
-      GLES30.glEnable(GLES30.GL_CULL_FACE);
-      GLES30.glCullFace(GLES30.GL_FRONT);
-      }
-    else
-      {
-      GLES30.glDisable(GLES30.GL_CULL_FACE);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can initialize its internal data structures.
- * Must be called from Activity.onCreate().
- */
-  public static void onCreate()
-    {
-    onCreate(0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can initialize its internal data structures.
- * Must be called from Activity.onCreate().
- *
- * @param id id of an Activity that is using the library; anything unique so that the Library can
- *           tell between Activities in case you're going to be using it from more than one.
- */
-  public static void onCreate(long id)
-    {
-    InternalStackFrameList.onCreate(id);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can resume its operations.
- * Must be called from Activity.onResume().
- */
-  public static void onResume()
-    {
-    onResume(0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can resume its operations.
- * Must be called from Activity.onResume().
- *
- * @param id id of an Activity that is using the library; anything unique so that the Library can
- *           tell between Activities in case you're going to be using it from more than one.
- */
-  public static void onResume(long id)
-    {
-    InternalStackFrameList.onResume(id);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can release the OpenGL related data that needs to be recreated.
- * Must be called from Activity.onPause().
- */
-  public static void onPause()
-    {
-    onPause(0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can release the OpenGL related data that needs to be recreated.
- * Must be called from Activity.onPause().
- *
- * @param id id of an Activity that is using the library; anything unique so that the Library can
- *           tell between Activities in case you're going to be using it from more than one.
- */
-  public static void onPause(long id)
-    {
-    InternalStackFrameList.onPause(id);
-
-    Dynamic.onPause();  // common for all frames
-    InternalOutputSurface.onPause();
-    Effect.onPause();
-    DeferredJobs.onPause();
-
-    mOITCompilationAttempted = false;
-    mNeedsTransformFeedback  = false;
-
-    mLinkedListSSBO[0]= -1;
-    mAtomicCounter = null;
-
-    mNormalProgram     = null;
-    mMainOITProgram    = null;
-    mMainProgram       = null;
-    mFullProgram       = null;
-    mOITClearProgram   = null;
-    mOITBuildProgram   = null;
-    mOITCollapseProgram= null;
-    mOITRenderProgram  = null;
-    mBlitDepthProgram  = null;
-    mBlitProgram       = null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can release its internal data structures.
- * Must be called from Activity.onDestroy().
- */
-  public static void onDestroy()
-    {
-    onDestroy(0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this so that the Library can release its internal data structures.
- * Must be called from Activity.onDestroy().
- *
- * @param id id of an Activity that is using the library; anything unique so that the Library can
- *           tell between Activities in case you're going to be using it from more than one.
- */
-  public static void onDestroy(long id)
-    {
-    if( InternalStackFrameList.isInitialized() )
-      {
-      InternalStackFrameList.onDestroy(id);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Some devices - Qualcomm's Adreno 3xx with drivers v. 84,95,100,104,140, and possibly more -
- * suffer from a very slow compilation of GLSL program if said program includes Transform Feedback.
- * Return true if the platform we are running on does not suffer from this problem.
- */
-  public static boolean fastCompilationTF()
-    {
-    return mFastCompilationTF;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the maximum size of the texture supported by the driver.
- */
-  public static int getMaxTextureSize()
-    {
-    return mMaxTextureSize;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Call this before calling onSurfaceCreated() if you want to access normal vectors in CPU.
- */
-  public static void needTransformFeedback()
-    {
-    mNeedsTransformFeedback = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the maximum number of effects of a given type that can be simultaneously applied to a
- * single (InputSurface,MeshBase) combo.
- *
- * @param type {@link EffectType}
- * @return The maximum number of effects of a given type.
- */
-  @SuppressWarnings("unused")
-  public static int getMax(EffectType type)
-    {
-    return EffectQueue.getMax(type.ordinal());
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Sets the maximum number of 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 Vertex Shader gets compiled, i.e. before the call to {@link DistortedLibrary#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 type {@link EffectType}
- * @param max new maximum number of simultaneous effects. Has to be a non-negative number not greater
- *            than Byte.MAX_VALUE
- * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
- */
-  @SuppressWarnings("unused")
-  public static boolean setMax(EffectType type, int max)
-    {
-    return EffectQueue.setMax(type.ordinal(),max);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return an interger - max number of uniforms one can upload to a Vertex Shader.
- */
-  public static int getMaxVertexUniforms()
-    {
-    return mMaxNumberOfVerUniforms;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return an interger - max number of uniforms one can upload to a Fragment Shader.
- */
-  public static int getMaxFragmentUniforms()
-    {
-    return mMaxNumberOfFraUniforms;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return a String defining the vendor of the graphics driver.
- */
-  public static String getDriverVendor()
-    {
-    return mVendor;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return a String defining the version of the graphics driver.
- */
-  public static String getDriverVersion()
-    {
-    return mVersion;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return a String defining the renderer of the graphics driver.
- */
-  public static String getDriverRenderer()
-    {
-    return mRenderer;
-    }
-  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/main/DistortedLibrary.kt b/src/main/java/org/distorted/library/main/DistortedLibrary.kt
new file mode 100644
index 0000000..4f5c52d
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/DistortedLibrary.kt
@@ -0,0 +1,1361 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main;
+
+import android.opengl.GLES30;
+import android.opengl.GLES31;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.distorted.library.R;
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effectqueue.EffectQueue;
+import org.distorted.library.effectqueue.EffectQueuePostprocess;
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.FragmentEffect;
+import org.distorted.library.effect.PostprocessEffect;
+import org.distorted.library.effect.VertexEffect;
+import org.distorted.library.effectqueue.EffectQueueVertex;
+import org.distorted.library.mesh.DeferredJobs;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.message.EffectMessageSender;
+import org.distorted.library.program.DistortedProgram;
+import org.distorted.library.program.VertexCompilationException;
+import org.distorted.library.type.Dynamic;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * A singleton class used to control various global dialog_settings.
+ */
+public class DistortedLibrary
+  {
+  /**
+   * When creating an instance of a DistortedTexture from another instance, clone the Bitmap that's
+   * backing up our DistortedTexture.
+   * <p>
+   * This way we can have two DistortedTextures, both backed up by the same Bitmap, to which we can
+   * apply different effects. Used in the copy constructor.
+   */
+  public static final int CLONE_SURFACE = 0x1;
+  /**
+   * When creating an instance of a DistortedEffects from another instance, clone the Matrix Effects.
+   * <p>
+   * This way we can have two different DistortedEffects sharing the MATRIX queue.
+   */
+  public static final int CLONE_MATRIX = 0x2;
+  /**
+   * When creating an instance of a DistortedEffects from another instance, clone the Vertex Effects.
+   * <p>
+   * This way we can have two different DistortedEffects sharing the VERTEX queue.
+   */
+  public static final int CLONE_VERTEX  = 0x4;
+  /**
+   * When creating an instance of a DistortedEffects from another instance, clone the Fragment Effects.
+   * <p>
+   * This way we can have two different DistortedEffects sharing the FRAGMENT queue.
+   */
+  public static final int CLONE_FRAGMENT= 0x8;
+   /**
+   * When creating an instance of a DistortedEffects from another instance, clone the PostProcess Effects.
+   * <p>
+   * This way we can have two different DistortedEffects sharing the POSTPROCESS queue.
+   */
+  public static final int CLONE_POSTPROCESS= 0x10;
+  /**
+   * When creating an instance of a DistortedNode from another instance, clone the children Nodes.
+   * <p>
+   * This is mainly useful for creating many similar sub-trees and rendering then at different places
+   * on the screen with (optionally) different Effects.
+   */
+  public static final int CLONE_CHILDREN= 0x20;
+
+  /**
+   * When creating a DistortedScreen (which needs to have mFBOQueueSize FBOs attached), pass this
+   * constant for 'numOfFBOs' and the number of backing FBOs will be taken from 'mFBOQueueSize'
+   * (the value of which is most likely unknown at the time of creation of the Screen)
+   */
+  public static final int WAIT_FOR_FBO_QUEUE_SIZE = -1;
+  /**
+   * Work around bugs in ARM Mali driver by, instead to a single FBO, rendering to a circular queue
+   * of mFBOQueueSize FBOs. (otherwise we sometimes get a 'full pipeline flush' and the end result
+   * might be missing part of the Objects)
+   * <p>
+   * This bug only exists on Mali driver r12. (or more precisely it's there in r12 but fixed in r22)
+   * <p>
+   * <a href="https://community.arm.com/graphics/f/discussions/10285/opengl-es-3-1-on-mali-t880-flashes">...</a>
+   */
+  private static int mFBOQueueSize;
+  private static int mGLSL;
+  private static String mGLSL_VERSION;
+  private static boolean mOITCompilationAttempted, mNeedsTransformFeedback;
+
+  private static int mMaxTextureSize         = Integer.MAX_VALUE;
+  private static int mMaxNumberOfVerUniforms = Integer.MAX_VALUE;
+  private static int mMaxNumberOfFraUniforms = Integer.MAX_VALUE;
+
+  private static boolean mBuggyUBOs;
+  private static String mVendor, mVersion, mRenderer;
+  private static boolean mFastCompilationTF;
+
+  //////////////////////////////////////////////////////////////////////////////////////////////
+  /// MAIN PROGRAM ///
+  private static DistortedProgram mMainProgram;
+  private static int mMainTextureH;
+  private static int mTransformFeedbackH;
+
+  /// NORMAL PROGRAM /////
+  private static DistortedProgram mNormalProgram;
+  private static int mNormalProjectionH;
+
+  /// MAIN OIT PROGRAM ///
+  private static DistortedProgram mMainOITProgram;
+  private static int mMainOITTextureH;
+  private static int mMainOITSizeH;
+  private static int mMainOITNumRecordsH;
+
+  /// BLIT PROGRAM ///
+  private static DistortedProgram mBlitProgram;
+  private static int mBlitTextureH;
+  private static int mBlitDepthH;
+  private static final FloatBuffer mQuadPositions;
+
+  /// FULL PROGRAM ///
+  private static DistortedProgram mFullProgram;
+
+  static
+    {
+    float[] positionData= { -0.5f, -0.5f,  -0.5f, 0.5f,  0.5f,-0.5f,  0.5f, 0.5f };
+    mQuadPositions = ByteBuffer.allocateDirect(32).order(ByteOrder.nativeOrder()).asFloatBuffer();
+    mQuadPositions.put(positionData).position(0);
+    }
+
+  /// BLIT DEPTH PROGRAM ///
+  private static DistortedProgram mBlitDepthProgram;
+  private static int mBlitDepthTextureH;
+  private static int mBlitDepthDepthTextureH;
+  private static int mBlitDepthTexCorrH;
+
+  /// Program Handles ///
+  private static int mMainProgramH, mFullProgramH, mMainOITProgramH;
+
+  /// OIT SSBO BUFFER ///
+  private static final int[] mLinkedListSSBO = new int[1];
+  private static int[] mAtomicCounter;
+  private static int   mCurrBuffer;
+
+  static
+    {
+    mLinkedListSSBO[0]= -1;
+    mCurrBuffer       =  0;
+    }
+
+  ///////////////////////////////////////////////////////////////
+  // meaning: allocate 1.0 screenful of places for transparent
+  // fragments in the SSBO backing up the OIT render method.
+  private static float mBufferSize=1.0f;
+
+  /// OIT CLEAR PROGRAM ///
+  private static DistortedProgram mOITClearProgram;
+  private static int mOITClearDepthH;
+  private static int mOITClearTexCorrH;
+  private static int mOITClearSizeH;
+
+  /// OIT BUILD PROGRAM ///
+  private static DistortedProgram mOITBuildProgram;
+  private static int mOITBuildTextureH;
+  private static int mOITBuildDepthTextureH;
+  private static int mOITBuildDepthH;
+  private static int mOITBuildTexCorrH;
+  private static int mOITBuildSizeH;
+  private static int mOITBuildNumRecordsH;
+
+  /// OIT COLLAPSE PROGRAM ///
+  private static DistortedProgram mOITCollapseProgram;
+  private static int mOITCollapseDepthTextureH;
+  private static int mOITCollapseDepthH;
+  private static int mOITCollapseTexCorrH;
+  private static int mOITCollapseSizeH;
+
+  /// OIT RENDER PROGRAM ///
+  private static DistortedProgram mOITRenderProgram;
+  private static int mOITRenderDepthH;
+  private static int mOITRenderTexCorrH;
+  private static int mOITRenderSizeH;
+
+  /// END PROGRAMS //////
+
+  /**
+   * Every application using the library must implement this interface so that the library can send
+   * it exceptions that arise. The exceptions may come at any time, for example the library will
+   * compile its OIT problem only on the first attempt to use the OIT
+   * Those will mainly be hardware-related: shaders do not compile on particular hardware, the required
+   * OpenGL ES 3.0 is not supported, etc.
+   * <p>
+   * Additionally, the User must be able to provide version of the OpenGL supported by the underlying
+   * hardware and InputStreams to given local files.
+   */
+  public interface LibraryUser
+    {
+    void distortedException(Exception ex);
+    InputStream localFile(int fileID);
+    void logMessage(String message);
+    }
+
+  private static LibraryUser mUser;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// private: hide this from Javadoc
+
+  private DistortedLibrary()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createMainProgram()
+    {
+    // MAIN PROGRAM ////////////////////////////////////
+    final InputStream mainVertStream = mUser.localFile(R.raw.main_vertex_shader);
+    final InputStream mainFragStream = mUser.localFile(R.raw.main_fragment_shader);
+
+    int numF = FragmentEffect.getNumEnabled();
+    int numV = VertexEffect.getNumEnabled();
+
+    String mainVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n");
+    String mainFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n");
+
+    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
+    if( MeshBase.getUseCenters() ) mainVertHeader += "#define COMP_CENTERS\n";
+    if( mBuggyUBOs )               mainVertHeader += "#define BUGGY_UBOS\n";
+
+    String enabledEffectV= VertexEffect.getGLSL();
+    String enabledEffectF= FragmentEffect.getGLSL();
+
+    String[] feedback = { "v_Position", "v_endPosition" };
+
+    try
+      {
+      mMainProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader,
+                                          mainFragHeader, enabledEffectV, enabledEffectF,
+                                          mGLSL, mNeedsTransformFeedback ? feedback : null );
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile MAIN program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    mMainProgramH = mMainProgram.getProgramHandle();
+    EffectQueue.getUniforms(mMainProgramH,0);
+    MeshBase.getUniforms(mMainProgramH,0);
+    mMainTextureH= GLES30.glGetUniformLocation( mMainProgramH, "u_Texture");
+    mTransformFeedbackH= GLES30.glGetUniformLocation( mMainProgramH, "u_TransformFeedback");
+
+    // BLIT PROGRAM ////////////////////////////////////
+    final InputStream blitVertStream = mUser.localFile(R.raw.blit_vertex_shader);
+    final InputStream blitFragStream = mUser.localFile(R.raw.blit_fragment_shader);
+
+    try
+      {
+      mBlitProgram = new DistortedProgram(blitVertStream,blitFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile BLIT program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int blitProgramH = mBlitProgram.getProgramHandle();
+    mBlitTextureH  = GLES30.glGetUniformLocation( blitProgramH, "u_Texture");
+    mBlitDepthH    = GLES30.glGetUniformLocation( blitProgramH, "u_Depth");
+
+    // BLIT DEPTH PROGRAM ////////////////////////////////////
+    final InputStream blitDepthVertStream = mUser.localFile(R.raw.blit_depth_vertex_shader);
+    final InputStream blitDepthFragStream = mUser.localFile(R.raw.blit_depth_fragment_shader);
+
+    try
+      {
+      mBlitDepthProgram = new DistortedProgram(blitDepthVertStream,blitDepthFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile BLIT DEPTH program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int blitDepthProgramH   = mBlitDepthProgram.getProgramHandle();
+    mBlitDepthTextureH      = GLES30.glGetUniformLocation( blitDepthProgramH, "u_Texture");
+    mBlitDepthDepthTextureH = GLES30.glGetUniformLocation( blitDepthProgramH, "u_DepthTexture");
+    mBlitDepthTexCorrH      = GLES30.glGetUniformLocation( blitDepthProgramH, "u_TexCorr");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createNormalProgram()
+    {
+    // NORMAL PROGRAM //////////////////////////////////////
+    final InputStream normalVertexStream   = mUser.localFile(R.raw.normal_vertex_shader);
+    final InputStream normalFragmentStream = mUser.localFile(R.raw.normal_fragment_shader);
+
+    try
+      {
+      mNormalProgram = new DistortedProgram(normalVertexStream,normalFragmentStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile NORMAL program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int normalProgramH = mNormalProgram.getProgramHandle();
+    mNormalProjectionH = GLES30.glGetUniformLocation( normalProgramH, "u_Projection");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createFullProgram()
+    {
+    final InputStream fullVertStream = mUser.localFile(R.raw.main_vertex_shader);
+    final InputStream fullFragStream = mUser.localFile(R.raw.main_fragment_shader);
+
+    int numV = VertexEffect.getAllEnabled();
+
+    String fullVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX ) : 0 ) + "\n");
+    String fullFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " +                                         0   + "\n");
+
+    fullVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
+    if( MeshBase.getUseCenters() ) fullVertHeader += "#define COMP_CENTERS\n";
+    if( mBuggyUBOs )               fullVertHeader += "#define BUGGY_UBOS\n";
+
+    String enabledEffectV= VertexEffect.getAllGLSL();
+    String enabledEffectF= "{}";
+
+    fullVertHeader += "#define PREAPPLY\n";
+
+    String[] feedback = { "v_Position", "v_endPosition" };
+
+    try
+      {
+      mFullProgram = new DistortedProgram(fullVertStream, fullFragStream, fullVertHeader, fullFragHeader,
+                                          enabledEffectV, enabledEffectF, mGLSL, feedback);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile FULL program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    mFullProgramH = mFullProgram.getProgramHandle();
+    EffectQueue.getUniforms(mFullProgramH,3);
+    MeshBase.getUniforms(mFullProgramH,3);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createOITProgram()
+    {
+    // MAIN OIT PROGRAM ////////////////////////////////
+    final InputStream mainVertStream = mUser.localFile(R.raw.main_vertex_shader);
+    final InputStream mainFragStream = mUser.localFile(R.raw.main_fragment_shader);
+
+    int numF = FragmentEffect.getNumEnabled();
+    int numV = VertexEffect.getNumEnabled();
+
+    String mainVertHeader= mGLSL_VERSION + ("#define NUM_VERTEX "   + ( numV>0 ? getMax(EffectType.VERTEX  ) : 0 ) + "\n") + ("#define OIT\n");
+    String mainFragHeader= mGLSL_VERSION + ("#define NUM_FRAGMENT " + ( numF>0 ? getMax(EffectType.FRAGMENT) : 0 ) + "\n") + ("#define OIT\n");
+
+    mainVertHeader += "#define MAX_COMPON " + MeshBase.getMaxEffComponents() + "\n";
+    if( MeshBase.getUseCenters() ) mainVertHeader += "#define COMP_CENTERS\n";
+    if( mBuggyUBOs )               mainVertHeader += "#define BUGGY_UBOS\n";
+
+    String enabledEffectV= VertexEffect.getGLSL();
+    String enabledEffectF= FragmentEffect.getGLSL();
+
+    try
+      {
+      mMainOITProgram = new DistortedProgram(mainVertStream, mainFragStream, mainVertHeader, mainFragHeader,
+                                             enabledEffectV, enabledEffectF, mGLSL, null);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile MAIN OIT program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    mMainOITProgramH = mMainOITProgram.getProgramHandle();
+    EffectQueue.getUniforms(mMainOITProgramH,1);
+    MeshBase.getUniforms(mMainOITProgramH,1);
+    mMainOITTextureH    = GLES30.glGetUniformLocation( mMainOITProgramH, "u_Texture");
+    mMainOITSizeH       = GLES30.glGetUniformLocation( mMainOITProgramH, "u_Size");
+    mMainOITNumRecordsH = GLES30.glGetUniformLocation( mMainOITProgramH, "u_numRecords");
+
+    // OIT CLEAR PROGRAM ////////////////////////////////////
+    final InputStream oitClearVertStream = mUser.localFile(R.raw.oit_vertex_shader);
+    final InputStream oitClearFragStream = mUser.localFile(R.raw.oit_clear_fragment_shader);
+
+    try
+      {
+      mOITClearProgram = new DistortedProgram(oitClearVertStream,oitClearFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT CLEAR program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitClearProgramH   = mOITClearProgram.getProgramHandle();
+    mOITClearDepthH        = GLES30.glGetUniformLocation( oitClearProgramH, "u_Depth");
+    mOITClearTexCorrH      = GLES30.glGetUniformLocation( oitClearProgramH, "u_TexCorr");
+    mOITClearSizeH         = GLES30.glGetUniformLocation( oitClearProgramH, "u_Size");
+
+    // OIT BUILD PROGRAM ////////////////////////////////////
+    final InputStream oitBuildVertStream = mUser.localFile(R.raw.oit_vertex_shader);
+    final InputStream oitBuildFragStream = mUser.localFile(R.raw.oit_build_fragment_shader);
+
+    try
+      {
+      mOITBuildProgram = new DistortedProgram(oitBuildVertStream,oitBuildFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT BUILD program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitBuildProgramH   = mOITBuildProgram.getProgramHandle();
+    mOITBuildTextureH      = GLES30.glGetUniformLocation( oitBuildProgramH, "u_Texture");
+    mOITBuildDepthTextureH = GLES30.glGetUniformLocation( oitBuildProgramH, "u_DepthTexture");
+    mOITBuildDepthH        = GLES30.glGetUniformLocation( oitBuildProgramH, "u_Depth");
+    mOITBuildTexCorrH      = GLES30.glGetUniformLocation( oitBuildProgramH, "u_TexCorr");
+    mOITBuildSizeH         = GLES30.glGetUniformLocation( oitBuildProgramH, "u_Size");
+    mOITBuildNumRecordsH   = GLES30.glGetUniformLocation( oitBuildProgramH, "u_numRecords");
+
+    // OIT COLLAPSE PROGRAM ///////////////////////////
+    final InputStream oitCollapseVertStream = mUser.localFile(R.raw.oit_vertex_shader);
+    final InputStream oitCollapseFragStream = mUser.localFile(R.raw.oit_collapse_fragment_shader);
+
+    try
+      {
+      mOITCollapseProgram = new DistortedProgram(oitCollapseVertStream,oitCollapseFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT COLLAPSE program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitCollapseProgramH   = mOITCollapseProgram.getProgramHandle();
+    mOITCollapseDepthTextureH = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_DepthTexture");
+    mOITCollapseDepthH        = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_Depth");
+    mOITCollapseTexCorrH      = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_TexCorr");
+    mOITCollapseSizeH         = GLES30.glGetUniformLocation( oitCollapseProgramH, "u_Size");
+
+    // OIT RENDER PROGRAM ///////////////////////////
+    final InputStream oitRenderVertStream = mUser.localFile(R.raw.oit_vertex_shader);
+    final InputStream oitRenderFragStream = mUser.localFile(R.raw.oit_render_fragment_shader);
+
+    try
+      {
+      mOITRenderProgram = new DistortedProgram(oitRenderVertStream,oitRenderFragStream, mGLSL_VERSION, mGLSL_VERSION, mGLSL);
+      }
+    catch(Exception e)
+      {
+      mUser.logMessage(e.getClass().getSimpleName()+" trying to compile OIT RENDER program: "+e.getMessage());
+      throw new RuntimeException(e.getMessage());
+      }
+
+    int oitRenderProgramH   = mOITRenderProgram.getProgramHandle();
+    mOITRenderDepthH        = GLES30.glGetUniformLocation( oitRenderProgramH, "u_Depth");
+    mOITRenderTexCorrH      = GLES30.glGetUniformLocation( oitRenderProgramH, "u_TexCorr");
+    mOITRenderSizeH         = GLES30.glGetUniformLocation( oitRenderProgramH, "u_Size");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void displayNormals(float[] projection, MeshBase mesh)
+    {
+    if( mNormalProgram==null )
+      {
+      try
+        {
+        createNormalProgram();
+        }
+      catch(Exception ex)
+        {
+        mUser.distortedException(ex);
+        return;
+        }
+      }
+
+    int num = mesh.getNumVertices();
+    int tfo = mesh.getTFO();
+
+    GLES30.glUniform1i(DistortedLibrary.mTransformFeedbackH, 1);
+    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
+    GLES30.glBeginTransformFeedback( GLES30.GL_POINTS);
+    InternalRenderState.switchOffDrawing();
+    GLES30.glDrawArrays( GLES30.GL_POINTS, 0, num );
+    InternalRenderState.restoreDrawing();
+    GLES30.glEndTransformFeedback();
+    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
+    GLES30.glUniform1i(DistortedLibrary.mTransformFeedbackH, 0);
+
+    mNormalProgram.useProgram();
+    GLES30.glUniformMatrix4fv(mNormalProjectionH, 1, false, projection, 0);
+    mesh.bindTransformAttribs(mNormalProgram);
+    GLES30.glLineWidth(8.0f);
+    GLES30.glDrawArrays(GLES30.GL_LINES, 0, 2*num);
+    mNormalProgram.stopUsingProgram();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Execute all VertexEffects and adjust all vertices
+ */
+  public static void adjustVertices(MeshBase mesh, EffectQueueVertex queue)
+    {
+    if( mFullProgram==null )
+      {
+      try
+        {
+        createFullProgram();
+        }
+      catch(Exception ex)
+        {
+        mUser.distortedException(ex);
+        return;
+        }
+      }
+
+    int num = mesh.getNumVertices();
+    int tfo = mesh.getTFO();
+
+    mFullProgram.useProgram();
+    mesh.bindVertexAttribs(mFullProgram);
+    queue.compute(1,0);
+    queue.send(0.0f,mFullProgramH,3);
+    mesh.send(mFullProgramH,3);
+
+    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfo );
+    GLES30.glBeginTransformFeedback( GLES30.GL_POINTS);
+    InternalRenderState.switchOffDrawing();
+    GLES30.glDrawArrays( GLES30.GL_POINTS, 0, num );
+    InternalRenderState.restoreDrawing();
+    GLES30.glEndTransformFeedback();
+    mesh.copyTransformToVertex();
+    GLES30.glBindBufferBase(GLES30.GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
+    mFullProgram.stopUsingProgram();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void drawPrivOIT(DistortedEffects effects, MeshBase mesh, InternalOutputSurface surface, long currTime, long step)
+    {
+    if( mMainOITProgram!=null )
+      {
+      int w = surface.getWidth();
+      int h = surface.getHeight();
+      EffectQueue[] queues = effects.getQueues();
+
+      EffectQueue.compute(queues, currTime, step);
+      GLES30.glViewport(0, 0, w, h );
+
+      mMainOITProgram.useProgram();
+      GLES30.glUniform1i(mMainOITTextureH, 0);
+      GLES30.glUniform2ui(mMainOITSizeH, w, h );
+      GLES30.glUniform1ui(mMainOITNumRecordsH, (int)(mBufferSize*w*h) );
+      mesh.bindVertexAttribs(mMainOITProgram);
+      mesh.send(mMainOITProgramH,1);
+
+      float inflate     = mesh.getInflate();
+      float distance    = surface.mDistance;
+      float mipmap      = surface.mMipmap;
+      float[] projection= surface.mProjectionMatrix;
+
+      EffectQueue.send(queues, mMainOITProgramH, distance, mipmap, projection, inflate, 1 );
+      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
+      if( mesh.getShowNormals() ) displayNormals(projection,mesh);
+      mMainOITProgram.stopUsingProgram();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void drawPriv(DistortedEffects effects, MeshBase mesh, InternalOutputSurface surface, long currTime, long step)
+    {
+    if( mMainProgram!=null )
+      {
+      EffectQueue[] queues = effects.getQueues();
+      int w = surface.getWidth();
+      int h = surface.getHeight();
+      EffectQueue.compute(queues, currTime, step);
+      GLES30.glViewport(0, 0, w, h);
+
+      mMainProgram.useProgram();
+      GLES30.glUniform1i(mMainTextureH, 0);
+      mesh.bindVertexAttribs(mMainProgram);
+      mesh.send(mMainProgramH,0);
+
+      float inflate     = mesh.getInflate();
+      float distance    = surface.mDistance;
+      float mipmap      = surface.mMipmap;
+      float[] projection= surface.mProjectionMatrix;
+
+      EffectQueue.send(queues, mMainProgramH, distance, mipmap, projection, inflate, 0 );
+      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.getNumVertices() );
+      if( mesh.getShowNormals() ) displayNormals(projection,mesh);
+      mMainProgram.stopUsingProgram();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void blitPriv(InternalOutputSurface surface)
+    {
+    if( mBlitProgram!=null )
+      {
+      int w = surface.getWidth();
+      int h = surface.getHeight();
+      float n = surface.getNear();
+      mBlitProgram.useProgram();
+      GLES30.glViewport(0, 0, w, h);
+      GLES30.glUniform1i(mBlitTextureH, 0);
+      GLES30.glUniform1f( mBlitDepthH , 1.0f-n);
+      GLES30.glVertexAttribPointer(mBlitProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+      mBlitProgram.stopUsingProgram();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void blitDepthPriv(InternalOutputSurface surface, float corrW, float corrH)
+    {
+    if( mBlitDepthProgram!=null )
+      {
+      mBlitDepthProgram.useProgram();
+      int w = surface.getWidth();
+      int h = surface.getHeight();
+      GLES30.glViewport(0, 0, w, h);
+      GLES30.glUniform1i(mBlitDepthTextureH, 0);
+      GLES30.glUniform1i(mBlitDepthDepthTextureH, 1);
+      GLES30.glUniform2f(mBlitDepthTexCorrH, corrW, corrH );
+      GLES30.glVertexAttribPointer(mBlitDepthProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+      mBlitDepthProgram.stopUsingProgram();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// yes it is safe to be mixing 3.0 and 3.1 like that, senior members of the OpenGL discussions forum assert
+
+  private static int printPreviousBuffer()
+    {
+    int counter = 0;
+
+    ByteBuffer atomicBuf = (ByteBuffer)GLES30.glMapBufferRange( GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, 4,
+                                                                GLES30.GL_MAP_READ_BIT);
+    if( atomicBuf!=null )
+      {
+      IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
+      counter = atomicIntBuf.get(0);
+      }
+    else
+      {
+      mUser.logMessage("printPreviousBuffer: failed to map atomic buffer");
+      }
+
+    GLES30.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
+
+    return counter;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void zeroBuffer()
+    {
+    ByteBuffer atomicBuf = (ByteBuffer)GLES30.glMapBufferRange( GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, 4,
+                                                                GLES30.GL_MAP_WRITE_BIT|GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
+    if( atomicBuf!=null )
+      {
+      IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
+      atomicIntBuf.put(0,0);
+      }
+    else
+      {
+      mUser.logMessage("zeroBuffer: failed to map atomic buffer");
+      }
+
+    GLES30.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// reset atomic counter to 0
+
+  static int zeroOutAtomic()
+    {
+    int counter = 0;
+
+    if( mAtomicCounter==null )
+      {
+      mAtomicCounter = new int[mFBOQueueSize];
+
+      GLES30.glGenBuffers(mFBOQueueSize,mAtomicCounter,0);
+
+      for(int i=0; i<mFBOQueueSize; i++)
+        {
+        GLES30.glBindBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER, mAtomicCounter[i]);
+        GLES30.glBufferData(GLES31.GL_ATOMIC_COUNTER_BUFFER, 4, null, GLES30.GL_DYNAMIC_DRAW);
+        zeroBuffer();
+        }
+      }
+
+    // reading the value of the buffer on every frame would slow down rendering by
+    // about 3%; doing it only once every 5 frames affects speed by less than 1%.
+    if( mCurrBuffer==0 )
+      {
+      GLES30.glBindBufferBase(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, mAtomicCounter[mCurrBuffer]);
+      counter = printPreviousBuffer();
+      }
+
+    if( ++mCurrBuffer>=mFBOQueueSize ) mCurrBuffer = 0;
+
+    GLES30.glBindBufferBase(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, mAtomicCounter[mCurrBuffer]);
+    zeroBuffer();
+
+    return counter;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Pass1 of the OIT algorithm. Clear per-pixel head-pointers.
+
+  static void oitClear(InternalOutputSurface surface, int counter)
+    {
+    if( mOITClearProgram==null )
+      {
+      if( mGLSL>=310 && !mOITCompilationAttempted )
+        {
+        mOITCompilationAttempted = true;
+
+        try
+          {
+          createOITProgram();
+          }
+        catch(Exception ex)
+          {
+          mUser.distortedException(ex);
+          return;
+          }
+        }
+      else
+        {
+        return;
+        }
+      }
+
+
+    int w = surface.getWidth();
+    int h = surface.getHeight();
+
+    if( mLinkedListSSBO[0]<0 )
+      {
+      GLES30.glGenBuffers(1,mLinkedListSSBO,0);
+
+      int size = (int)(w*h*(3*mBufferSize+1)*4);
+      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, mLinkedListSSBO[0]);
+      GLES30.glBufferData(GLES31.GL_SHADER_STORAGE_BUFFER, size, null, GLES30.GL_DYNAMIC_READ|GLES30.GL_DYNAMIC_DRAW);
+      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, 0);
+
+      GLES30.glBindBufferBase(GLES31.GL_SHADER_STORAGE_BUFFER, 1, mLinkedListSSBO[0]);
+      }
+
+    // See if we have overflown the SSBO in one of the previous frames.
+    // If yes, assume we need to make the SSBO larger.
+    float overflow = counter/(mBufferSize*w*h);
+
+    if( overflow>1.0f )
+      {
+      mBufferSize *= (int)(overflow+1.0f);
+      int size = (int)(w*h*(3*mBufferSize+1)*4);
+      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, mLinkedListSSBO[0]);
+      GLES30.glBufferData(GLES31.GL_SHADER_STORAGE_BUFFER, size, null, GLES30.GL_DYNAMIC_READ|GLES30.GL_DYNAMIC_DRAW);
+      GLES30.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, 0);
+      }
+
+    mOITClearProgram.useProgram();
+    GLES30.glViewport(0, 0, w, h);
+    GLES30.glUniform2f(mOITClearTexCorrH, 1.0f, 1.0f );   // corrections do not really matter here - only present because of common vertex shader.
+    GLES30.glUniform1f( mOITClearDepthH , 1.0f);          // likewise depth
+    GLES30.glUniform2ui(mOITClearSizeH, w, h);
+    GLES30.glVertexAttribPointer(mOITClearProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+    GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+    mOITClearProgram.stopUsingProgram();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Pass2 of the OIT algorithm - build per-pixel linked lists.
+
+  static void oitBuild(InternalOutputSurface surface, float corrW, float corrH)
+    {
+    if( mOITBuildProgram!=null )
+      {
+      int w = surface.getWidth();
+      int h = surface.getHeight();
+      float n = surface.getNear();
+      mOITBuildProgram.useProgram();
+      GLES30.glViewport(0, 0, w, h);
+      GLES30.glUniform1i(mOITBuildTextureH, 0);
+      GLES30.glUniform1i(mOITBuildDepthTextureH, 1);
+      GLES30.glUniform2f(mOITBuildTexCorrH, corrW, corrH );
+      GLES30.glUniform2ui(mOITBuildSizeH, w, h);
+      GLES30.glUniform1ui(mOITBuildNumRecordsH, (int)(mBufferSize*w*h) );
+      GLES30.glUniform1f(mOITBuildDepthH , 1.0f-n);
+      GLES30.glVertexAttribPointer(mOITBuildProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+      mOITBuildProgram.stopUsingProgram();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Pass3 of the OIT algorithm. Cut occluded parts of the linked list.
+
+  static void oitCollapse(InternalOutputSurface surface, float corrW, float corrH)
+    {
+    if( mOITCollapseProgram!=null )
+      {
+      int w = surface.getWidth();
+      int h = surface.getHeight();
+      float n = surface.getNear();
+      mOITCollapseProgram.useProgram();
+      GLES30.glViewport(0, 0, w, h);
+      GLES30.glUniform1i(mOITCollapseDepthTextureH, 1);
+      GLES30.glUniform2f(mOITCollapseTexCorrH, corrW, corrH );
+      GLES30.glUniform2ui(mOITCollapseSizeH, w, h);
+      GLES30.glUniform1f( mOITCollapseDepthH , 1.0f-n);
+      GLES30.glVertexAttribPointer(mOITCollapseProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+      mOITCollapseProgram.stopUsingProgram();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Pass4 of the OIT algorithm. Render all the transparent pixels from the per-pixel linked lists.
+
+  static void oitRender(InternalOutputSurface surface, float corrW, float corrH)
+    {
+    if( mOITRenderProgram!=null )
+      {
+      int w = surface.getWidth();
+      int h = surface.getHeight();
+      float n = surface.getNear();
+      mOITRenderProgram.useProgram();
+      GLES30.glViewport(0, 0, w, h);
+      GLES30.glUniform2f(mOITRenderTexCorrH, corrW, corrH );
+      GLES30.glUniform2ui(mOITRenderSizeH, w, h);
+      GLES30.glUniform1f( mOITRenderDepthH , 1.0f-n);
+      GLES30.glVertexAttribPointer(mOITRenderProgram.mAttribute[0], 2, GLES30.GL_FLOAT, false, 0, mQuadPositions);
+      GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
+      mOITRenderProgram.stopUsingProgram();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void setSSBOSize(float size)
+    {
+    mBufferSize = size;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static int getQueueSize()
+    {
+    return mFBOQueueSize;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int parseQualcommDriverVersion(String version)
+    {
+    int at = version.indexOf('@');
+
+    if( at>=0 )
+      {
+      int dot = version.indexOf('.',at);
+
+      if( dot>at+1 )
+        {
+        String ver = version.substring(at+1,dot);
+        return Integer.parseInt(ver);
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// ARM Mali driver r12 has problems when we keep swapping many FBOs (fixed in r22)
+// PowerVR GE8100 / GE8300 compiler fails to compile OIT programs.
+
+  private static void detectBuggyDriversAndSetQueueSize(int queueSize)
+    {
+    mVendor  = GLES30.glGetString(GLES30.GL_VENDOR);
+    mVersion = GLES30.glGetString(GLES30.GL_VERSION);
+    mRenderer= GLES30.glGetString(GLES30.GL_RENDERER);
+
+    mFBOQueueSize = 1;
+    mFastCompilationTF = true;
+
+    if( mVendor.contains("ARM") )
+      {
+      try
+        {
+        String regex = ".*r(\\d+)p\\d.*";
+        Pattern pattern = Pattern.compile(regex);
+        Matcher matcher = pattern.matcher(mVersion);
+
+        if( matcher.find() )
+          {
+          String driverVersion = matcher.group(1);
+
+          if( driverVersion!=null )
+            {
+            int drvVersion = Integer.parseInt(driverVersion);
+
+            if( drvVersion<22 )
+              {
+              mUser.logMessage("You are running this on a ARM Mali driver r"+driverVersion+".\n" +
+                    "This is a buggy driver, please update to r22. Inserting workaround which uses a lot of memory.");
+
+              mFBOQueueSize = queueSize;
+              }
+            }
+          }
+        }
+      catch(Exception ex)
+        {
+        mUser.logMessage("DistortedLibrary: exception trying to pattern match version: "+ ex);
+        }
+      }
+    else if( mVendor.contains("Imagination") )
+      {
+      if( mRenderer.contains("GE8") )
+        {
+        mUser.logMessage("You are running this on a PowerVR GE8XXX.\nDue to a buggy compiler OIT rendering will not work");
+        mUser.logMessage("GLSL Version "+GLES30.glGetString(GLES31.GL_SHADING_LANGUAGE_VERSION));
+        }
+      }
+    else if( mVendor.contains("Qualcomm"))
+      {
+      int driverVersion = parseQualcommDriverVersion(mVersion);
+
+      if( mRenderer.contains("308") && (driverVersion==331 || driverVersion==415) )
+        {
+        mUser.logMessage("You are running this on an Adreno 308 driver 331 or 415.\nStrange shit might happen.");
+        mBuggyUBOs = true;
+        }
+      if( mGLSL<=300 && driverVersion<415 ) // This is only on old enough drivers, 84,95,100,104,140 and known bad, 415 is known good.
+        {
+        mUser.logMessage("Slow compilation of Transform Feedback programs!");
+        mFastCompilationTF = false;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int getVersion()
+    {
+    int[] major = new int[1];
+    int[] minor = new int[1];
+    GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, major, 0);
+    GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, minor, 0);
+    return major[0] * 100 + minor[0] * 10;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  public static void logMessage(String message)
+    {
+    mUser.logMessage(message);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return OpenGL ES version supported by the hardware we are running on.
+ * There are only three possibilities: 300 (OpenGL ES 3.0) or 310 (at least OpenGL ES 3.1)
+ * or 200 (OpenGL ES 2.0)
+ */
+  public static int getGLSL()
+    {
+    return mGLSL;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Are we running this on hardware where UBOs are buggy?
+ */
+  public static boolean isUBOBuggy()
+    {
+    return mBuggyUBOs;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When OpenGL context gets created, call this method so that the library can initialise its internal data structures.
+ * I.e. best called from GLSurfaceView.Renderer.onSurfaceCreated().
+ * <p>
+ * Needs to be called from a thread holding the OpenGL context.
+ *
+ * @param user The Code which will be using this library, which must implement the LibraryUser interface.
+ */
+  public static void onSurfaceCreated(final LibraryUser user)
+    {
+    onSurfaceCreated(user,4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When OpenGL context gets created, call this method so that the library can initialise its internal data structures.
+ * I.e. best called from GLSurfaceView.Renderer.onSurfaceCreated().
+ * <p>
+ * Needs to be called from a thread holding the OpenGL context.
+ *   
+ * @param user The Code which will be using this library, which must implement the LibraryUser interface.
+ * @param queueSize the size of the FBO queue, a workaround for the bug on Mali drivers. Use a small integer - 1,...,4
+ */
+  public static void onSurfaceCreated(final LibraryUser user, int queueSize)
+    {
+    mUser = user;
+    int version = getVersion();
+    int major = version/100;
+    int minor = ((version/10)%10);
+
+    if( major< 3 )
+      {
+      mGLSL = 100*major + 10*minor;
+      VertexCompilationException ex = new VertexCompilationException("at least OpenGL ES 3.0 required, this device supports only "+major+"."+minor);
+      mUser.distortedException(ex);
+      }
+    else
+      {
+      mGLSL = (major==3 && minor==0) ? 300 : 310;
+      }
+
+    int[] tmp = new int[1];
+    GLES30.glGetIntegerv(GLES30.GL_MAX_TEXTURE_SIZE, tmp, 0);
+    mMaxTextureSize = tmp[0];
+    GLES30.glGetIntegerv(GLES30.GL_MAX_VERTEX_UNIFORM_VECTORS  , tmp, 0);
+    mMaxNumberOfVerUniforms = tmp[0];
+    GLES30.glGetIntegerv(GLES30.GL_MAX_FRAGMENT_UNIFORM_VECTORS, tmp, 0);
+    mMaxNumberOfFraUniforms = tmp[0];
+
+    mUser.logMessage("Using OpenGL ES "+major+"."+minor+" texSize="+mMaxTextureSize+" maxVerUniforms: "+mMaxNumberOfVerUniforms+" maxFraUniforms: "+mMaxNumberOfFraUniforms);
+
+    mGLSL_VERSION = "#version "+mGLSL+" es\n";
+
+    InternalStackFrameList.setInitialized(true);
+    mOITCompilationAttempted = false;
+
+    detectBuggyDriversAndSetQueueSize(queueSize);
+    EffectMessageSender.startSending();
+
+    try
+      {
+      createMainProgram();
+      }
+    catch(Exception ex)
+      {
+      mUser.distortedException(ex);
+      }
+
+    try
+      {
+      final InputStream vertStream = mUser.localFile(R.raw.main_vertex_shader);
+      final InputStream fragStream = mUser.localFile(R.raw.preprocess_fragment_shader);
+      EffectQueuePostprocess.createPrograms(vertStream, fragStream, mGLSL);
+      }
+    catch(Exception ex)
+      {
+      mUser.distortedException(ex);
+      }
+
+    try
+      {
+      PostprocessEffect.createPrograms(mGLSL);
+      }
+    catch(Exception ex)
+      {
+      mUser.distortedException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Switch face culling on/off
+ */
+  public static void setCull(boolean on)
+    {
+    if( on )
+      {
+      GLES30.glEnable(GLES30.GL_CULL_FACE);
+      GLES30.glCullFace(GLES30.GL_FRONT);
+      }
+    else
+      {
+      GLES30.glDisable(GLES30.GL_CULL_FACE);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can initialize its internal data structures.
+ * Must be called from Activity.onCreate().
+ */
+  public static void onCreate()
+    {
+    onCreate(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can initialize its internal data structures.
+ * Must be called from Activity.onCreate().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onCreate(long id)
+    {
+    InternalStackFrameList.onCreate(id);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can resume its operations.
+ * Must be called from Activity.onResume().
+ */
+  public static void onResume()
+    {
+    onResume(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can resume its operations.
+ * Must be called from Activity.onResume().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onResume(long id)
+    {
+    InternalStackFrameList.onResume(id);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can release the OpenGL related data that needs to be recreated.
+ * Must be called from Activity.onPause().
+ */
+  public static void onPause()
+    {
+    onPause(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can release the OpenGL related data that needs to be recreated.
+ * Must be called from Activity.onPause().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onPause(long id)
+    {
+    InternalStackFrameList.onPause(id);
+
+    Dynamic.onPause();  // common for all frames
+    InternalOutputSurface.onPause();
+    Effect.onPause();
+    DeferredJobs.onPause();
+
+    mOITCompilationAttempted = false;
+    mNeedsTransformFeedback  = false;
+
+    mLinkedListSSBO[0]= -1;
+    mAtomicCounter = null;
+
+    mNormalProgram     = null;
+    mMainOITProgram    = null;
+    mMainProgram       = null;
+    mFullProgram       = null;
+    mOITClearProgram   = null;
+    mOITBuildProgram   = null;
+    mOITCollapseProgram= null;
+    mOITRenderProgram  = null;
+    mBlitDepthProgram  = null;
+    mBlitProgram       = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can release its internal data structures.
+ * Must be called from Activity.onDestroy().
+ */
+  public static void onDestroy()
+    {
+    onDestroy(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this so that the Library can release its internal data structures.
+ * Must be called from Activity.onDestroy().
+ *
+ * @param id id of an Activity that is using the library; anything unique so that the Library can
+ *           tell between Activities in case you're going to be using it from more than one.
+ */
+  public static void onDestroy(long id)
+    {
+    if( InternalStackFrameList.isInitialized() )
+      {
+      InternalStackFrameList.onDestroy(id);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Some devices - Qualcomm's Adreno 3xx with drivers v. 84,95,100,104,140, and possibly more -
+ * suffer from a very slow compilation of GLSL program if said program includes Transform Feedback.
+ * Return true if the platform we are running on does not suffer from this problem.
+ */
+  public static boolean fastCompilationTF()
+    {
+    return mFastCompilationTF;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the maximum size of the texture supported by the driver.
+ */
+  public static int getMaxTextureSize()
+    {
+    return mMaxTextureSize;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Call this before calling onSurfaceCreated() if you want to access normal vectors in CPU.
+ */
+  public static void needTransformFeedback()
+    {
+    mNeedsTransformFeedback = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the maximum number of effects of a given type that can be simultaneously applied to a
+ * single (InputSurface,MeshBase) combo.
+ *
+ * @param type {@link EffectType}
+ * @return The maximum number of effects of a given type.
+ */
+  @SuppressWarnings("unused")
+  public static int getMax(EffectType type)
+    {
+    return EffectQueue.getMax(type.ordinal());
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the maximum number of 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 Vertex Shader gets compiled, i.e. before the call to {@link DistortedLibrary#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 type {@link EffectType}
+ * @param max new maximum number of simultaneous effects. Has to be a non-negative number not greater
+ *            than Byte.MAX_VALUE
+ * @return <code>true</code> if operation was successful, <code>false</code> otherwise.
+ */
+  @SuppressWarnings("unused")
+  public static boolean setMax(EffectType type, int max)
+    {
+    return EffectQueue.setMax(type.ordinal(),max);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return an interger - max number of uniforms one can upload to a Vertex Shader.
+ */
+  public static int getMaxVertexUniforms()
+    {
+    return mMaxNumberOfVerUniforms;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return an interger - max number of uniforms one can upload to a Fragment Shader.
+ */
+  public static int getMaxFragmentUniforms()
+    {
+    return mMaxNumberOfFraUniforms;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return a String defining the vendor of the graphics driver.
+ */
+  public static String getDriverVendor()
+    {
+    return mVendor;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return a String defining the version of the graphics driver.
+ */
+  public static String getDriverVersion()
+    {
+    return mVersion;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return a String defining the renderer of the graphics driver.
+ */
+  public static String getDriverRenderer()
+    {
+    return mRenderer;
+    }
+  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/main/DistortedNode.java b/src/main/java/org/distorted/library/main/DistortedNode.java
deleted file mode 100644
index fe9957f..0000000
--- a/src/main/java/org/distorted/library/main/DistortedNode.java
+++ /dev/null
@@ -1,799 +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.main;
-
-import android.opengl.GLES30;
-
-import org.distorted.library.mesh.MeshBase;
-
-import java.util.ArrayList;
-import java.util.Collections;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Class which represents a Node in a Tree of (InputSurface,Mesh,Effects) triplets.
- * <p>
- * Having organized such sets into a Tree, we can then render any Node to any OutputSurface.
- * That recursively renders the set held in the Node and all its children.
- * <p>
- * The class takes special care to only render identical sub-trees once. Each Node holds a reference
- * to sub-class 'NodeData'. Two identical sub-trees attached at different points of the main tree
- * will point to the same NodeData; only the first of this is rendered (mData.numRender!).
- */
-public class DistortedNode implements InternalChildrenList.Parent
-  {
-  private static final int DEFAULT_FBO_SIZE = 100;
-
-  private final DistortedEffects mEffects;
-  private final InternalRenderState mState;
-  private final InternalChildrenList mChildren;
-  private InternalChildrenList.Parent mParent;
-  private InternalSurface mSurface;
-  private InternalNodeData mData;
-  private MeshBase mMesh;
-
-  private int mFboW, mFboH, mFboDepthStencil;
-  private boolean mRenderWayOIT;
-  private float mFOV, mNear;
-  private long mLastTime;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void markForDeletion()
-    {
-    if( mData.removeData() )
-      {
-      mData.mFBO.markForDeletion();
-      mData.mFBO = null;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// [3] --> the postprocessing queue. See EffectType.
-
-  long getBucket()
-    {
-    return mEffects.getQueues()[3].getID();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private ArrayList<Long> generateIDList()
-    {
-    ArrayList<Long> ret = new ArrayList<>();
-    int numChildren = mChildren.getNumChildren();
-
-    if( numChildren==0 )
-      {
-      // add a negative number so this leaf never gets confused with a internal node
-      // with a single child that happens to have ID identical to some leaf's Effects ID.
-      ret.add(-mEffects.getID());
-      }
-    else
-      {
-      DistortedNode node;
-   
-      for(int i=0; i<numChildren; i++)
-        {
-        node = mChildren.getChild(i);
-        ret.add(node.mData.ID);
-        }
-
-      // A bit questionable decision here - we are sorting the children IDs, which means
-      // that order in which we draw the children is going to be undefined (well, this is not
-      // strictly speaking true - when rendering, if no postprocessing and isomorphism are
-      // involved, we *DO* render the children in order they were added; if however there
-      // are two internal nodes with the same list of identical children, just added in a
-      // different order each time, then we consider them isomorphic, i.e. identical and only
-      // render the first one. If then two children of such 'pseudo-isomorphic' nodes are at
-      // exactly the same Z-height this might result in some unexpected sights).
-      //
-      // Reason: with the children being sorted by postprocessing buckets, the order is
-      // undefined anyway (although only when postprocessing is applied).
-      //
-      // See the consequences in the 'Olympic' app - remove a few leaves and add them back in
-      // different order. You will see the number of renders go back to the original 15.
-      Collections.sort(ret);
-      }
-
-    ret.add( 0, mSurface.getID() );
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This is not really part of the public API. Has to be public only because it is a part of the
- * InternalChildrenList.Parent interface.
- *
- * @y.exclude
- */
-  public void adjustIsomorphism()
-    {
-    InternalNodeData newData = InternalNodeData.returnData(generateIDList());
-    boolean deleteOldFBO = mData.removeData();
-    boolean createNewFBO = (mChildren.getNumChildren()>0 && newData.mFBO==null);
-
-    if( deleteOldFBO && createNewFBO )
-      {
-      newData.mFBO = mData.mFBO;
-      }
-    else if( deleteOldFBO )
-      {
-      mData.mFBO.markForDeletion();
-      mData.mFBO = null;
-      }
-    else if( createNewFBO )
-      {
-      newData.mFBO = allocateNewFBO();
-      }
-
-    mData = newData;
-
-    if( mParent!=null ) mParent.adjustIsomorphism();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return the total number of render calls issued
-
-  int drawNoBlend(long currTime, InternalOutputSurface surface)
-    {
-    InternalSurface input = getSurface();
-
-    if( input.setAsInput() )
-      {
-      mState.apply();
-      GLES30.glDisable(GLES30.GL_BLEND);
-      if( mLastTime==0 ) mLastTime=currTime;
-      DistortedLibrary.drawPriv(mEffects, mMesh, surface, currTime, (currTime-mLastTime));
-      mLastTime = currTime;
-      GLES30.glEnable(GLES30.GL_BLEND);
-      return 1;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Use the Order Independent Transparency method to draw a non-postprocessed child.
-
-  int drawOIT(long currTime, InternalOutputSurface surface)
-    {
-    InternalSurface input = getSurface();
-
-    if( input.setAsInput() )
-      {
-      mState.apply();
-      if( mLastTime==0 ) mLastTime=currTime;
-      DistortedLibrary.drawPrivOIT(mEffects, mMesh, surface, currTime, (currTime-mLastTime));
-      mLastTime = currTime;
-      return 1;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return the total number of render calls issued
-
-  int draw(long currTime, InternalOutputSurface surface)
-    {
-    InternalSurface input = getSurface();
-
-    if( input.setAsInput() )
-      {
-      mState.apply();
-      if( mLastTime==0 ) mLastTime=currTime;
-      DistortedLibrary.drawPriv(mEffects, mMesh, surface, currTime, (currTime-mLastTime));
-      mLastTime = currTime;
-      return 1;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return the total number of render calls issued
-
-  int renderRecursive(long currTime)
-    {
-    int numRenders = 0;
-    int numChildren = mChildren.getNumChildren();
-
-    if( numChildren>0 && mData.notRenderedYetAtThisTime(currTime) )
-      {
-      DistortedNode node;
-      long oldBucket=0, newBucket;
-
-      for (int i=0; i<numChildren; i++)
-        {
-        node = mChildren.getChild(i);
-        newBucket = node.getBucket();
-        numRenders += node.renderRecursive(currTime);
-        if( newBucket<oldBucket ) mChildren.rearrangeByBuckets(i,newBucket);
-        else oldBucket=newBucket;
-        }
-
-      if( mData.mFBO==null ) mData.mFBO = allocateNewFBO();
-      mData.mFBO.setAsOutput(currTime);
-
-      if( mSurface.setAsInput() )
-        {
-        numRenders++;
-        DistortedLibrary.blitPriv(mData.mFBO);
-        }
-
-      numRenders += mData.mFBO.renderChildren(currTime,numChildren,mChildren,0, mRenderWayOIT);
-      }
-
-    return numRenders;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private DistortedFramebuffer allocateNewFBO()
-    {
-    int width, height;
-
-    if( mFboW>0 && mFboH>0 )
-      {
-      width = mFboW;
-      height= mFboH;
-      }
-    else
-      {
-      if( mSurface instanceof DistortedFramebuffer )
-        {
-        DistortedFramebuffer fbo = (DistortedFramebuffer)mSurface;
-        width = fbo.getWidth();
-        height= fbo.getHeight();
-        }
-      else
-        {
-        width = DEFAULT_FBO_SIZE;
-        height= DEFAULT_FBO_SIZE;
-        }
-      }
-
-    DistortedFramebuffer fbo = new DistortedFramebuffer(1,mFboDepthStencil, InternalSurface.TYPE_TREE, width, height);
-
-    if( mFOV!=InternalOutputSurface.DEFAULT_FOV || mNear!=InternalOutputSurface.DEFAULT_NEAR )
-      {
-      fbo.setProjection(mFOV,mNear);
-      }
-
-    return fbo;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void resetLastTime()
-    {
-    mLastTime = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setParent(InternalChildrenList.Parent parent)
-    {
-    mParent = parent;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Constructs new Node.
- *     
- * @param surface InputSurface to put into the new Node.
- * @param effects DistortedEffects to put into the new Node.
- * @param mesh MeshBase to put into the new Node.
- */
-  public DistortedNode(InternalSurface surface, DistortedEffects effects, MeshBase mesh)
-    {
-    mLastTime      = 0;
-    mSurface       = surface;
-    mEffects       = effects;
-    mMesh          = mesh;
-    mState         = new InternalRenderState();
-    mChildren      = new InternalChildrenList(this);
-    mParent        = null;
-    mRenderWayOIT  = false;
-
-    mFOV = InternalOutputSurface.DEFAULT_FOV;
-    mNear= InternalOutputSurface.DEFAULT_NEAR;
-
-    mFboW            = 0;  // i.e. take this from
-    mFboH            = 0;  // mEffects's stretch{X,Y}
-    mFboDepthStencil = DistortedFramebuffer.DEPTH_NO_STENCIL;
-
-    mData = InternalNodeData.returnData(generateIDList());
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////  
-/**
- * Copy-constructs new Node from another Node.
- *     
- * @param node The DistortedNode to copy data from.
- * @param flags bit field composed of a subset of the following:
- *        {@link DistortedLibrary#CLONE_SURFACE},  {@link DistortedLibrary#CLONE_MATRIX}, {@link DistortedLibrary#CLONE_VERTEX},
- *        {@link DistortedLibrary#CLONE_FRAGMENT} and {@link DistortedLibrary#CLONE_CHILDREN}.
- *        For example flags = CLONE_SURFACE | CLONE_CHILDREN.
- */
-  public DistortedNode(DistortedNode node, int flags)
-    {
-    mLastTime     = 0;
-    mEffects      = new DistortedEffects(node.mEffects,flags);
-    mMesh         = node.mMesh;
-    mState        = new InternalRenderState();
-    mParent       = null;
-    mRenderWayOIT = false;
-
-    mFOV = InternalOutputSurface.DEFAULT_FOV;
-    mNear= InternalOutputSurface.DEFAULT_NEAR;
-
-    mFboW            = node.mFboW;
-    mFboH            = node.mFboH;
-    mFboDepthStencil = node.mFboDepthStencil;
-
-    if( (flags & DistortedLibrary.CLONE_SURFACE) != 0 )
-      {
-      mSurface = node.mSurface;
-      }
-    else
-      {
-      if( node.mSurface instanceof DistortedTexture )
-        {
-        mSurface = new DistortedTexture(InternalSurface.TYPE_TREE);
-        }
-      else if( node.mSurface instanceof DistortedFramebuffer )
-        {
-        DistortedFramebuffer fbo = (DistortedFramebuffer)node.mSurface;
-
-        int w = fbo.getWidth();
-        int h = fbo.getHeight();
-        int depthStencil = DistortedFramebuffer.NO_DEPTH_NO_STENCIL;
-
-        if( fbo.hasDepth() )
-          {
-          boolean hasStencil = fbo.hasStencil();
-          depthStencil = (hasStencil ? DistortedFramebuffer.BOTH_DEPTH_STENCIL:DistortedFramebuffer.DEPTH_NO_STENCIL);
-          }
-
-        mSurface = new DistortedFramebuffer(1,depthStencil, InternalSurface.TYPE_TREE,w,h);
-        }
-      }
-
-    if( (flags & DistortedLibrary.CLONE_CHILDREN) != 0 )
-      {
-      mChildren = node.mChildren;
-      }
-    else
-      {
-      mChildren = new InternalChildrenList(this);
-      }
-
-    mData = InternalNodeData.returnData(generateIDList());
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * Change the input surface while keeping everything else about the Node the same.
-   *
-   * @param surface The new input surface.
-   */
-  public void changeInputSurface(InternalSurface surface)
-    {
-    mSurface = surface;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  /**
-   * When rendering this Node, should we use the Order Independent Transparency render more?
-   * <p>
-   * There are two modes of rendering: the fast 'normal' way, which however renders transparent
-   * fragments in different ways depending on which fragments get rendered first, or the slower
-   * 'oit' way, which renders transparent fragments correctly regardless of their order.
-   *
-   * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
-   */
-  public void setOrderIndependentTransparency(boolean oit)
-    {
-    mRenderWayOIT = oit;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * When rendering this Node, should we use the Order Independent Transparency render more?
-   * <p>
-   * There are two modes of rendering: the fast 'normal' way, which however renders transparent
-   * fragments in different ways depending on which fragments get rendered first, or the slower
-   * 'oit' way, which renders transparent fragments correctly regardless of their order.
-   *
-   * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
-   * @param initialSize Initial number of transparent fragments we expect, in screenfulls.
-   *                    I.e '1.0' means 'the scene we are going to render contains dialog_about 1 screen
-   *                    worth of transparent fragments'. Valid values: 0.0 &lt; initialSize &lt; 10.0
-   *                    Even if you get this wrong, the library will detect that there are more
-   *                    transparent fragments than it has space for and readjust its internal buffers,
-   *                    but only after a few frames during which one will probably see missing objects.
-   */
-  public void setOrderIndependentTransparency(boolean oit, float initialSize)
-    {
-    mRenderWayOIT = oit;
-
-    if( initialSize>0.0f && initialSize<10.0f )
-      DistortedLibrary.setSSBOSize(initialSize);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Adds a new child to the last position in the list of our Node's children.
- * <p>
- * We cannot do this mid-render - actual attachment will be done just before the next render, by the
- * InternalMaster (by calling doWork())
- *
- * @param node The new Node to add.
- */
-  public void attach(DistortedNode node)
-    {
-    mChildren.attach(node);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Adds a new child to the last position in the list of our Node's children.
- * <p>
- * We cannot do this mid-render - actual attachment will be done just before the next render, by the
- * InternalMaster (by calling doWork())
- *
- * @param surface InputSurface to initialize our child Node with.
- * @param effects DistortedEffects to initialize our child Node with.
- * @param mesh MeshBase to initialize our child Node with.
- * @return the newly constructed child Node, or null if we couldn't allocate resources.
- */
-  public DistortedNode attach(InternalSurface surface, DistortedEffects effects, MeshBase mesh)
-    {
-    return mChildren.attach(surface,effects,mesh);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Removes the first occurrence of a specified child from the list of children of our Node.
- * <p>
- * We cannot do this mid-render - actual detachment will be done just before the next render, by the
- * InternalMaster (by calling doWork())
- *
- * @param node The Node to remove.
- */
-  public void detach(DistortedNode node)
-    {
-    mChildren.detach(node);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Removes the first occurrence of a specified child from the list of children of our Node.
- * <p>
- * A bit questionable method as there can be many different Nodes attached as children, some
- * of them having the same Effects but - for instance - different Mesh. Use with care.
- * <p>
- * We cannot do this mid-render - actual detachment will be done just before the next render, by the
- * InternalMaster (by calling doWork())
- *
- * @param effects DistortedEffects to remove.
- */
-  public void detach(DistortedEffects effects)
-    {
-    mChildren.detach(effects);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Removes all children Nodes.
- * <p>
- * We cannot do this mid-render - actual detachment will be done just before the next render, by the
- * InternalMaster (by calling doWork())
- */
-  public void detachAll()
-    {
-    mChildren.detachAll();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the DistortedEffects object that's in the Node.
- * 
- * @return The DistortedEffects contained in the Node.
- */
-  public DistortedEffects getEffects()
-    {
-    return mEffects;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * Returns the surface this object gets rendered to.
-   *
-   * @return The InternalSurface contained in the Node (if a leaf), or the FBO (if an internal Node)
-   */
-  public InternalSurface getSurface()
-    {
-    return mChildren.getNumChildren()==0 ? mSurface : mData.mFBO;
-    }
-
-//////////////////////////////////////////////////////////////////////////////////////////////////
-  /**
-   * Returns the FBO contained in this object, even if it is a leaf.
-   *
-   * @return The DistortedFramebuffer.
-   */
-  public DistortedFramebuffer getFramebuffer()
-    {
-    return mData.mFBO;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Returns the Mesh object that's in the Node.
- *
- * @return Mesh contained in the Node.
- */
-  public MeshBase getMesh()
-    {
-    return mMesh;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set a new Mesh.
- */
-  public void setMesh(MeshBase mesh)
-    {
-    mMesh = mesh;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Resizes the DistortedFramebuffer object that we render this Node to.
- */
-  public void resizeFBO(int width, int height)
-    {
-    mFboW = width;
-    mFboH = height;
-
-    if ( mData.mFBO !=null )
-      {
-      // TODO: potentially allocate a new NodeData if we have to
-      mData.mFBO.resize(width,height);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set Projection Matrix for the Framebuffer contained in this Node.
- * <p>
- * If this Node is a Leaf and has no Framebuffer in it, this call does nothing.
- *
- * @param fov Vertical 'field of view' of the Projection frustrum (in degrees).
- *            Valid values: 0<=fov<180. FOV==0 means 'parallel projection'.
- * @param near The Near plane.
- */
-  public void setProjection(float fov, float near)
-    {
-    if( fov < 180.0f && fov >=0.0f )
-      {
-      mFOV = fov;
-      }
-
-    if( near<   1.0f && near> 0.0f )
-      {
-      mNear= near;
-      }
-    else if( near<=0.0f )
-      {
-      mNear = 0.01f;
-      }
-    else if( near>=1.0f )
-      {
-      mNear=0.99f;
-      }
-
-    if( mData.mFBO!=null )
-      {
-      mData.mFBO.setProjection(mFOV,mNear);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Enables/disables DEPTH and STENCIL buffers in the Framebuffer object that we render this Node to.
- */
-  public void enableDepthStencil(int depthStencil)
-    {
-    mFboDepthStencil = depthStencil;
-
-    if ( mData.mFBO !=null )
-      {
-      // TODO: potentially allocate a new NodeData if we have to
-      mData.mFBO.enableDepthStencil(depthStencil);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// APIs that control how to set the OpenGL state just before rendering this Node.
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, use ColorMask (r,g,b,a).
- *
- * @param r Write to the RED color channel when rendering this Node?
- * @param g Write to the GREEN color channel when rendering this Node?
- * @param b Write to the BLUE color channel when rendering this Node?
- * @param a Write to the ALPHA channel when rendering this Node?
- */
-  @SuppressWarnings("unused")
-  public void glColorMask(boolean r, boolean g, boolean b, boolean a)
-    {
-    mState.glColorMask(r,g,b,a);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, switch on writing to Depth buffer?
- *
- * @param mask Write to the Depth buffer when rendering this Node?
- */
-  @SuppressWarnings("unused")
-  public void glDepthMask(boolean mask)
-    {
-    mState.glDepthMask(mask);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, which bits of the Stencil buffer to write to?
- *
- * @param mask Marks the bits of the Stencil buffer we will write to when rendering this Node.
- */
-  @SuppressWarnings("unused")
-  public void glStencilMask(int mask)
-    {
-    mState.glStencilMask(mask);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, which Tests to enable?
- *
- * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
- */
-  @SuppressWarnings("unused")
-  public void glEnable(int test)
-    {
-    mState.glEnable(test);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, which Tests to enable?
- *
- * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
- */
-  @SuppressWarnings("unused")
-  public void glDisable(int test)
-    {
-    mState.glDisable(test);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, use the following StencilFunc.
- *
- * @param func Valid values: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL
- * @param ref  Reference valut to compare our stencil with.
- * @param mask Mask used when comparing.
- */
-  @SuppressWarnings("unused")
-  public void glStencilFunc(int func, int ref, int mask)
-    {
-    mState.glStencilFunc(func,ref,mask);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, use the following StencilOp.
- * <p>
- * Valid values of all 3 parameters: GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP
- *
- * @param sfail  What to do when Stencil Test fails.
- * @param dpfail What to do when Depth Test fails.
- * @param dppass What to do when Depth Test passes.
- */
-  @SuppressWarnings("unused")
-  public void glStencilOp(int sfail, int dpfail, int dppass)
-    {
-    mState.glStencilOp(sfail,dpfail,dppass);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, use the following DepthFunc.
- *
- * @param func Valid values: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL
- */
-  @SuppressWarnings("unused")
-  public void glDepthFunc(int func)
-    {
-    mState.glDepthFunc(func);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, use the following Blending mode.
- * <p>
- * Valid values: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
- *               GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR,
- *               GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_SRC_ALPHA_SATURATE
- *
- * @param src Source Blend function
- * @param dst Destination Blend function
- */
-  @SuppressWarnings("unused")
-  public void glBlendFunc(int src, int dst)
-    {
-    mState.glBlendFunc(src,dst);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Before rendering this Node, clear the following buffers.
- * <p>
- * Valid values: 0, or bitwise OR of one or more values from the set GL_COLOR_BUFFER_BIT,
- *               GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT.
- * Default: 0
- *
- * @param mask bitwise OR of BUFFER_BITs to clear.
- */
-  @SuppressWarnings("unused")
-  public void glClear(int mask)
-    {
-    mState.glClear(mask);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Recursively print all the effect queues attached to the children Nodes and to this Node.
- */
-  public void debug(int depth)
-    {
-    String dbg = mEffects.debug(depth);
-    DistortedLibrary.logMessage(dbg);
-
-    int numChildren = mChildren.getNumChildren();
-
-    for(int i=0; i<numChildren; i++)
-      {
-      DistortedNode node = mChildren.getChild(i);
-      node.debug(depth+1);
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/DistortedNode.kt b/src/main/java/org/distorted/library/main/DistortedNode.kt
new file mode 100644
index 0000000..fe9957f
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/DistortedNode.kt
@@ -0,0 +1,799 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main;
+
+import android.opengl.GLES30;
+
+import org.distorted.library.mesh.MeshBase;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class which represents a Node in a Tree of (InputSurface,Mesh,Effects) triplets.
+ * <p>
+ * Having organized such sets into a Tree, we can then render any Node to any OutputSurface.
+ * That recursively renders the set held in the Node and all its children.
+ * <p>
+ * The class takes special care to only render identical sub-trees once. Each Node holds a reference
+ * to sub-class 'NodeData'. Two identical sub-trees attached at different points of the main tree
+ * will point to the same NodeData; only the first of this is rendered (mData.numRender!).
+ */
+public class DistortedNode implements InternalChildrenList.Parent
+  {
+  private static final int DEFAULT_FBO_SIZE = 100;
+
+  private final DistortedEffects mEffects;
+  private final InternalRenderState mState;
+  private final InternalChildrenList mChildren;
+  private InternalChildrenList.Parent mParent;
+  private InternalSurface mSurface;
+  private InternalNodeData mData;
+  private MeshBase mMesh;
+
+  private int mFboW, mFboH, mFboDepthStencil;
+  private boolean mRenderWayOIT;
+  private float mFOV, mNear;
+  private long mLastTime;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void markForDeletion()
+    {
+    if( mData.removeData() )
+      {
+      mData.mFBO.markForDeletion();
+      mData.mFBO = null;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// [3] --> the postprocessing queue. See EffectType.
+
+  long getBucket()
+    {
+    return mEffects.getQueues()[3].getID();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private ArrayList<Long> generateIDList()
+    {
+    ArrayList<Long> ret = new ArrayList<>();
+    int numChildren = mChildren.getNumChildren();
+
+    if( numChildren==0 )
+      {
+      // add a negative number so this leaf never gets confused with a internal node
+      // with a single child that happens to have ID identical to some leaf's Effects ID.
+      ret.add(-mEffects.getID());
+      }
+    else
+      {
+      DistortedNode node;
+   
+      for(int i=0; i<numChildren; i++)
+        {
+        node = mChildren.getChild(i);
+        ret.add(node.mData.ID);
+        }
+
+      // A bit questionable decision here - we are sorting the children IDs, which means
+      // that order in which we draw the children is going to be undefined (well, this is not
+      // strictly speaking true - when rendering, if no postprocessing and isomorphism are
+      // involved, we *DO* render the children in order they were added; if however there
+      // are two internal nodes with the same list of identical children, just added in a
+      // different order each time, then we consider them isomorphic, i.e. identical and only
+      // render the first one. If then two children of such 'pseudo-isomorphic' nodes are at
+      // exactly the same Z-height this might result in some unexpected sights).
+      //
+      // Reason: with the children being sorted by postprocessing buckets, the order is
+      // undefined anyway (although only when postprocessing is applied).
+      //
+      // See the consequences in the 'Olympic' app - remove a few leaves and add them back in
+      // different order. You will see the number of renders go back to the original 15.
+      Collections.sort(ret);
+      }
+
+    ret.add( 0, mSurface.getID() );
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is not really part of the public API. Has to be public only because it is a part of the
+ * InternalChildrenList.Parent interface.
+ *
+ * @y.exclude
+ */
+  public void adjustIsomorphism()
+    {
+    InternalNodeData newData = InternalNodeData.returnData(generateIDList());
+    boolean deleteOldFBO = mData.removeData();
+    boolean createNewFBO = (mChildren.getNumChildren()>0 && newData.mFBO==null);
+
+    if( deleteOldFBO && createNewFBO )
+      {
+      newData.mFBO = mData.mFBO;
+      }
+    else if( deleteOldFBO )
+      {
+      mData.mFBO.markForDeletion();
+      mData.mFBO = null;
+      }
+    else if( createNewFBO )
+      {
+      newData.mFBO = allocateNewFBO();
+      }
+
+    mData = newData;
+
+    if( mParent!=null ) mParent.adjustIsomorphism();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return the total number of render calls issued
+
+  int drawNoBlend(long currTime, InternalOutputSurface surface)
+    {
+    InternalSurface input = getSurface();
+
+    if( input.setAsInput() )
+      {
+      mState.apply();
+      GLES30.glDisable(GLES30.GL_BLEND);
+      if( mLastTime==0 ) mLastTime=currTime;
+      DistortedLibrary.drawPriv(mEffects, mMesh, surface, currTime, (currTime-mLastTime));
+      mLastTime = currTime;
+      GLES30.glEnable(GLES30.GL_BLEND);
+      return 1;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Use the Order Independent Transparency method to draw a non-postprocessed child.
+
+  int drawOIT(long currTime, InternalOutputSurface surface)
+    {
+    InternalSurface input = getSurface();
+
+    if( input.setAsInput() )
+      {
+      mState.apply();
+      if( mLastTime==0 ) mLastTime=currTime;
+      DistortedLibrary.drawPrivOIT(mEffects, mMesh, surface, currTime, (currTime-mLastTime));
+      mLastTime = currTime;
+      return 1;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return the total number of render calls issued
+
+  int draw(long currTime, InternalOutputSurface surface)
+    {
+    InternalSurface input = getSurface();
+
+    if( input.setAsInput() )
+      {
+      mState.apply();
+      if( mLastTime==0 ) mLastTime=currTime;
+      DistortedLibrary.drawPriv(mEffects, mMesh, surface, currTime, (currTime-mLastTime));
+      mLastTime = currTime;
+      return 1;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return the total number of render calls issued
+
+  int renderRecursive(long currTime)
+    {
+    int numRenders = 0;
+    int numChildren = mChildren.getNumChildren();
+
+    if( numChildren>0 && mData.notRenderedYetAtThisTime(currTime) )
+      {
+      DistortedNode node;
+      long oldBucket=0, newBucket;
+
+      for (int i=0; i<numChildren; i++)
+        {
+        node = mChildren.getChild(i);
+        newBucket = node.getBucket();
+        numRenders += node.renderRecursive(currTime);
+        if( newBucket<oldBucket ) mChildren.rearrangeByBuckets(i,newBucket);
+        else oldBucket=newBucket;
+        }
+
+      if( mData.mFBO==null ) mData.mFBO = allocateNewFBO();
+      mData.mFBO.setAsOutput(currTime);
+
+      if( mSurface.setAsInput() )
+        {
+        numRenders++;
+        DistortedLibrary.blitPriv(mData.mFBO);
+        }
+
+      numRenders += mData.mFBO.renderChildren(currTime,numChildren,mChildren,0, mRenderWayOIT);
+      }
+
+    return numRenders;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private DistortedFramebuffer allocateNewFBO()
+    {
+    int width, height;
+
+    if( mFboW>0 && mFboH>0 )
+      {
+      width = mFboW;
+      height= mFboH;
+      }
+    else
+      {
+      if( mSurface instanceof DistortedFramebuffer )
+        {
+        DistortedFramebuffer fbo = (DistortedFramebuffer)mSurface;
+        width = fbo.getWidth();
+        height= fbo.getHeight();
+        }
+      else
+        {
+        width = DEFAULT_FBO_SIZE;
+        height= DEFAULT_FBO_SIZE;
+        }
+      }
+
+    DistortedFramebuffer fbo = new DistortedFramebuffer(1,mFboDepthStencil, InternalSurface.TYPE_TREE, width, height);
+
+    if( mFOV!=InternalOutputSurface.DEFAULT_FOV || mNear!=InternalOutputSurface.DEFAULT_NEAR )
+      {
+      fbo.setProjection(mFOV,mNear);
+      }
+
+    return fbo;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void resetLastTime()
+    {
+    mLastTime = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setParent(InternalChildrenList.Parent parent)
+    {
+    mParent = parent;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Constructs new Node.
+ *     
+ * @param surface InputSurface to put into the new Node.
+ * @param effects DistortedEffects to put into the new Node.
+ * @param mesh MeshBase to put into the new Node.
+ */
+  public DistortedNode(InternalSurface surface, DistortedEffects effects, MeshBase mesh)
+    {
+    mLastTime      = 0;
+    mSurface       = surface;
+    mEffects       = effects;
+    mMesh          = mesh;
+    mState         = new InternalRenderState();
+    mChildren      = new InternalChildrenList(this);
+    mParent        = null;
+    mRenderWayOIT  = false;
+
+    mFOV = InternalOutputSurface.DEFAULT_FOV;
+    mNear= InternalOutputSurface.DEFAULT_NEAR;
+
+    mFboW            = 0;  // i.e. take this from
+    mFboH            = 0;  // mEffects's stretch{X,Y}
+    mFboDepthStencil = DistortedFramebuffer.DEPTH_NO_STENCIL;
+
+    mData = InternalNodeData.returnData(generateIDList());
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////  
+/**
+ * Copy-constructs new Node from another Node.
+ *     
+ * @param node The DistortedNode to copy data from.
+ * @param flags bit field composed of a subset of the following:
+ *        {@link DistortedLibrary#CLONE_SURFACE},  {@link DistortedLibrary#CLONE_MATRIX}, {@link DistortedLibrary#CLONE_VERTEX},
+ *        {@link DistortedLibrary#CLONE_FRAGMENT} and {@link DistortedLibrary#CLONE_CHILDREN}.
+ *        For example flags = CLONE_SURFACE | CLONE_CHILDREN.
+ */
+  public DistortedNode(DistortedNode node, int flags)
+    {
+    mLastTime     = 0;
+    mEffects      = new DistortedEffects(node.mEffects,flags);
+    mMesh         = node.mMesh;
+    mState        = new InternalRenderState();
+    mParent       = null;
+    mRenderWayOIT = false;
+
+    mFOV = InternalOutputSurface.DEFAULT_FOV;
+    mNear= InternalOutputSurface.DEFAULT_NEAR;
+
+    mFboW            = node.mFboW;
+    mFboH            = node.mFboH;
+    mFboDepthStencil = node.mFboDepthStencil;
+
+    if( (flags & DistortedLibrary.CLONE_SURFACE) != 0 )
+      {
+      mSurface = node.mSurface;
+      }
+    else
+      {
+      if( node.mSurface instanceof DistortedTexture )
+        {
+        mSurface = new DistortedTexture(InternalSurface.TYPE_TREE);
+        }
+      else if( node.mSurface instanceof DistortedFramebuffer )
+        {
+        DistortedFramebuffer fbo = (DistortedFramebuffer)node.mSurface;
+
+        int w = fbo.getWidth();
+        int h = fbo.getHeight();
+        int depthStencil = DistortedFramebuffer.NO_DEPTH_NO_STENCIL;
+
+        if( fbo.hasDepth() )
+          {
+          boolean hasStencil = fbo.hasStencil();
+          depthStencil = (hasStencil ? DistortedFramebuffer.BOTH_DEPTH_STENCIL:DistortedFramebuffer.DEPTH_NO_STENCIL);
+          }
+
+        mSurface = new DistortedFramebuffer(1,depthStencil, InternalSurface.TYPE_TREE,w,h);
+        }
+      }
+
+    if( (flags & DistortedLibrary.CLONE_CHILDREN) != 0 )
+      {
+      mChildren = node.mChildren;
+      }
+    else
+      {
+      mChildren = new InternalChildrenList(this);
+      }
+
+    mData = InternalNodeData.returnData(generateIDList());
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * Change the input surface while keeping everything else about the Node the same.
+   *
+   * @param surface The new input surface.
+   */
+  public void changeInputSurface(InternalSurface surface)
+    {
+    mSurface = surface;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  /**
+   * When rendering this Node, should we use the Order Independent Transparency render more?
+   * <p>
+   * There are two modes of rendering: the fast 'normal' way, which however renders transparent
+   * fragments in different ways depending on which fragments get rendered first, or the slower
+   * 'oit' way, which renders transparent fragments correctly regardless of their order.
+   *
+   * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
+   */
+  public void setOrderIndependentTransparency(boolean oit)
+    {
+    mRenderWayOIT = oit;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * When rendering this Node, should we use the Order Independent Transparency render more?
+   * <p>
+   * There are two modes of rendering: the fast 'normal' way, which however renders transparent
+   * fragments in different ways depending on which fragments get rendered first, or the slower
+   * 'oit' way, which renders transparent fragments correctly regardless of their order.
+   *
+   * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
+   * @param initialSize Initial number of transparent fragments we expect, in screenfulls.
+   *                    I.e '1.0' means 'the scene we are going to render contains dialog_about 1 screen
+   *                    worth of transparent fragments'. Valid values: 0.0 &lt; initialSize &lt; 10.0
+   *                    Even if you get this wrong, the library will detect that there are more
+   *                    transparent fragments than it has space for and readjust its internal buffers,
+   *                    but only after a few frames during which one will probably see missing objects.
+   */
+  public void setOrderIndependentTransparency(boolean oit, float initialSize)
+    {
+    mRenderWayOIT = oit;
+
+    if( initialSize>0.0f && initialSize<10.0f )
+      DistortedLibrary.setSSBOSize(initialSize);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new child to the last position in the list of our Node's children.
+ * <p>
+ * We cannot do this mid-render - actual attachment will be done just before the next render, by the
+ * InternalMaster (by calling doWork())
+ *
+ * @param node The new Node to add.
+ */
+  public void attach(DistortedNode node)
+    {
+    mChildren.attach(node);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Adds a new child to the last position in the list of our Node's children.
+ * <p>
+ * We cannot do this mid-render - actual attachment will be done just before the next render, by the
+ * InternalMaster (by calling doWork())
+ *
+ * @param surface InputSurface to initialize our child Node with.
+ * @param effects DistortedEffects to initialize our child Node with.
+ * @param mesh MeshBase to initialize our child Node with.
+ * @return the newly constructed child Node, or null if we couldn't allocate resources.
+ */
+  public DistortedNode attach(InternalSurface surface, DistortedEffects effects, MeshBase mesh)
+    {
+    return mChildren.attach(surface,effects,mesh);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the first occurrence of a specified child from the list of children of our Node.
+ * <p>
+ * We cannot do this mid-render - actual detachment will be done just before the next render, by the
+ * InternalMaster (by calling doWork())
+ *
+ * @param node The Node to remove.
+ */
+  public void detach(DistortedNode node)
+    {
+    mChildren.detach(node);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes the first occurrence of a specified child from the list of children of our Node.
+ * <p>
+ * A bit questionable method as there can be many different Nodes attached as children, some
+ * of them having the same Effects but - for instance - different Mesh. Use with care.
+ * <p>
+ * We cannot do this mid-render - actual detachment will be done just before the next render, by the
+ * InternalMaster (by calling doWork())
+ *
+ * @param effects DistortedEffects to remove.
+ */
+  public void detach(DistortedEffects effects)
+    {
+    mChildren.detach(effects);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all children Nodes.
+ * <p>
+ * We cannot do this mid-render - actual detachment will be done just before the next render, by the
+ * InternalMaster (by calling doWork())
+ */
+  public void detachAll()
+    {
+    mChildren.detachAll();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the DistortedEffects object that's in the Node.
+ * 
+ * @return The DistortedEffects contained in the Node.
+ */
+  public DistortedEffects getEffects()
+    {
+    return mEffects;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * Returns the surface this object gets rendered to.
+   *
+   * @return The InternalSurface contained in the Node (if a leaf), or the FBO (if an internal Node)
+   */
+  public InternalSurface getSurface()
+    {
+    return mChildren.getNumChildren()==0 ? mSurface : mData.mFBO;
+    }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * Returns the FBO contained in this object, even if it is a leaf.
+   *
+   * @return The DistortedFramebuffer.
+   */
+  public DistortedFramebuffer getFramebuffer()
+    {
+    return mData.mFBO;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Returns the Mesh object that's in the Node.
+ *
+ * @return Mesh contained in the Node.
+ */
+  public MeshBase getMesh()
+    {
+    return mMesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set a new Mesh.
+ */
+  public void setMesh(MeshBase mesh)
+    {
+    mMesh = mesh;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resizes the DistortedFramebuffer object that we render this Node to.
+ */
+  public void resizeFBO(int width, int height)
+    {
+    mFboW = width;
+    mFboH = height;
+
+    if ( mData.mFBO !=null )
+      {
+      // TODO: potentially allocate a new NodeData if we have to
+      mData.mFBO.resize(width,height);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set Projection Matrix for the Framebuffer contained in this Node.
+ * <p>
+ * If this Node is a Leaf and has no Framebuffer in it, this call does nothing.
+ *
+ * @param fov Vertical 'field of view' of the Projection frustrum (in degrees).
+ *            Valid values: 0<=fov<180. FOV==0 means 'parallel projection'.
+ * @param near The Near plane.
+ */
+  public void setProjection(float fov, float near)
+    {
+    if( fov < 180.0f && fov >=0.0f )
+      {
+      mFOV = fov;
+      }
+
+    if( near<   1.0f && near> 0.0f )
+      {
+      mNear= near;
+      }
+    else if( near<=0.0f )
+      {
+      mNear = 0.01f;
+      }
+    else if( near>=1.0f )
+      {
+      mNear=0.99f;
+      }
+
+    if( mData.mFBO!=null )
+      {
+      mData.mFBO.setProjection(mFOV,mNear);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Enables/disables DEPTH and STENCIL buffers in the Framebuffer object that we render this Node to.
+ */
+  public void enableDepthStencil(int depthStencil)
+    {
+    mFboDepthStencil = depthStencil;
+
+    if ( mData.mFBO !=null )
+      {
+      // TODO: potentially allocate a new NodeData if we have to
+      mData.mFBO.enableDepthStencil(depthStencil);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// APIs that control how to set the OpenGL state just before rendering this Node.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, use ColorMask (r,g,b,a).
+ *
+ * @param r Write to the RED color channel when rendering this Node?
+ * @param g Write to the GREEN color channel when rendering this Node?
+ * @param b Write to the BLUE color channel when rendering this Node?
+ * @param a Write to the ALPHA channel when rendering this Node?
+ */
+  @SuppressWarnings("unused")
+  public void glColorMask(boolean r, boolean g, boolean b, boolean a)
+    {
+    mState.glColorMask(r,g,b,a);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, switch on writing to Depth buffer?
+ *
+ * @param mask Write to the Depth buffer when rendering this Node?
+ */
+  @SuppressWarnings("unused")
+  public void glDepthMask(boolean mask)
+    {
+    mState.glDepthMask(mask);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, which bits of the Stencil buffer to write to?
+ *
+ * @param mask Marks the bits of the Stencil buffer we will write to when rendering this Node.
+ */
+  @SuppressWarnings("unused")
+  public void glStencilMask(int mask)
+    {
+    mState.glStencilMask(mask);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, which Tests to enable?
+ *
+ * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
+ */
+  @SuppressWarnings("unused")
+  public void glEnable(int test)
+    {
+    mState.glEnable(test);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, which Tests to enable?
+ *
+ * @param test Valid values: GL_DEPTH_TEST, GL_STENCIL_TEST, GL_BLEND
+ */
+  @SuppressWarnings("unused")
+  public void glDisable(int test)
+    {
+    mState.glDisable(test);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, use the following StencilFunc.
+ *
+ * @param func Valid values: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL
+ * @param ref  Reference valut to compare our stencil with.
+ * @param mask Mask used when comparing.
+ */
+  @SuppressWarnings("unused")
+  public void glStencilFunc(int func, int ref, int mask)
+    {
+    mState.glStencilFunc(func,ref,mask);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, use the following StencilOp.
+ * <p>
+ * Valid values of all 3 parameters: GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, GL_INVERT, GL_INCR_WRAP, GL_DECR_WRAP
+ *
+ * @param sfail  What to do when Stencil Test fails.
+ * @param dpfail What to do when Depth Test fails.
+ * @param dppass What to do when Depth Test passes.
+ */
+  @SuppressWarnings("unused")
+  public void glStencilOp(int sfail, int dpfail, int dppass)
+    {
+    mState.glStencilOp(sfail,dpfail,dppass);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, use the following DepthFunc.
+ *
+ * @param func Valid values: GL_NEVER, GL_ALWAYS, GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER, GL_NOTEQUAL
+ */
+  @SuppressWarnings("unused")
+  public void glDepthFunc(int func)
+    {
+    mState.glDepthFunc(func);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, use the following Blending mode.
+ * <p>
+ * Valid values: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
+ *               GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR,
+ *               GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_SRC_ALPHA_SATURATE
+ *
+ * @param src Source Blend function
+ * @param dst Destination Blend function
+ */
+  @SuppressWarnings("unused")
+  public void glBlendFunc(int src, int dst)
+    {
+    mState.glBlendFunc(src,dst);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Before rendering this Node, clear the following buffers.
+ * <p>
+ * Valid values: 0, or bitwise OR of one or more values from the set GL_COLOR_BUFFER_BIT,
+ *               GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT.
+ * Default: 0
+ *
+ * @param mask bitwise OR of BUFFER_BITs to clear.
+ */
+  @SuppressWarnings("unused")
+  public void glClear(int mask)
+    {
+    mState.glClear(mask);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Recursively print all the effect queues attached to the children Nodes and to this Node.
+ */
+  public void debug(int depth)
+    {
+    String dbg = mEffects.debug(depth);
+    DistortedLibrary.logMessage(dbg);
+
+    int numChildren = mChildren.getNumChildren();
+
+    for(int i=0; i<numChildren; i++)
+      {
+      DistortedNode node = mChildren.getChild(i);
+      node.debug(depth+1);
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/DistortedScreen.java b/src/main/java/org/distorted/library/main/DistortedScreen.java
deleted file mode 100644
index 3109243..0000000
--- a/src/main/java/org/distorted/library/main/DistortedScreen.java
+++ /dev/null
@@ -1,277 +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.main;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.opengl.GLES30;
-
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.effect.MatrixEffectScale;
-import org.distorted.library.mesh.MeshQuad;
-import org.distorted.library.type.Static3D;
-
-/**
- * Class which represents the Screen.
- * <p>
- * User is able to render to it just like to a DistortedFramebuffer.
- */
-public class DistortedScreen extends DistortedFramebuffer
-  {
-  ///// DEBUGGING ONLY /////////////////////////
-  private static final int NUM_FRAMES  = 80;
-  private static final float DEBUG_SCR_FRAC = 0.15f;
-  private static final float DEBUG_FRAC     = 0.5f;
-
-  private static final int DEBUG_MODE_NONE = 0;
-  private static final int DEBUG_MODE_FPS  = 1;
-  private static final int DEBUG_MODE_FRAME= 2;
-
-  private int mDebugMode;
-  private boolean mDebugAllocated;
-
-  private MeshQuad debugMesh;
-  private DistortedTexture debugTexture;
-  private DistortedEffects debugEffects;
-  private Canvas debugCanvas;
-  private Bitmap debugBitmap;
-  private Paint mPaint;
-  private String debugString;
-  private long lastTime=0;
-  private long[] durations;
-  private int currDuration;
-  private int frameNumber;
-  private static final Static3D mMoveVector = new Static3D(0,0,0);
-  private static final MatrixEffectMove mMoveEffect = new MatrixEffectMove(mMoveVector);
-  ///// END DEBUGGING //////////////////////////
-
-  private int mQueueSize;
-  private int mCurRenderedFBO;    // During the first FBO_QUEUE_SIZE frames, we blit the very first
-  private int mToBeBlittedFBO;    // FBO one we have rendered. Then, we keep blitting the one we
-  private boolean mFirstCircle;   // rendered FBO_QUEUE_SIZE ago.
-
-  private int mDebugWidth, mDebugHeight, mDebugGap, mDebugTextColor, mDebugBackColor;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create a new Screen. Initially 1x1 in size.
- * <p>
- * Has to be followed by a 'resizeFBO()' to set the size.
- */
-  public DistortedScreen()
-    {
-    super(DistortedLibrary.WAIT_FOR_FBO_QUEUE_SIZE,1,BOTH_DEPTH_STENCIL, TYPE_SYST, STORAGE_PRIVATE,1,1);
-    mDebugMode = DEBUG_MODE_NONE;
-    mCurRenderedFBO = 0;
-    mToBeBlittedFBO = 0;
-    mFirstCircle = true;
-    mDebugAllocated = false;
-    mQueueSize = -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Draws all the attached children to this OutputSurface.
- * <p>
- * Must be called from a thread holding OpenGL Context.
- *
- * @param time Current time, in milliseconds. This will be passed to all the Effects stored in the children Nodes.
- * @return Number of objects rendered.
- */
-  public int render(long time)
-    {
-    if( mDebugMode!=DEBUG_MODE_NONE )
-      {
-      int w = getWidth();
-      int h = getHeight();
-
-      if( lastTime==0 ) lastTime = time;
-
-      if( mDebugMode==DEBUG_MODE_FPS )
-        {
-        currDuration++;
-        if (currDuration >= NUM_FRAMES) currDuration = 0;
-        durations[NUM_FRAMES] += ((time - lastTime) - durations[currDuration]);
-        durations[currDuration] = time - lastTime;
-
-        debugString = "" + ((int)(10000.0f*NUM_FRAMES/durations[NUM_FRAMES]))/10.0f;
-        }
-      else if( mDebugMode==DEBUG_MODE_FRAME )
-        {
-        debugString = "" + frameNumber;
-        frameNumber++;
-        }
-
-      if( !mDebugAllocated )
-        {
-        mDebugAllocated = true;
-        debugString = "";
-
-        if( mDebugWidth<=0 || mDebugHeight<=0 )
-          {
-          mDebugWidth = (int)(w*DEBUG_SCR_FRAC);
-          mDebugHeight= (int)(w*DEBUG_SCR_FRAC*DEBUG_FRAC);
-
-          if( mDebugWidth<=0 || mDebugHeight<=0 )
-            {
-            int width = 100;
-            mDebugWidth = (int)(width*DEBUG_SCR_FRAC);
-            mDebugHeight= (int)(width*DEBUG_SCR_FRAC*DEBUG_FRAC);
-            }
-          }
-
-        debugBitmap = Bitmap.createBitmap( mDebugWidth, mDebugHeight, Bitmap.Config.ARGB_8888);
-        debugMesh = new MeshQuad();
-        debugTexture = new DistortedTexture();
-        debugTexture.setTexture(debugBitmap);
-        debugCanvas = new Canvas(debugBitmap);
-        debugEffects = new DistortedEffects();
-        debugEffects.apply( new MatrixEffectScale( new Static3D(mDebugWidth,mDebugHeight,1) ) );
-        debugEffects.apply(mMoveEffect);
-
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-        mPaint.setTextAlign(Paint.Align.CENTER);
-        mPaint.setTextSize(0.7f*mDebugHeight);
-        }
-
-      mPaint.setColor(mDebugBackColor);
-      debugCanvas.drawRect(0, 0, mDebugWidth, mDebugHeight, mPaint);
-      mPaint.setColor(mDebugTextColor);
-      debugCanvas.drawText(debugString, 0.5f*mDebugWidth, 0.75f*mDebugHeight, mPaint);
-      debugTexture.setTexture(debugBitmap);
-
-      mMoveVector.set( (-w+mDebugWidth)*0.5f +mDebugGap, (h-mDebugHeight)*0.5f -mDebugGap, 0);
-
-      lastTime = time;
-      }
-
-    int numrender = super.render(time,mCurRenderedFBO);
-
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
-
-    // workaround for the Mali issues: blit the framebuffer we have computed DistortedLibrary.FBO_QUEUE_SIZE
-    // frames ago. Looks like FBO_QUEUE_SIZE=2 solves the issue already but I decided to play it safe and
-    // make it equal to 3.
-    // This of course introduces a delay and uses more memory, but it does not appear to have any effect
-    // on speed. Maybe a slight positive effect if any!
-    setAsInput(mToBeBlittedFBO,0);
-
-    GLES30.glColorMask(true,true,true,true);
-    GLES30.glDepthMask(false);
-    GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-    GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-    GLES30.glDisable(GLES30.GL_BLEND);
-
-    DistortedLibrary.blitPriv(this);
-
-    if( mDebugMode!=DEBUG_MODE_NONE && debugTexture.setAsInput())
-      {
-      DistortedLibrary.drawPriv(debugEffects, debugMesh, this, time,0);
-      }
-
-    if( mQueueSize<=0 )
-      {
-      mQueueSize = DistortedLibrary.getQueueSize();
-      }
-
-    if( ++mCurRenderedFBO>=mQueueSize )
-      {
-      mCurRenderedFBO = 0;
-      if (mFirstCircle) mFirstCircle = false;
-      }
-    if( !mFirstCircle && ++mToBeBlittedFBO>=mQueueSize )
-      {
-      mToBeBlittedFBO=0;
-      }
-/*
-    int err = GLES30.glGetError();
-
-    if( err!=GLES30.GL_NO_ERROR )
-      {
-      DistortedLibrary.logMessage("DistortedScreen: OpenGL error "+err);
-      }
-*/
-    return numrender+1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Make the library show Frames Per Second in the upper-left corner.
- * <p>
- */
-  public void showFPS()
-    {
-    int w         = getWidth();
-    int width     = (int)(w*DEBUG_SCR_FRAC);
-    int height    = (int)(w*DEBUG_SCR_FRAC*DEBUG_FRAC);
-    int gap       = 5;
-    int textColor = 0xffffffff;
-    int backColor = 0xff000000;
-
-    showFPS(width,height,gap,textColor,backColor);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Make the library show Frames Per Second in the upper-left corner.
- * Set appropriate params.
- * <p>
- */
-  public void showFPS(int width, int height, int gap, int textColor, int backColor)
-    {
-    mDebugWidth     = width;
-    mDebugHeight    = height;
-    mDebugGap       = gap;
-    mDebugBackColor = backColor;
-    mDebugTextColor = textColor;
-
-    if( mDebugMode!=DEBUG_MODE_FPS )
-      {
-      mDebugMode = DEBUG_MODE_FPS;
-
-      durations = new long[NUM_FRAMES + 1];
-      currDuration = 0;
-
-      for (int i=0; i<NUM_FRAMES+1; i++) durations[i] = 16;  // Assume FPS will be
-      durations[NUM_FRAMES] = NUM_FRAMES * 16;               // close to 1000/16 ~ 60
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Make the library show current frame number in the upper-left corner.
- * <p>
- */
-  public void showFrameNumber()
-    {
-    if( mDebugMode!=DEBUG_MODE_FRAME )
-      {
-      mDebugMode = DEBUG_MODE_FRAME;
-      frameNumber = 0;
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/DistortedScreen.kt b/src/main/java/org/distorted/library/main/DistortedScreen.kt
new file mode 100644
index 0000000..3109243
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/DistortedScreen.kt
@@ -0,0 +1,277 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.opengl.GLES30;
+
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.mesh.MeshQuad;
+import org.distorted.library.type.Static3D;
+
+/**
+ * Class which represents the Screen.
+ * <p>
+ * User is able to render to it just like to a DistortedFramebuffer.
+ */
+public class DistortedScreen extends DistortedFramebuffer
+  {
+  ///// DEBUGGING ONLY /////////////////////////
+  private static final int NUM_FRAMES  = 80;
+  private static final float DEBUG_SCR_FRAC = 0.15f;
+  private static final float DEBUG_FRAC     = 0.5f;
+
+  private static final int DEBUG_MODE_NONE = 0;
+  private static final int DEBUG_MODE_FPS  = 1;
+  private static final int DEBUG_MODE_FRAME= 2;
+
+  private int mDebugMode;
+  private boolean mDebugAllocated;
+
+  private MeshQuad debugMesh;
+  private DistortedTexture debugTexture;
+  private DistortedEffects debugEffects;
+  private Canvas debugCanvas;
+  private Bitmap debugBitmap;
+  private Paint mPaint;
+  private String debugString;
+  private long lastTime=0;
+  private long[] durations;
+  private int currDuration;
+  private int frameNumber;
+  private static final Static3D mMoveVector = new Static3D(0,0,0);
+  private static final MatrixEffectMove mMoveEffect = new MatrixEffectMove(mMoveVector);
+  ///// END DEBUGGING //////////////////////////
+
+  private int mQueueSize;
+  private int mCurRenderedFBO;    // During the first FBO_QUEUE_SIZE frames, we blit the very first
+  private int mToBeBlittedFBO;    // FBO one we have rendered. Then, we keep blitting the one we
+  private boolean mFirstCircle;   // rendered FBO_QUEUE_SIZE ago.
+
+  private int mDebugWidth, mDebugHeight, mDebugGap, mDebugTextColor, mDebugBackColor;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create a new Screen. Initially 1x1 in size.
+ * <p>
+ * Has to be followed by a 'resizeFBO()' to set the size.
+ */
+  public DistortedScreen()
+    {
+    super(DistortedLibrary.WAIT_FOR_FBO_QUEUE_SIZE,1,BOTH_DEPTH_STENCIL, TYPE_SYST, STORAGE_PRIVATE,1,1);
+    mDebugMode = DEBUG_MODE_NONE;
+    mCurRenderedFBO = 0;
+    mToBeBlittedFBO = 0;
+    mFirstCircle = true;
+    mDebugAllocated = false;
+    mQueueSize = -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draws all the attached children to this OutputSurface.
+ * <p>
+ * Must be called from a thread holding OpenGL Context.
+ *
+ * @param time Current time, in milliseconds. This will be passed to all the Effects stored in the children Nodes.
+ * @return Number of objects rendered.
+ */
+  public int render(long time)
+    {
+    if( mDebugMode!=DEBUG_MODE_NONE )
+      {
+      int w = getWidth();
+      int h = getHeight();
+
+      if( lastTime==0 ) lastTime = time;
+
+      if( mDebugMode==DEBUG_MODE_FPS )
+        {
+        currDuration++;
+        if (currDuration >= NUM_FRAMES) currDuration = 0;
+        durations[NUM_FRAMES] += ((time - lastTime) - durations[currDuration]);
+        durations[currDuration] = time - lastTime;
+
+        debugString = "" + ((int)(10000.0f*NUM_FRAMES/durations[NUM_FRAMES]))/10.0f;
+        }
+      else if( mDebugMode==DEBUG_MODE_FRAME )
+        {
+        debugString = "" + frameNumber;
+        frameNumber++;
+        }
+
+      if( !mDebugAllocated )
+        {
+        mDebugAllocated = true;
+        debugString = "";
+
+        if( mDebugWidth<=0 || mDebugHeight<=0 )
+          {
+          mDebugWidth = (int)(w*DEBUG_SCR_FRAC);
+          mDebugHeight= (int)(w*DEBUG_SCR_FRAC*DEBUG_FRAC);
+
+          if( mDebugWidth<=0 || mDebugHeight<=0 )
+            {
+            int width = 100;
+            mDebugWidth = (int)(width*DEBUG_SCR_FRAC);
+            mDebugHeight= (int)(width*DEBUG_SCR_FRAC*DEBUG_FRAC);
+            }
+          }
+
+        debugBitmap = Bitmap.createBitmap( mDebugWidth, mDebugHeight, Bitmap.Config.ARGB_8888);
+        debugMesh = new MeshQuad();
+        debugTexture = new DistortedTexture();
+        debugTexture.setTexture(debugBitmap);
+        debugCanvas = new Canvas(debugBitmap);
+        debugEffects = new DistortedEffects();
+        debugEffects.apply( new MatrixEffectScale( new Static3D(mDebugWidth,mDebugHeight,1) ) );
+        debugEffects.apply(mMoveEffect);
+
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setTextAlign(Paint.Align.CENTER);
+        mPaint.setTextSize(0.7f*mDebugHeight);
+        }
+
+      mPaint.setColor(mDebugBackColor);
+      debugCanvas.drawRect(0, 0, mDebugWidth, mDebugHeight, mPaint);
+      mPaint.setColor(mDebugTextColor);
+      debugCanvas.drawText(debugString, 0.5f*mDebugWidth, 0.75f*mDebugHeight, mPaint);
+      debugTexture.setTexture(debugBitmap);
+
+      mMoveVector.set( (-w+mDebugWidth)*0.5f +mDebugGap, (h-mDebugHeight)*0.5f -mDebugGap, 0);
+
+      lastTime = time;
+      }
+
+    int numrender = super.render(time,mCurRenderedFBO);
+
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+
+    // workaround for the Mali issues: blit the framebuffer we have computed DistortedLibrary.FBO_QUEUE_SIZE
+    // frames ago. Looks like FBO_QUEUE_SIZE=2 solves the issue already but I decided to play it safe and
+    // make it equal to 3.
+    // This of course introduces a delay and uses more memory, but it does not appear to have any effect
+    // on speed. Maybe a slight positive effect if any!
+    setAsInput(mToBeBlittedFBO,0);
+
+    GLES30.glColorMask(true,true,true,true);
+    GLES30.glDepthMask(false);
+    GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+    GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+    GLES30.glDisable(GLES30.GL_BLEND);
+
+    DistortedLibrary.blitPriv(this);
+
+    if( mDebugMode!=DEBUG_MODE_NONE && debugTexture.setAsInput())
+      {
+      DistortedLibrary.drawPriv(debugEffects, debugMesh, this, time,0);
+      }
+
+    if( mQueueSize<=0 )
+      {
+      mQueueSize = DistortedLibrary.getQueueSize();
+      }
+
+    if( ++mCurRenderedFBO>=mQueueSize )
+      {
+      mCurRenderedFBO = 0;
+      if (mFirstCircle) mFirstCircle = false;
+      }
+    if( !mFirstCircle && ++mToBeBlittedFBO>=mQueueSize )
+      {
+      mToBeBlittedFBO=0;
+      }
+/*
+    int err = GLES30.glGetError();
+
+    if( err!=GLES30.GL_NO_ERROR )
+      {
+      DistortedLibrary.logMessage("DistortedScreen: OpenGL error "+err);
+      }
+*/
+    return numrender+1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Make the library show Frames Per Second in the upper-left corner.
+ * <p>
+ */
+  public void showFPS()
+    {
+    int w         = getWidth();
+    int width     = (int)(w*DEBUG_SCR_FRAC);
+    int height    = (int)(w*DEBUG_SCR_FRAC*DEBUG_FRAC);
+    int gap       = 5;
+    int textColor = 0xffffffff;
+    int backColor = 0xff000000;
+
+    showFPS(width,height,gap,textColor,backColor);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Make the library show Frames Per Second in the upper-left corner.
+ * Set appropriate params.
+ * <p>
+ */
+  public void showFPS(int width, int height, int gap, int textColor, int backColor)
+    {
+    mDebugWidth     = width;
+    mDebugHeight    = height;
+    mDebugGap       = gap;
+    mDebugBackColor = backColor;
+    mDebugTextColor = textColor;
+
+    if( mDebugMode!=DEBUG_MODE_FPS )
+      {
+      mDebugMode = DEBUG_MODE_FPS;
+
+      durations = new long[NUM_FRAMES + 1];
+      currDuration = 0;
+
+      for (int i=0; i<NUM_FRAMES+1; i++) durations[i] = 16;  // Assume FPS will be
+      durations[NUM_FRAMES] = NUM_FRAMES * 16;               // close to 1000/16 ~ 60
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Make the library show current frame number in the upper-left corner.
+ * <p>
+ */
+  public void showFrameNumber()
+    {
+    if( mDebugMode!=DEBUG_MODE_FRAME )
+      {
+      mDebugMode = DEBUG_MODE_FRAME;
+      frameNumber = 0;
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/DistortedTexture.java b/src/main/java/org/distorted/library/main/DistortedTexture.java
deleted file mode 100644
index bfcee89..0000000
--- a/src/main/java/org/distorted/library/main/DistortedTexture.java
+++ /dev/null
@@ -1,211 +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.main;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.opengl.GLES30;
-import android.opengl.GLUtils;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Class which represents a OpenGL Texture object.
- * <p>
- * Create a Texture of arbitrary size and feed some pixels to it via the setTexture() method.
- */
-public class DistortedTexture extends InternalSurface
-  {
-  private Bitmap mBmp;
-  private boolean mBitmapInverted;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// We have to vertically flip all the bitmaps passed here via setTexture().
-//
-// Reason: textures read from files are the only objects in OpenGL which have their origins at the
-// upper-left corner. Everywhere else the origin is in the lower-left corner. Thus we have to flip.
-// The alternative solution, namely inverting the y-coordinate of the TexCoord does not really work-
-// i.e. it works only in case of rendering directly to the screen, but if we render to an FBO and
-// then take the FBO and render to screen, (DistortedNode does so!) things get inverted as textures
-// created from FBO have their origins in the lower-left...
-
-  private static Bitmap flipBitmap(Bitmap src)
-    {
-    Matrix matrix = new Matrix();
-    matrix.preScale(1.0f,-1.0f);
-
-    return Bitmap.createBitmap(src,0,0,src.getWidth(),src.getHeight(), matrix,true);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called from a thread holding OpenGL Context
-
-  public void create()
-    {
-    if( mBmp!=null )
-      {
-      if( mColorCreated==NOT_CREATED_YET )
-        {
-        mColorCreated = CREATED;
-        GLES30.glGenTextures(1, mColorH, 0);
-        }
-
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[0]);
-      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR );
-      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR );
-      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE );
-      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE );
-      GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, mBitmapInverted ? mBmp : flipBitmap(mBmp), 0);
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-
-      mBmp = null;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called from a thread holding OpenGL Context
-
-  public void delete()
-    {
-    if( mColorH[0]>0 )
-      {
-      GLES30.glDeleteTextures(1, mColorH, 0);
-      mColorH[0] = 0;
-      mColorCreated = NOT_CREATED_YET;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// called from onDestroy(); mark OpenGL assets as 'not created'
-
-  public void recreate()
-    {
-    if( mColorCreated!=DONT_CREATE )
-      {
-      mColorCreated = NOT_CREATED_YET;
-      mColorH[0] = 0;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// create SYSTEM or TREE textures (those are just like normal Textures, just hold information
-// that they were autocreated only for the Library's internal purposes (SYSTEM) or for using
-// inside a Tree of DistortedNodes (TREE)
-// SYSTEM surfaces do not get removed in onDestroy().
-
-  public DistortedTexture(int type)
-    {
-    super(NOT_CREATED_YET,1,1,type,InternalObject.STORAGE_PRIVATE);
-    mBmp= null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create an empty texture.
- */
-  public DistortedTexture()
-    {
-    this(TYPE_USER);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Sets the underlying android.graphics.Bitmap object.
- * <p>
- * You can only recycle() the passed Bitmap once the OpenGL context gets created (i.e. after call
- * to GLSurfaceView.onSurfaceCreated) because only after this point can the Library upload it to the GPU!
- *
- * @param bmp The android.graphics.Bitmap object to apply effects to and display.
- * @return true if successful, false if texture too large.
- */
-  public boolean setTexture(Bitmap bmp)
-    {
-    int width = bmp.getWidth();
-    int height= bmp.getHeight();
-    int max   = DistortedLibrary.getMaxTextureSize();
-
-    if( width>max || height>max )
-      {
-      DistortedLibrary.logMessage("DistortedTexture: error, trying to upload too large texture of size "+width+" x "+height+". Max is "+max);
-      return false;
-      }
-    else
-      {
-      mBitmapInverted = false;
-      mBmp= bmp;
-      markForCreation();
-      return true;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Sets the underlying android.graphics.Bitmap object - this version assumes the object is already
- * flipped upside down.
- * <p>
- * You can only recycle() the passed Bitmap once the OpenGL context gets created (i.e. after call
- * to GLSurfaceView.onSurfaceCreated) because only after this point can the Library upload it to the GPU!
- *
- * @param bmp The (vertically flipped!) android.graphics.Bitmap object to apply effects to and display.
- * @return true if successful, false if texture too large.
- */
-  public boolean setTextureAlreadyInverted(Bitmap bmp)
-    {
-    int width = bmp.getWidth();
-    int height= bmp.getHeight();
-    int max   = DistortedLibrary.getMaxTextureSize();
-
-    if( width>max || height>max )
-      {
-      DistortedLibrary.logMessage("DistortedTexture: error, trying to upload too large texture of size "+width+" x "+height+". Max is "+max);
-      return false;
-      }
-    else
-      {
-      mBitmapInverted = true;
-      mBmp= bmp;
-      markForCreation();
-      return true;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Paints the Texture with solid color.
- *
- * @param argb The color to paint the Texture with. Example: 0xffff0000 - red.
- */
-  public void setColorARGB(int argb)
-    {
-    Paint paint = new Paint();
-    paint.setColor(argb);
-    paint.setStyle(Paint.Style.FILL);
-
-    mBmp = Bitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888);
-    Canvas canvas = new Canvas(mBmp);
-    canvas.drawRect(0,0,1,1,paint);
-
-    markForCreation();
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/DistortedTexture.kt b/src/main/java/org/distorted/library/main/DistortedTexture.kt
new file mode 100644
index 0000000..bfcee89
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/DistortedTexture.kt
@@ -0,0 +1,211 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.opengl.GLES30;
+import android.opengl.GLUtils;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Class which represents a OpenGL Texture object.
+ * <p>
+ * Create a Texture of arbitrary size and feed some pixels to it via the setTexture() method.
+ */
+public class DistortedTexture extends InternalSurface
+  {
+  private Bitmap mBmp;
+  private boolean mBitmapInverted;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// We have to vertically flip all the bitmaps passed here via setTexture().
+//
+// Reason: textures read from files are the only objects in OpenGL which have their origins at the
+// upper-left corner. Everywhere else the origin is in the lower-left corner. Thus we have to flip.
+// The alternative solution, namely inverting the y-coordinate of the TexCoord does not really work-
+// i.e. it works only in case of rendering directly to the screen, but if we render to an FBO and
+// then take the FBO and render to screen, (DistortedNode does so!) things get inverted as textures
+// created from FBO have their origins in the lower-left...
+
+  private static Bitmap flipBitmap(Bitmap src)
+    {
+    Matrix matrix = new Matrix();
+    matrix.preScale(1.0f,-1.0f);
+
+    return Bitmap.createBitmap(src,0,0,src.getWidth(),src.getHeight(), matrix,true);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context
+
+  public void create()
+    {
+    if( mBmp!=null )
+      {
+      if( mColorCreated==NOT_CREATED_YET )
+        {
+        mColorCreated = CREATED;
+        GLES30.glGenTextures(1, mColorH, 0);
+        }
+
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[0]);
+      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR );
+      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR );
+      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE );
+      GLES30.glTexParameteri ( GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE );
+      GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, mBitmapInverted ? mBmp : flipBitmap(mBmp), 0);
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+
+      mBmp = null;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context
+
+  public void delete()
+    {
+    if( mColorH[0]>0 )
+      {
+      GLES30.glDeleteTextures(1, mColorH, 0);
+      mColorH[0] = 0;
+      mColorCreated = NOT_CREATED_YET;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// called from onDestroy(); mark OpenGL assets as 'not created'
+
+  public void recreate()
+    {
+    if( mColorCreated!=DONT_CREATE )
+      {
+      mColorCreated = NOT_CREATED_YET;
+      mColorH[0] = 0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// create SYSTEM or TREE textures (those are just like normal Textures, just hold information
+// that they were autocreated only for the Library's internal purposes (SYSTEM) or for using
+// inside a Tree of DistortedNodes (TREE)
+// SYSTEM surfaces do not get removed in onDestroy().
+
+  public DistortedTexture(int type)
+    {
+    super(NOT_CREATED_YET,1,1,type,InternalObject.STORAGE_PRIVATE);
+    mBmp= null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create an empty texture.
+ */
+  public DistortedTexture()
+    {
+    this(TYPE_USER);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the underlying android.graphics.Bitmap object.
+ * <p>
+ * You can only recycle() the passed Bitmap once the OpenGL context gets created (i.e. after call
+ * to GLSurfaceView.onSurfaceCreated) because only after this point can the Library upload it to the GPU!
+ *
+ * @param bmp The android.graphics.Bitmap object to apply effects to and display.
+ * @return true if successful, false if texture too large.
+ */
+  public boolean setTexture(Bitmap bmp)
+    {
+    int width = bmp.getWidth();
+    int height= bmp.getHeight();
+    int max   = DistortedLibrary.getMaxTextureSize();
+
+    if( width>max || height>max )
+      {
+      DistortedLibrary.logMessage("DistortedTexture: error, trying to upload too large texture of size "+width+" x "+height+". Max is "+max);
+      return false;
+      }
+    else
+      {
+      mBitmapInverted = false;
+      mBmp= bmp;
+      markForCreation();
+      return true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Sets the underlying android.graphics.Bitmap object - this version assumes the object is already
+ * flipped upside down.
+ * <p>
+ * You can only recycle() the passed Bitmap once the OpenGL context gets created (i.e. after call
+ * to GLSurfaceView.onSurfaceCreated) because only after this point can the Library upload it to the GPU!
+ *
+ * @param bmp The (vertically flipped!) android.graphics.Bitmap object to apply effects to and display.
+ * @return true if successful, false if texture too large.
+ */
+  public boolean setTextureAlreadyInverted(Bitmap bmp)
+    {
+    int width = bmp.getWidth();
+    int height= bmp.getHeight();
+    int max   = DistortedLibrary.getMaxTextureSize();
+
+    if( width>max || height>max )
+      {
+      DistortedLibrary.logMessage("DistortedTexture: error, trying to upload too large texture of size "+width+" x "+height+". Max is "+max);
+      return false;
+      }
+    else
+      {
+      mBitmapInverted = true;
+      mBmp= bmp;
+      markForCreation();
+      return true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Paints the Texture with solid color.
+ *
+ * @param argb The color to paint the Texture with. Example: 0xffff0000 - red.
+ */
+  public void setColorARGB(int argb)
+    {
+    Paint paint = new Paint();
+    paint.setColor(argb);
+    paint.setStyle(Paint.Style.FILL);
+
+    mBmp = Bitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888);
+    Canvas canvas = new Canvas(mBmp);
+    canvas.drawRect(0,0,1,1,paint);
+
+    markForCreation();
+    }
+  }
