commit 635a3fdb8a8f78192f5221327db75d3cfbe399ce
Author: LeszekKoltunski <leszek@koltunski.pl>
Date:   Fri May 30 10:38:41 2025 +0200

    Rename .java to .kt

diff --git a/src/main/java/org/distorted/library/main/InternalBuffer.java b/src/main/java/org/distorted/library/main/InternalBuffer.java
deleted file mode 100644
index 904f81e..0000000
--- a/src/main/java/org/distorted/library/main/InternalBuffer.java
+++ /dev/null
@@ -1,223 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2018 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 java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Implements OpenGL buffer object such as GL_ARRAY_BUFFER or GL_TRANSFORM_FEEDBACK_BUFFER.
- * Main point: deal with Android lifecycle and recreate the buffer on loss of OpenGL context.
- * <p>
- * Not part of public API, do not document (public only because has to be used in Meshes)
- *
- * @y.exclude
- */
-public class InternalBuffer extends InternalObject
-  {
-  private static final int DONE     = 0;
-  private static final int RECREATE = 1;
-  private static final int UPDATE   = 2;
-
-  private int mStatus, mSize;
-  private final int[] mIndex;
-  private final int mTarget, mUsage;
-  private Buffer mBuffer;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public InternalBuffer()
-    {
-    super(InternalObject.TYPE_USER, InternalObject.STORAGE_PRIVATE );
-
-    mIndex  = new int[1];
-    mTarget = GLES30.GL_UNIFORM_BUFFER;
-    mUsage  = GLES30.GL_STATIC_DRAW;
-    mBuffer = null;
-    mSize   = 0;
-    mStatus = RECREATE;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public InternalBuffer(int target, int usage)
-    {
-    super(InternalObject.TYPE_USER, InternalObject.STORAGE_PRIVATE );
-
-    mIndex  = new int[1];
-    mTarget = target;
-    mUsage  = usage;
-    mBuffer = null;
-    mSize   = 0;
-    mStatus = RECREATE;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called from a thread holding OpenGL Context.
-
-  public int createImmediatelyFloat(int size, float[] buffer)
-    {
-    if( (mStatus & RECREATE) != 0 )
-      {
-      mSize= size;
-
-      if( buffer!=null )
-        {
-        FloatBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asFloatBuffer();
-        buf.put(buffer).position(0);
-        mBuffer = buf;
-        }
-      else
-        {
-        mBuffer = null;
-        }
-
-      GLES30.glGenBuffers( 1, mIndex, 0);
-      GLES30.glBindBuffer( mTarget, mIndex[0]);
-      GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
-      GLES30.glBindBuffer( mTarget, 0);
-
-      markWasCreatedImmediately();
-      }
-    else if( (mStatus & UPDATE) != 0 )
-      {
-      updateFloat(buffer);
-      }
-
-    mStatus = DONE;
-
-    return mIndex[0];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called from a thread holding OpenGL Context.
-
-  public int createImmediatelyInt(int size, int[] buffer)
-    {
-    if( (mStatus & RECREATE) != 0 )
-      {
-      mSize= size;
-
-      if( buffer!=null )
-        {
-        IntBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asIntBuffer();
-        buf.put(buffer).position(0);
-        mBuffer = buf;
-        }
-      else
-        {
-        mBuffer = null;
-        }
-
-      GLES30.glGenBuffers( 1, mIndex, 0);
-      GLES30.glBindBuffer( mTarget,  mIndex[0]);
-      GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
-      GLES30.glBindBuffer( mTarget,  0);
-
-      markWasCreatedImmediately();
-      }
-    else if( (mStatus & UPDATE) != 0  )
-      {
-      updateInt(buffer);
-      }
-
-    mStatus = DONE;
-
-    return mIndex[0];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// buffer non-null!!
-
-  public void updateFloat(float[] buffer)
-    {
-    ((FloatBuffer)mBuffer).put(buffer).position(0);
-
-    GLES30.glBindBuffer( mTarget, mIndex[0]);
-    GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
-    GLES30.glBindBuffer( mTarget, 0);
-
-    mStatus &= (~UPDATE);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// buffer non-null!!
-
-  public void updateInt(int[] buffer)
-    {
-    ((IntBuffer)mBuffer).put(buffer).position(0);
-
-    GLES30.glBindBuffer( mTarget, mIndex[0]);
-    GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
-    GLES30.glBindBuffer( mTarget, 0);
-
-    mStatus &= (~UPDATE);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void invalidate()
-    {
-    mStatus |= UPDATE;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Intentionally empty, no need to do anything here since it will be done in createImmediatelyXXX().
-// In fact, recreating a Mesh's mVBO1 here - rather than in createImmediatelyFloat - was the reason
-// of the 'disappearing cube after the mesh has changed from nice to simple' bug. I don't quite
-// understand why TBH.
-
-  void create()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called from a thread holding OpenGL Context
-
-  void delete()
-    {
-    GLES30.glDeleteBuffers(1, mIndex, 0);
-    mStatus |= RECREATE;
-    removeFromDone();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void recreate()
-    {
-    mStatus |= RECREATE;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// debugging only
-
-  String printDetails()
-    {
-    return getClass().getSimpleName();
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/InternalBuffer.kt b/src/main/java/org/distorted/library/main/InternalBuffer.kt
new file mode 100644
index 0000000..904f81e
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalBuffer.kt
@@ -0,0 +1,223 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2018 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 java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Implements OpenGL buffer object such as GL_ARRAY_BUFFER or GL_TRANSFORM_FEEDBACK_BUFFER.
+ * Main point: deal with Android lifecycle and recreate the buffer on loss of OpenGL context.
+ * <p>
+ * Not part of public API, do not document (public only because has to be used in Meshes)
+ *
+ * @y.exclude
+ */
+public class InternalBuffer extends InternalObject
+  {
+  private static final int DONE     = 0;
+  private static final int RECREATE = 1;
+  private static final int UPDATE   = 2;
+
+  private int mStatus, mSize;
+  private final int[] mIndex;
+  private final int mTarget, mUsage;
+  private Buffer mBuffer;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public InternalBuffer()
+    {
+    super(InternalObject.TYPE_USER, InternalObject.STORAGE_PRIVATE );
+
+    mIndex  = new int[1];
+    mTarget = GLES30.GL_UNIFORM_BUFFER;
+    mUsage  = GLES30.GL_STATIC_DRAW;
+    mBuffer = null;
+    mSize   = 0;
+    mStatus = RECREATE;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public InternalBuffer(int target, int usage)
+    {
+    super(InternalObject.TYPE_USER, InternalObject.STORAGE_PRIVATE );
+
+    mIndex  = new int[1];
+    mTarget = target;
+    mUsage  = usage;
+    mBuffer = null;
+    mSize   = 0;
+    mStatus = RECREATE;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context.
+
+  public int createImmediatelyFloat(int size, float[] buffer)
+    {
+    if( (mStatus & RECREATE) != 0 )
+      {
+      mSize= size;
+
+      if( buffer!=null )
+        {
+        FloatBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asFloatBuffer();
+        buf.put(buffer).position(0);
+        mBuffer = buf;
+        }
+      else
+        {
+        mBuffer = null;
+        }
+
+      GLES30.glGenBuffers( 1, mIndex, 0);
+      GLES30.glBindBuffer( mTarget, mIndex[0]);
+      GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
+      GLES30.glBindBuffer( mTarget, 0);
+
+      markWasCreatedImmediately();
+      }
+    else if( (mStatus & UPDATE) != 0 )
+      {
+      updateFloat(buffer);
+      }
+
+    mStatus = DONE;
+
+    return mIndex[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context.
+
+  public int createImmediatelyInt(int size, int[] buffer)
+    {
+    if( (mStatus & RECREATE) != 0 )
+      {
+      mSize= size;
+
+      if( buffer!=null )
+        {
+        IntBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()).asIntBuffer();
+        buf.put(buffer).position(0);
+        mBuffer = buf;
+        }
+      else
+        {
+        mBuffer = null;
+        }
+
+      GLES30.glGenBuffers( 1, mIndex, 0);
+      GLES30.glBindBuffer( mTarget,  mIndex[0]);
+      GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
+      GLES30.glBindBuffer( mTarget,  0);
+
+      markWasCreatedImmediately();
+      }
+    else if( (mStatus & UPDATE) != 0  )
+      {
+      updateInt(buffer);
+      }
+
+    mStatus = DONE;
+
+    return mIndex[0];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// buffer non-null!!
+
+  public void updateFloat(float[] buffer)
+    {
+    ((FloatBuffer)mBuffer).put(buffer).position(0);
+
+    GLES30.glBindBuffer( mTarget, mIndex[0]);
+    GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
+    GLES30.glBindBuffer( mTarget, 0);
+
+    mStatus &= (~UPDATE);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// buffer non-null!!
+
+  public void updateInt(int[] buffer)
+    {
+    ((IntBuffer)mBuffer).put(buffer).position(0);
+
+    GLES30.glBindBuffer( mTarget, mIndex[0]);
+    GLES30.glBufferData( mTarget, mSize, mBuffer, mUsage);
+    GLES30.glBindBuffer( mTarget, 0);
+
+    mStatus &= (~UPDATE);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void invalidate()
+    {
+    mStatus |= UPDATE;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Intentionally empty, no need to do anything here since it will be done in createImmediatelyXXX().
+// In fact, recreating a Mesh's mVBO1 here - rather than in createImmediatelyFloat - was the reason
+// of the 'disappearing cube after the mesh has changed from nice to simple' bug. I don't quite
+// understand why TBH.
+
+  void create()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context
+
+  void delete()
+    {
+    GLES30.glDeleteBuffers(1, mIndex, 0);
+    mStatus |= RECREATE;
+    removeFromDone();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void recreate()
+    {
+    mStatus |= RECREATE;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// debugging only
+
+  String printDetails()
+    {
+    return getClass().getSimpleName();
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/InternalChildrenList.java b/src/main/java/org/distorted/library/main/InternalChildrenList.java
deleted file mode 100644
index 96fcf10..0000000
--- a/src/main/java/org/distorted/library/main/InternalChildrenList.java
+++ /dev/null
@@ -1,257 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 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.mesh.MeshBase;
-
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class InternalChildrenList implements InternalMaster.Slave
-  {
-  private static final int ATTACH = 0;
-  private static final int DETACH = 1;
-  private static final int DETALL = 2;
-  private static final int SORT   = 3;
-
-  private static class Job
-    {
-    int type;
-    DistortedNode node;
-
-    Job(int t, DistortedNode n)
-      {
-      type = t;
-      node = n;
-      }
-    }
-
-  private final ArrayList<Job> mJobs;
-  private final InternalChildrenList.Parent mParent;
-  private ArrayList<DistortedNode> mChildren;
-  private int mNumChildren;
-
-  public interface Parent
-    {
-    void adjustIsomorphism();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalChildrenList(InternalChildrenList.Parent parent)
-    {
-    mParent = parent;
-    mJobs = new ArrayList<>();
-    mChildren = null;
-    mNumChildren = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getNumChildren()
-    {
-    return mNumChildren;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  DistortedNode getChild(int index)
-    {
-    return mChildren.get(index);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void rearrangeByBuckets(int index,long bucket)
-    {
-    DistortedNode child = mChildren.remove(index);
-    int i;
-
-    for(i=0; i<index; i++)
-      {
-      if( mChildren.get(i).getBucket() > bucket ) break;
-      }
-
-    mChildren.add(i,child);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Can make this logarithmic but the typical number of children is very small anyway.
-//
-// We want to keep same buckets next to each other, while avoiding changes in order of the children
-// (if possible!)
-// 2022/10/25: removed keeping bucket 0 (i.e. non-postprocessed children) always in the front -
-// we don't need it (given the fixes to renderChildren() )
-
-  private void addSortingByBuckets(DistortedNode newChild)
-    {
-    int i;
-    long bucket = newChild.getBucket();
-    boolean thisSame,lastSame = false;
-
-    for(i=0; i<mNumChildren; i++)
-      {
-      thisSame= (mChildren.get(i).getBucket()==bucket);
-      if( lastSame && !thisSame ) break;
-      lastSame = thisSame;
-      }
-
-    mChildren.add(i,newChild);
-    mNumChildren++;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void attach(DistortedNode node)
-    {
-    node.resetLastTime();
-    mJobs.add(new Job(ATTACH,node));
-    InternalMaster.newSlave(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  DistortedNode attach(InternalSurface surface, DistortedEffects effects, MeshBase mesh)
-    {
-    DistortedNode node = new DistortedNode(surface,effects,mesh);
-    mJobs.add(new Job(ATTACH,node));
-    InternalMaster.newSlave(this);
-    return node;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void detach(DistortedNode node)
-    {
-    mJobs.add(new Job(DETACH,node));
-    InternalMaster.newSlave(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void detach(DistortedEffects effects)
-    {
-    long id = effects.getID();
-    DistortedNode node;
-    boolean detached = false;
-
-    for(int i=0; i<mNumChildren; i++)
-      {
-      node = mChildren.get(i);
-
-      if( node.getEffects().getID()==id )
-        {
-        detached = true;
-        mJobs.add(new Job(DETACH,node));
-        InternalMaster.newSlave(this);
-        break;
-        }
-      }
-
-    if( !detached )
-      {
-      // if we failed to detach any, it still might be the case that
-      // there's an ATTACH job that we need to cancel.
-      int num = mJobs.size();
-      Job job;
-
-      for(int i=0; i<num; i++)
-        {
-        job = mJobs.get(i);
-
-        if( job.type==ATTACH && job.node.getEffects()==effects )
-          {
-          mJobs.remove(i);
-          break;
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void detachAll()
-    {
-    mJobs.add(new Job(DETALL,null));
-    InternalMaster.newSlave(this);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This is not really part of the public API. Has to be public only because it is a part of the
- * DistortedSlave interface, which should really be a class that we extend here instead but
- * Java has no multiple inheritance.
- *
- * @y.exclude
- */
-  public void doWork()
-    {
-    int num = mJobs.size();
-
-    if( num>0 )
-      {
-      Job job;
-      int numChanges=0;
-
-      for(int i=0; i<num; i++)
-        {
-        job = mJobs.remove(0);
-
-        switch(job.type)
-          {
-          case ATTACH: numChanges++;
-                       if( mChildren==null ) mChildren = new ArrayList<>(2);
-                       job.node.setParent(mParent);
-                       addSortingByBuckets(job.node);
-                       break;
-          case DETACH: numChanges++;
-                       if( mNumChildren>0 && mChildren.remove(job.node) )
-                         {
-                         job.node.setParent(null);
-                         mNumChildren--;
-                         }
-                       break;
-          case DETALL: numChanges++;
-                       if( mNumChildren>0 )
-                         {
-                         DistortedNode tmp;
-
-                         for(int j=mNumChildren-1; j>=0; j--)
-                           {
-                           tmp = mChildren.remove(j);
-                           tmp.setParent(null);
-                           }
-
-                         mNumChildren = 0;
-                         }
-                       break;
-          case SORT  : mChildren.remove(job.node);
-                       addSortingByBuckets(job.node);
-                       break;
-          }
-        }
-      if( numChanges>0 ) mParent.adjustIsomorphism();
-      }
-    }
-  }
-
diff --git a/src/main/java/org/distorted/library/main/InternalChildrenList.kt b/src/main/java/org/distorted/library/main/InternalChildrenList.kt
new file mode 100644
index 0000000..96fcf10
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalChildrenList.kt
@@ -0,0 +1,257 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 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.mesh.MeshBase;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class InternalChildrenList implements InternalMaster.Slave
+  {
+  private static final int ATTACH = 0;
+  private static final int DETACH = 1;
+  private static final int DETALL = 2;
+  private static final int SORT   = 3;
+
+  private static class Job
+    {
+    int type;
+    DistortedNode node;
+
+    Job(int t, DistortedNode n)
+      {
+      type = t;
+      node = n;
+      }
+    }
+
+  private final ArrayList<Job> mJobs;
+  private final InternalChildrenList.Parent mParent;
+  private ArrayList<DistortedNode> mChildren;
+  private int mNumChildren;
+
+  public interface Parent
+    {
+    void adjustIsomorphism();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalChildrenList(InternalChildrenList.Parent parent)
+    {
+    mParent = parent;
+    mJobs = new ArrayList<>();
+    mChildren = null;
+    mNumChildren = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getNumChildren()
+    {
+    return mNumChildren;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedNode getChild(int index)
+    {
+    return mChildren.get(index);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void rearrangeByBuckets(int index,long bucket)
+    {
+    DistortedNode child = mChildren.remove(index);
+    int i;
+
+    for(i=0; i<index; i++)
+      {
+      if( mChildren.get(i).getBucket() > bucket ) break;
+      }
+
+    mChildren.add(i,child);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Can make this logarithmic but the typical number of children is very small anyway.
+//
+// We want to keep same buckets next to each other, while avoiding changes in order of the children
+// (if possible!)
+// 2022/10/25: removed keeping bucket 0 (i.e. non-postprocessed children) always in the front -
+// we don't need it (given the fixes to renderChildren() )
+
+  private void addSortingByBuckets(DistortedNode newChild)
+    {
+    int i;
+    long bucket = newChild.getBucket();
+    boolean thisSame,lastSame = false;
+
+    for(i=0; i<mNumChildren; i++)
+      {
+      thisSame= (mChildren.get(i).getBucket()==bucket);
+      if( lastSame && !thisSame ) break;
+      lastSame = thisSame;
+      }
+
+    mChildren.add(i,newChild);
+    mNumChildren++;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void attach(DistortedNode node)
+    {
+    node.resetLastTime();
+    mJobs.add(new Job(ATTACH,node));
+    InternalMaster.newSlave(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  DistortedNode attach(InternalSurface surface, DistortedEffects effects, MeshBase mesh)
+    {
+    DistortedNode node = new DistortedNode(surface,effects,mesh);
+    mJobs.add(new Job(ATTACH,node));
+    InternalMaster.newSlave(this);
+    return node;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void detach(DistortedNode node)
+    {
+    mJobs.add(new Job(DETACH,node));
+    InternalMaster.newSlave(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void detach(DistortedEffects effects)
+    {
+    long id = effects.getID();
+    DistortedNode node;
+    boolean detached = false;
+
+    for(int i=0; i<mNumChildren; i++)
+      {
+      node = mChildren.get(i);
+
+      if( node.getEffects().getID()==id )
+        {
+        detached = true;
+        mJobs.add(new Job(DETACH,node));
+        InternalMaster.newSlave(this);
+        break;
+        }
+      }
+
+    if( !detached )
+      {
+      // if we failed to detach any, it still might be the case that
+      // there's an ATTACH job that we need to cancel.
+      int num = mJobs.size();
+      Job job;
+
+      for(int i=0; i<num; i++)
+        {
+        job = mJobs.get(i);
+
+        if( job.type==ATTACH && job.node.getEffects()==effects )
+          {
+          mJobs.remove(i);
+          break;
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void detachAll()
+    {
+    mJobs.add(new Job(DETALL,null));
+    InternalMaster.newSlave(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is not really part of the public API. Has to be public only because it is a part of the
+ * DistortedSlave interface, which should really be a class that we extend here instead but
+ * Java has no multiple inheritance.
+ *
+ * @y.exclude
+ */
+  public void doWork()
+    {
+    int num = mJobs.size();
+
+    if( num>0 )
+      {
+      Job job;
+      int numChanges=0;
+
+      for(int i=0; i<num; i++)
+        {
+        job = mJobs.remove(0);
+
+        switch(job.type)
+          {
+          case ATTACH: numChanges++;
+                       if( mChildren==null ) mChildren = new ArrayList<>(2);
+                       job.node.setParent(mParent);
+                       addSortingByBuckets(job.node);
+                       break;
+          case DETACH: numChanges++;
+                       if( mNumChildren>0 && mChildren.remove(job.node) )
+                         {
+                         job.node.setParent(null);
+                         mNumChildren--;
+                         }
+                       break;
+          case DETALL: numChanges++;
+                       if( mNumChildren>0 )
+                         {
+                         DistortedNode tmp;
+
+                         for(int j=mNumChildren-1; j>=0; j--)
+                           {
+                           tmp = mChildren.remove(j);
+                           tmp.setParent(null);
+                           }
+
+                         mNumChildren = 0;
+                         }
+                       break;
+          case SORT  : mChildren.remove(job.node);
+                       addSortingByBuckets(job.node);
+                       break;
+          }
+        }
+      if( numChanges>0 ) mParent.adjustIsomorphism();
+      }
+    }
+  }
+
diff --git a/src/main/java/org/distorted/library/main/InternalMaster.java b/src/main/java/org/distorted/library/main/InternalMaster.java
deleted file mode 100644
index d313190..0000000
--- a/src/main/java/org/distorted/library/main/InternalMaster.java
+++ /dev/null
@@ -1,106 +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 java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This static class handles assigning jobs to other classes. It does it once, at the beginning of
- * each frame.
- * <p>
- * Not part of public API, do not document (public only because has to be used in PostprocessEffects)
- *
- * @y.exclude
- */
-public class InternalMaster
-  {
-  /**
-   * Not part of public API, do not document (public only because has to be used in PostprocessEffects)
-   *
-   * @y.exclude
-   */
-  public interface Slave
-    {
-    void doWork();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private InternalMaster()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static boolean toDo()
-    {
-    Slave slave;
-    ArrayList<Slave> list = InternalStackFrameList.getSet();
-    int numSlaves = list.size();
-
-    try
-      {
-      for(int i=0; i<numSlaves; i++)
-        {
-        slave = list.remove(0);
-        if( slave!=null ) slave.doWork();
-        }
-      }
-    catch(IndexOutOfBoundsException ie)
-      {
-      // onDestroy must have been called, ignore
-      }
-
-    return numSlaves>0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void newSlave(Slave s)
-    {
-    ArrayList<Slave> list = InternalStackFrameList.getSet();
-    int num = list.size();
-    boolean found = false;
-    Slave tmp;
-
-    try
-      {
-      for(int i=0; i<num; i++)
-        {
-        tmp = list.get(i);
-
-        if( tmp==s )
-          {
-          found = true;
-          break;
-          }
-        }
-      }
-    catch(IndexOutOfBoundsException ie)
-      {
-      // onDestroy must have been called, ignore
-      }
-
-    if( !found ) list.add(s);
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/InternalMaster.kt b/src/main/java/org/distorted/library/main/InternalMaster.kt
new file mode 100644
index 0000000..d313190
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalMaster.kt
@@ -0,0 +1,106 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This static class handles assigning jobs to other classes. It does it once, at the beginning of
+ * each frame.
+ * <p>
+ * Not part of public API, do not document (public only because has to be used in PostprocessEffects)
+ *
+ * @y.exclude
+ */
+public class InternalMaster
+  {
+  /**
+   * Not part of public API, do not document (public only because has to be used in PostprocessEffects)
+   *
+   * @y.exclude
+   */
+  public interface Slave
+    {
+    void doWork();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private InternalMaster()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static boolean toDo()
+    {
+    Slave slave;
+    ArrayList<Slave> list = InternalStackFrameList.getSet();
+    int numSlaves = list.size();
+
+    try
+      {
+      for(int i=0; i<numSlaves; i++)
+        {
+        slave = list.remove(0);
+        if( slave!=null ) slave.doWork();
+        }
+      }
+    catch(IndexOutOfBoundsException ie)
+      {
+      // onDestroy must have been called, ignore
+      }
+
+    return numSlaves>0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void newSlave(Slave s)
+    {
+    ArrayList<Slave> list = InternalStackFrameList.getSet();
+    int num = list.size();
+    boolean found = false;
+    Slave tmp;
+
+    try
+      {
+      for(int i=0; i<num; i++)
+        {
+        tmp = list.get(i);
+
+        if( tmp==s )
+          {
+          found = true;
+          break;
+          }
+        }
+      }
+    catch(IndexOutOfBoundsException ie)
+      {
+      // onDestroy must have been called, ignore
+      }
+
+    if( !found ) list.add(s);
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/InternalNodeData.java b/src/main/java/org/distorted/library/main/InternalNodeData.java
deleted file mode 100644
index 42192c4..0000000
--- a/src/main/java/org/distorted/library/main/InternalNodeData.java
+++ /dev/null
@@ -1,93 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 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 java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This is a member of DistortedNode. Makes sure two isomorphic Nodes only get rendered once.
- */
-class InternalNodeData
-  {
-  private final ArrayList<Long> mKey;
-  private int numPointingNodes;
-  private long currTime;
-
-  final long ID;
-  DistortedFramebuffer mFBO;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalNodeData(long id, ArrayList<Long> k)
-    {
-    ID              = id;
-    mKey            = k;
-    numPointingNodes= 1;
-    currTime        =-1;
-    mFBO            = null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static InternalNodeData returnData(ArrayList<Long> list)
-    {
-    InternalNodeData data = InternalStackFrameList.getMapID(list);
-
-    if( data!=null )
-      {
-      data.numPointingNodes++;
-      }
-    else
-      {
-      data = InternalStackFrameList.putNewDataToMap(list);
-      }
-
-    return data;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean removeData()
-    {
-    if( --numPointingNodes==0 )
-      {
-      InternalStackFrameList.removeKeyFromMap(mKey);
-
-        return mFBO != null;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean notRenderedYetAtThisTime(long time)
-    {
-    if( currTime!=time )
-      {
-      currTime = time;
-      return true;
-      }
-
-    return false;
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/InternalNodeData.kt b/src/main/java/org/distorted/library/main/InternalNodeData.kt
new file mode 100644
index 0000000..42192c4
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalNodeData.kt
@@ -0,0 +1,93 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 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 java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is a member of DistortedNode. Makes sure two isomorphic Nodes only get rendered once.
+ */
+class InternalNodeData
+  {
+  private final ArrayList<Long> mKey;
+  private int numPointingNodes;
+  private long currTime;
+
+  final long ID;
+  DistortedFramebuffer mFBO;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalNodeData(long id, ArrayList<Long> k)
+    {
+    ID              = id;
+    mKey            = k;
+    numPointingNodes= 1;
+    currTime        =-1;
+    mFBO            = null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static InternalNodeData returnData(ArrayList<Long> list)
+    {
+    InternalNodeData data = InternalStackFrameList.getMapID(list);
+
+    if( data!=null )
+      {
+      data.numPointingNodes++;
+      }
+    else
+      {
+      data = InternalStackFrameList.putNewDataToMap(list);
+      }
+
+    return data;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean removeData()
+    {
+    if( --numPointingNodes==0 )
+      {
+      InternalStackFrameList.removeKeyFromMap(mKey);
+
+        return mFBO != null;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean notRenderedYetAtThisTime(long time)
+    {
+    if( currTime!=time )
+      {
+      currTime = time;
+      return true;
+      }
+
+    return false;
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/InternalObject.java b/src/main/java/org/distorted/library/main/InternalObject.java
deleted file mode 100644
index b1f042a..0000000
--- a/src/main/java/org/distorted/library/main/InternalObject.java
+++ /dev/null
@@ -1,128 +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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Any Object which gets uploaded to GPU memory and thus needs to be re-created (transparently to
- * applications!) whenever we lose OpenGL context.
- * <p>
- * Keep all objects created in a static LinkedList. The point: we need to be able to mark
- * Objects for deletion, and delete all marked Objects later at a convenient time (that's
- * because we can only delete from a thread that holds the OpenGL context so here we provide a
- * framework where one is able to mark for deletion at any time and actual deletion takes place
- * on the next render).
-*/
-abstract class InternalObject
-{
-  static final int FAILED_TO_CREATE = 1;
-  static final int NOT_CREATED_YET  = 2;
-  static final int DONT_CREATE      = 3;
-  static final int CREATED          = 4;
-
-  static final int TYPE_USER = 0;
-  static final int TYPE_TREE = 1;
-  static final int TYPE_SYST = 2;
-
-  static final int STORAGE_COMMON  = 0;
-  static final int STORAGE_PRIVATE = 1;
-
-  static final int JOB_CREATE = 0;
-  static final int JOB_DELETE = 1;
-
-  private final long mID;
-  private final int mType;
-  private final int mStorage;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  abstract void create();
-  abstract void delete();
-  abstract void recreate();
-  abstract String printDetails();
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void print(String msg)
-    {
-    String str = "ID:"+mID;
-
-    switch(mType)
-      {
-      case TYPE_SYST: str+=" SYSTEM "; break;
-      case TYPE_USER: str+=" USER   "; break;
-      case TYPE_TREE: str+=" TREE   "; break;
-      default       : str+=" ERROR? ";
-      }
-
-    DistortedLibrary.logMessage("InternalObject: "+str+printDetails()+msg);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalObject(int type, int storage)
-    {
-    mType    = type;
-    mStorage = storage;
-    mID      = InternalStackFrameList.getCurrentFrame().generateID(mType,mStorage);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void markWasCreatedImmediately()
-    {
-    InternalStackFrameList.getCurrentFrame().addToDoneList(this,mStorage);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void markForCreation()
-    {
-    InternalStackFrameList.markFor(this,mID,mStorage,JOB_CREATE);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void removeFromDone()
-    {
-    InternalStackFrameList.removeFromDone(this,mStorage);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
- */
-  public void markForDeletion()
-    {
-    InternalStackFrameList.markFor(this,mID,mStorage,JOB_DELETE);
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return unique ID of this Object.
- */
-  public long getID()
-    {
-    return mID;
-    }
-}
diff --git a/src/main/java/org/distorted/library/main/InternalObject.kt b/src/main/java/org/distorted/library/main/InternalObject.kt
new file mode 100644
index 0000000..b1f042a
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalObject.kt
@@ -0,0 +1,128 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Any Object which gets uploaded to GPU memory and thus needs to be re-created (transparently to
+ * applications!) whenever we lose OpenGL context.
+ * <p>
+ * Keep all objects created in a static LinkedList. The point: we need to be able to mark
+ * Objects for deletion, and delete all marked Objects later at a convenient time (that's
+ * because we can only delete from a thread that holds the OpenGL context so here we provide a
+ * framework where one is able to mark for deletion at any time and actual deletion takes place
+ * on the next render).
+*/
+abstract class InternalObject
+{
+  static final int FAILED_TO_CREATE = 1;
+  static final int NOT_CREATED_YET  = 2;
+  static final int DONT_CREATE      = 3;
+  static final int CREATED          = 4;
+
+  static final int TYPE_USER = 0;
+  static final int TYPE_TREE = 1;
+  static final int TYPE_SYST = 2;
+
+  static final int STORAGE_COMMON  = 0;
+  static final int STORAGE_PRIVATE = 1;
+
+  static final int JOB_CREATE = 0;
+  static final int JOB_DELETE = 1;
+
+  private final long mID;
+  private final int mType;
+  private final int mStorage;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract void create();
+  abstract void delete();
+  abstract void recreate();
+  abstract String printDetails();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void print(String msg)
+    {
+    String str = "ID:"+mID;
+
+    switch(mType)
+      {
+      case TYPE_SYST: str+=" SYSTEM "; break;
+      case TYPE_USER: str+=" USER   "; break;
+      case TYPE_TREE: str+=" TREE   "; break;
+      default       : str+=" ERROR? ";
+      }
+
+    DistortedLibrary.logMessage("InternalObject: "+str+printDetails()+msg);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalObject(int type, int storage)
+    {
+    mType    = type;
+    mStorage = storage;
+    mID      = InternalStackFrameList.getCurrentFrame().generateID(mType,mStorage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void markWasCreatedImmediately()
+    {
+    InternalStackFrameList.getCurrentFrame().addToDoneList(this,mStorage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void markForCreation()
+    {
+    InternalStackFrameList.markFor(this,mID,mStorage,JOB_CREATE);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeFromDone()
+    {
+    InternalStackFrameList.removeFromDone(this,mStorage);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
+ */
+  public void markForDeletion()
+    {
+    InternalStackFrameList.markFor(this,mID,mStorage,JOB_DELETE);
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return unique ID of this Object.
+ */
+  public long getID()
+    {
+    return mID;
+    }
+}
diff --git a/src/main/java/org/distorted/library/main/InternalOutputSurface.java b/src/main/java/org/distorted/library/main/InternalOutputSurface.java
deleted file mode 100644
index 5d47cfc..0000000
--- a/src/main/java/org/distorted/library/main/InternalOutputSurface.java
+++ /dev/null
@@ -1,980 +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 org.distorted.library.effect.EffectQuality;
-import org.distorted.library.effectqueue.EffectQueuePostprocess;
-import org.distorted.library.helpers.MatrixHelper;
-import org.distorted.library.mesh.MeshBase;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * This is not really part of the public API.
- *
- * @y.exclude
- */
-public abstract class InternalOutputSurface extends InternalSurface implements InternalChildrenList.Parent
-{
-  public static final int NO_DEPTH_NO_STENCIL = 0;
-  public static final int DEPTH_NO_STENCIL    = 1;
-  public static final int BOTH_DEPTH_STENCIL  = 2;
-
-  static final float DEFAULT_FOV = 60.0f;
-  static final float DEFAULT_NEAR=  0.1f;
-
-  private float mFOV;
-  private final int mTmpFBO;
-
-  private long[] mTime;
-  private float mClearR, mClearG, mClearB, mClearA, mClearDepth;
-  private int mClear, mClearStencil;
-  private boolean mRenderWayOIT;
-  private final InternalChildrenList mChildren;
-
-  // Global buffers used for postprocessing
-  private final static DistortedFramebuffer[] mBuffer= new DistortedFramebuffer[EffectQuality.LENGTH];
-  private final boolean[] mBufferInitialized;
-
-  float mDistance, mNear, mMipmap;
-  float[] mProjectionMatrix;
-  int mDepthStencilCreated, mDepthStencil;
-  int[] mDepthStencilH, mFBOH;
-  int mRealWidth;   // the Surface can be backed up by a texture larger than the viewport we have to it.
-  int mRealHeight;  // mWidth,mHeight are the sizes of the Viewport, those - sizes of the backing up texture.
-  int mCurrFBO;     // internal current FBO (see DistortedLibrary.FBO_QUEUE_SIZE)
-  int mWidth, mHeight;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalOutputSurface(int width, int height, int createColor, int numfbos, int numcolors, int depthStencil, int fbo, int type, int storage)
-    {
-    super(createColor,numfbos,numcolors,type,storage);
-
-    mRenderWayOIT = false;
-    mCurrFBO      = 0;
-
-    mRealWidth = mWidth = width;
-    mRealHeight= mHeight= height;
-
-    mProjectionMatrix = new float[16];
-
-    mFOV = DEFAULT_FOV;
-    mNear= DEFAULT_NEAR;
-
-    mDepthStencilCreated= (depthStencil== NO_DEPTH_NO_STENCIL ? DONT_CREATE:NOT_CREATED_YET);
-    mDepthStencil = depthStencil;
-
-    mClearR = 0.0f;
-    mClearG = 0.0f;
-    mClearB = 0.0f;
-    mClearA = 0.0f;
-
-    mClearDepth = 1.0f;
-    mClearStencil = 0;
-    mClear = GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT;
-
-    mMipmap = 1.0f;
-
-    mChildren = new InternalChildrenList(this);
-
-    mTmpFBO = fbo;
-
-    mFBOH = new int[10];  // Crashlytics shows the library occasionally crashing in setAsOutput()
-    mTime = new long[10]; // when trying to read from 'null array' mFBOH. Probably sometimes a
-                          // a Framebuffer gets created in the wrong moment, just after we did a
-                          // round of create(), but before we start rendering.
-                          // Create an empty FBO and Time here so that setAsOutput() is always safe to call.
-
-    mBufferInitialized = new boolean[EffectQuality.LENGTH];
-
-    allocateStuffDependantOnNumFBOS();
-    createProjection();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void allocateStuffDependantOnNumFBOS()
-    {
-    if( mNumFBOs>0 )
-      {
-      mDepthStencilH   = new int[mNumFBOs];
-      mDepthStencilH[0]= 0;
-
-      mFBOH   = new int[mNumFBOs];
-      mFBOH[0]= mTmpFBO;
-
-      mTime = new long[mNumFBOs];
-      for(int i=0; i<mNumFBOs;i++) mTime[i]=0;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createProjection()
-    {
-    if( mWidth>0 && mHeight>1 )
-      {
-      if( mFOV>0.0f )  // perspective projection
-        {
-        float a = 2.0f*(float)Math.tan(mFOV*Math.PI/360);
-        float q = mWidth*mNear;
-        float c = mHeight*mNear;
-
-        float left   = -q/2;
-        float right  =  q/2;
-        float bottom = -c/2;
-        float top    =  c/2;
-        float near   =  c/a;
-
-        mDistance    = mHeight/a;
-        float far    = 2*mDistance-near;
-
-        MatrixHelper.frustum(mProjectionMatrix, left, right, bottom, top, near, far);
-        }
-      else             // parallel projection
-        {
-        float left   = -mWidth/2.0f;
-        float right  =  mWidth/2.0f;
-        float bottom = -mHeight/2.0f;
-        float top    =  mHeight/2.0f;
-        float near   = mWidth+mHeight-mHeight*(1.0f-mNear);
-        mDistance    = mWidth+mHeight;
-        float far    = mWidth+mHeight+mHeight*(1.0f-mNear);
-
-        MatrixHelper.ortho(mProjectionMatrix, left, right, bottom, top, near, far);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void createPostprocessingBuffers(int quality, int width, int height, float near)
-    {
-    final float CLEAR_R = 1.0f;
-    final float CLEAR_G = 1.0f;
-    final float CLEAR_B = 1.0f;
-    final float CLEAR_A = 0.0f;
-    final float CLEAR_D = 1.0f;
-    final int   CLEAR_S = 0;
-
-    final int queueSize = DistortedLibrary.getQueueSize();
-    float mipmap= EffectQuality.getMipmap(quality);
-
-    mBuffer[quality] = new DistortedFramebuffer(queueSize,2,BOTH_DEPTH_STENCIL,TYPE_SYST, STORAGE_COMMON, (int)(width*mipmap), (int)(height*mipmap) );
-    mBuffer[quality].mMipmap = mipmap;
-    mBuffer[quality].mNear = near;  // copy mNear as well (for blitting- see PostprocessEffect.apply() )
-    mBuffer[quality].glClearColor(CLEAR_R, CLEAR_G, CLEAR_B, CLEAR_A);
-
-    InternalStackFrameList.toDo(); // create the FBOs immediately. This is safe as we must be holding the OpenGL context now.
-
-    InternalRenderState.colorDepthStencilOn();
-    GLES30.glClearColor(CLEAR_R, CLEAR_G, CLEAR_B, CLEAR_A);
-    GLES30.glClearDepthf(CLEAR_D);
-    GLES30.glClearStencil(CLEAR_S);
-
-    for(int k=0; k<queueSize; k++)
-      {
-      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mBuffer[quality].mFBOH[k]);
-      GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mBuffer[quality].mColorH[2*k+1], 0);
-      GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_STENCIL_BUFFER_BIT);
-      GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mBuffer[quality].mColorH[2*k  ], 0);
-      GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
-      }
-
-    InternalRenderState.colorDepthStencilRestore();
-
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static synchronized void onPause()
-    {
-    for (int j=0; j<EffectQuality.LENGTH; j++)
-      if( mBuffer[j]!=null )
-        {
-        mBuffer[j].markForDeletion();
-        mBuffer[j] = null;
-        }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int blitWithDepth(long currTime, InternalOutputSurface buffer, int fbo)
-    {
-    GLES30.glViewport(0, 0, mWidth, mHeight);
-    setAsOutput(currTime);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo]);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mDepthStencilH[fbo]);
-
-    GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-    GLES30.glStencilMask(0x00);
-
-    DistortedLibrary.blitDepthPriv(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-
-    // clear buffers
-    GLES30.glStencilMask(0xff);
-    GLES30.glDepthMask(true);
-    GLES30.glColorMask(true,true,true,true);
-    GLES30.glClearColor(buffer.mClearR,buffer.mClearG,buffer.mClearB,buffer.mClearA);
-    GLES30.glClearDepthf(buffer.mClearDepth);
-    GLES30.glClearStencil(buffer.mClearStencil);
-
-    buffer.setAsOutput();
-    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo+1], 0);
-    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT|GLES30.GL_DEPTH_BUFFER_BIT|GLES30.GL_STENCIL_BUFFER_BIT);
-    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo  ], 0);
-    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private static void oitClear(InternalOutputSurface buffer)
-    {
-    int counter = DistortedLibrary.zeroOutAtomic();
-    DistortedLibrary.oitClear(buffer,counter);
-    GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT|GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int oitBuild(long time, InternalOutputSurface buffer, int fbo)
-    {
-    GLES30.glViewport(0, 0, mWidth, mHeight);
-    setAsOutput(time);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo]);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mDepthStencilH[fbo]);
-
-    InternalRenderState.colorDepthStencilOn();
-    InternalRenderState.enableDepthTest();
-
-    DistortedLibrary.oitBuild(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
-    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-
-    InternalRenderState.colorDepthStencilRestore();
-    InternalRenderState.restoreDepthTest();
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// two phases: 1. collapse the SSBO 2. blend the ssbo's color
-
-  private int oitRender(long currTime, int fbo)
-    {
-    float corrW = getWidthCorrection();
-    float corrH = getHeightCorrection();
-
-    // Do the Collapse Pass only if we do have a Depth attachment.
-    // Otherwise there's no point (in fact we then would create a feedback loop!)
-
-    if( mDepthStencilH[fbo] != 0 )
-      {
-      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
-      GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mDepthStencilH[fbo]);
-      InternalRenderState.switchOffColorDepthStencil();
-      DistortedLibrary.oitCollapse(this, corrW, corrH);
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
-      }
-
-    setAsOutput(currTime);
-    InternalRenderState.switchColorDepthOnStencilOff();
-    DistortedLibrary.oitRender(this, corrW, corrH);
-    InternalRenderState.restoreColorDepthStencil();
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void clear()
-    {
-    InternalRenderState.colorDepthStencilOn();
-    GLES30.glClearColor(mClearR, mClearG, mClearB, mClearA);
-    GLES30.glClearDepthf(mClearDepth);
-    GLES30.glClearStencil(mClearStencil);
-    GLES30.glClear(mClear);
-    InternalRenderState.colorDepthStencilRestore();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setCurrFBO(int fbo)
-    {
-    mCurrFBO = fbo;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Render all children from the current bucket to the buffer, apply the postprocessing once to the
-// whole buffer (queue.postprocess) and merge it to 'this' (oitBuild or blitWithDepth depending on
-// the type of rendering)
-
-  private int accumulateAndBlit(EffectQueuePostprocess queue, InternalChildrenList children, DistortedFramebuffer buffer,
-                                int begIndex, int endIndex, boolean isFinal, long time, int fbo, boolean oit )
-    {
-    int numRenders = 0;
-
-    for(int j=begIndex; j<endIndex; j++)
-       {
-       DistortedNode node = children.getChild(j);
-
-       if( node.getSurface().setAsInput() )
-         {
-         buffer.setAsOutput();
-         numRenders += queue.preprocess( buffer, node, buffer.mDistance, buffer.mMipmap, buffer.mProjectionMatrix );
-         }
-       }
-    numRenders += queue.postprocess(buffer);
-
-    if( oit )
-      {
-      numRenders += oitBuild(time, buffer, fbo);
-      GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT | GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
-      buffer.clearBuffer(fbo);
-      }
-    else
-      {
-      numRenders += blitWithDepth(time, buffer, fbo);
-      if( !isFinal ) buffer.clearBuffer(fbo);
-      }
-
-    return numRenders;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int renderChildToThisOrToBuffer(DistortedNode child, DistortedFramebuffer buffer, long time, boolean oit, boolean toThis)
-    {
-    int numRenders;
-
-    if( toThis )
-      {
-      setAsOutput(time);
-
-      if( oit )
-        {
-        numRenders = child.drawOIT(time, this);
-        GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT | GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
-        }
-      else
-        {
-        numRenders = child.draw(time, this);
-        }
-      }
-    else
-      {
-      buffer.setAsOutput(time);
-      numRenders = child.drawNoBlend(time, buffer);
-      }
-
-    return numRenders;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// The postprocessing buffers mBuffer[] are generally speaking too large (there's just one static
-// set of them) so before we use them for output, we need to adjust the Viewport as if they were
-// smaller. That takes care of outputting pixels to them. When we use them as input, we have to
-// adjust the texture coords - see the get{Width|Height}Correction functions.
-//
-// Also, adjust the Buffers so their Projection is the same like the surface we are supposed to be
-// rendering to.
-
-  private void clonePostprocessingViewportAndProjection(InternalOutputSurface surface, InternalOutputSurface from)
-    {
-    if( surface.mWidth != from.mWidth || surface.mHeight != from.mHeight ||
-        surface.mFOV   != from.mFOV   || surface.mNear   != from.mNear    )
-      {
-      surface.mWidth  = (int)(from.mWidth *surface.mMipmap);
-      surface.mHeight = (int)(from.mHeight*surface.mMipmap);
-      surface.mFOV    = from.mFOV;
-      surface.mNear   = from.mNear;  // Near plane is independent of the mipmap level
-
-      surface.createProjection();
-
-      int maxw = Math.max(surface.mWidth , surface.mRealWidth );
-      int maxh = Math.max(surface.mHeight, surface.mRealHeight);
-
-      if (maxw > surface.mRealWidth || maxh > surface.mRealHeight)
-        {
-        surface.mRealWidth = maxw;
-        surface.mRealHeight = maxh;
-
-        surface.recreate();
-        surface.create();
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private DistortedFramebuffer initializeBuffer(EffectQueuePostprocess queue, int fbo )
-    {
-    int currQuality = queue.getQuality();
-    if( mBuffer[currQuality]==null ) createPostprocessingBuffers(currQuality, mWidth, mHeight, mNear);
-    mBuffer[currQuality].setCurrFBO(fbo);
-
-    if( !mBufferInitialized[currQuality] )
-      {
-      mBufferInitialized[currQuality] = true;
-      clonePostprocessingViewportAndProjection(mBuffer[currQuality],this);
-      }
-
-    return mBuffer[currQuality];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Render all children, one by one. If there are no postprocessing effects, just render to THIS.
-// Otherwise, render to a buffer and on each change of Postprocessing Bucket, apply the postprocessing
-// to a whole buffer (lastQueue.postprocess) and merge it (this.oitBuild or blitWithDepth - depending
-// on the type of rendering)
-
-  int renderChildren(long time, int numChildren, InternalChildrenList children, int fbo, boolean oit)
-    {
-    int numRenders=0, bucketChange=0;
-    DistortedNode child;
-    DistortedFramebuffer buffer=null;
-    EffectQueuePostprocess lastQueue=null, currQueue;
-    long lastBucket=0, currBucket;
-    boolean toThis=false;
-
-    setCurrFBO(fbo);
-    if( numChildren==0 ) setAsOutput(time);
-    if( oit && numChildren>0 ) oitClear(this);
-    for(int i=0; i<EffectQuality.LENGTH; i++) mBufferInitialized[i]=false;
-
-    for(int i=0; i<numChildren; i++)
-      {
-      child = children.getChild(i);
-      currQueue = (EffectQueuePostprocess)child.getEffects().getQueues()[3];
-      currBucket= currQueue.getID();
-
-      if( currBucket!=0 && lastBucket!=currBucket )
-        {
-        buffer = initializeBuffer(currQueue,fbo);
-        if( lastBucket!=0 ) numRenders += accumulateAndBlit(lastQueue,children,buffer,bucketChange,i,false,time,fbo,oit);
-        bucketChange= i;
-        toThis = currQueue.getRenderDirectly();
-        }
-      numRenders += renderChildToThisOrToBuffer(child,buffer,time,oit,currBucket==0 || toThis);
-      if( currBucket!=0 && i==numChildren-1 ) numRenders += accumulateAndBlit(currQueue,children,buffer,bucketChange,numChildren,true,time,fbo,oit);
-
-      lastQueue = currQueue;
-      lastBucket= currBucket;
-      }
-
-    if( oit && numChildren>0 ) numRenders += oitRender(time, fbo);  // merge the OIT linked list
-
-    return numRenders;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of the public API.
- *
- * @y.exclude
- */
-  public void adjustIsomorphism() { }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of the Public API.
- *
- * @y.exclude
- */
-  public float getWidthCorrection()
-    {
-    return (float)mWidth/mRealWidth;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Not part of the Public API.
- *
- * @y.exclude
- */
-  public float getHeightCorrection()
-    {
-    return (float)mHeight/mRealHeight;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void clearBuffer(int fbo)
-    {
-    InternalRenderState.colorDepthStencilOn();
-
-    GLES30.glClearColor(mClearR, mClearG, mClearB, mClearA);
-    GLES30.glClearDepthf(mClearDepth);
-    GLES30.glClearStencil(mClearStencil);
-
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[fbo]);
-    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[2*fbo+1], 0);
-    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT|GLES30.GL_DEPTH_BUFFER_BIT|GLES30.GL_STENCIL_BUFFER_BIT);
-    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[2*fbo  ], 0);
-    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
-
-    InternalRenderState.colorDepthStencilRestore();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setAsOutput(long time)
-    {
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[mCurrFBO]);
-
-    if( mTime[mCurrFBO]!=time )
-      {
-      mTime[mCurrFBO] = time;
-      clear();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Draws all the attached children to this OutputSurface's 0th FBO.
- * <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)
-    {
-    return render(time,0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * 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.
- * @param fbo The surface can have many FBOs backing it up - render this to FBO number 'fbo'.
- * @return Number of objects rendered.
- */
-  public int render(long time, int fbo)
-    {
-    InternalMaster.toDo();
-    InternalStackFrameList.toDo();
-    InternalRenderState.reset();
-
-    int numRenders=0, numChildren = mChildren.getNumChildren();
-    DistortedNode node;
-    long oldBucket=0, newBucket;
-
-    for(int i=0; i<numChildren; i++)
-      {
-      node = mChildren.getChild(i);
-      newBucket = node.getBucket();
-      numRenders += node.renderRecursive(time);
-      if( newBucket<oldBucket ) mChildren.rearrangeByBuckets(i,newBucket);
-      else oldBucket=newBucket;
-      }
-
-    numRenders += renderChildren(time,numChildren,mChildren,fbo, mRenderWayOIT);
-
-    return numRenders;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Recursively print all the effect queues attached to the children Nodes and to this Node.
- */
-  public void debug()
-    {
-    int numChildren = mChildren.getNumChildren();
-
-    for(int i=0; i<numChildren; i++)
-      {
-      DistortedNode node = mChildren.getChild(i);
-      node.debug(0);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Bind this Surface as a Framebuffer we can render to.
- * <p>
- * This version does not attempt to clear anything.
- */
-  public void setAsOutput()
-    {
-    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[mCurrFBO]);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the Near plane of the Projection included in the Surface.
- *
- * @return the Near plane.
- */
-  public float getNear()
-    {
-    return mNear;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set mipmap level.
- * <p>
- * Trick for speeding up your renders - one can create a pyramid of OutputSurface objects, each next
- * one some constant FACTOR smaller than the previous (0.5 is the common value), then set the Mipmap
- * Level of the i-th object to be FACTOR^i (we start counting from 0). When rendering any scene into
- * such prepared OutputSurface, the library will make sure to scale any Effects used so that the end
- * scene will end up looking identical no matter which object we render to. Identical, that is, except
- * for the loss of quality and gain in speed associated with rendering to a smaller Surface.
- * <p>
- * Example: if you create two FBOs, one 1000x1000 and another 500x500 in size, and set the second one
- * mipmap to 0.5 (the first one's is 1.0 by default), define Effects to be a single move by (100,100),
- * and render a skinned Mesh into both FBO, the end result will look proportionally the same, because
- * in the second case the move vector (100,100) will be auto-scaled to (50,50).
- *
- * @param mipmap The mipmap level. Acceptable range: 0&lt;mipmap&lt;infinity, although mipmap&gt;1
- *               does not make any sense (that would result in loss of speed and no gain in quality)
- */
-  public void setMipmap(float mipmap)
-    {
-    mMipmap = mipmap;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Set the (R,G,B,A) values of GLES31.glClearColor() to set up color with which to clear
- * this Surface at the beginning of each frame.
- *
- * @param r the Red component. Default: 0.0f
- * @param g the Green component. Default: 0.0f
- * @param b the Blue component. Default: 0.0f
- * @param a the Alpha component. Default: 0.0f
- */
-  public void glClearColor(float r, float g, float b, float a)
-    {
-    mClearR = r;
-    mClearG = g;
-    mClearB = b;
-    mClearA = a;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Uses glClearDepthf() to set up a value with which to clear
- * the Depth buffer of our Surface at the beginning of each frame.
- *
- * @param d the Depth. Default: 1.0f
- */
-  public void glClearDepthf(float d)
-    {
-    mClearDepth = d;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Uses glClearStencil() to set up a value with which to clear the
- * Stencil buffer of our Surface at the beginning of each frame.
- *
- * @param s the Stencil. Default: 0
- */
-  public void glClearStencil(int s)
-    {
-    mClearStencil = s;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Which buffers to Clear at the beginning of each frame?
- * <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: GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT.
- *
- * @param mask bitwise OR of BUFFER_BITs to clear.
- */
-  public void glClear(int mask)
-    {
-    mClear = mask;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Create new Projection matrix.
- *
- * @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;
-      }
-
-    for(int j=0; j<EffectQuality.LENGTH; j++)
-      {
-      if( mBuffer[j]!=null ) mBuffer[j].mNear = mNear;
-      }
-
-    createProjection();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the vertical field of view angle.
- *
- * @return Vertival Field of View Angle, in degrees.
- */
-  public float getFOV()
-    {
-    return mFOV;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Resize the underlying Framebuffer.
- * <p>
- * This method can be safely called mid-render as it doesn't interfere with rendering.
- *
- * @param width The new width.
- * @param height The new height.
- */
-  public void resize(int width, int height)
-    {
-    if( mWidth!=width || mHeight!=height )
-      {
-      mWidth = mRealWidth = width;
-      mHeight= mRealHeight= height;
-
-      createProjection();
-
-      if( mColorCreated==CREATED )
-        {
-        markForCreation();
-        recreate();
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return true if the Surface contains a DEPTH attachment.
- *
- * @return <bold>true</bold> if the Surface contains a DEPTH attachment.
- */
-  public boolean hasDepth()
-    {
-    return mDepthStencilCreated==CREATED;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return true if the Surface contains a STENCIL attachment.
- *
- * @return <bold>true</bold> if the Surface contains a STENCIL attachment.
- */
-  public boolean hasStencil()
-    {
-    return (mDepthStencilCreated==CREATED && mDepthStencil==BOTH_DEPTH_STENCIL);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, should we use the Order Independent Transparency render mode?
- * <p>
- * This feature requires OpenGL ES 3.1. If we are running on OpenGL 3.0, this will do nothing.
- * Also, if you are running on a buggy driver ( Imagination GE8100/8300 driver build 1.8@4490469 )
- * then do nothing.
- *
- * 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)
-    {
-    if( DistortedLibrary.getGLSL()>=310 )
-      {
-      mRenderWayOIT = oit;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * When rendering this Node, should we use the Order Independent Transparency render mode?
- * <p>
- * This feature requires OpenGL ES 3.1. If we are running on OpenGL 3.0, this will do nothing.
- * Also, if you are running on a buggy driver ( Imagination GE8100/8300 driver build 1.8@4490469 )
- * then do nothing.
- *
- * 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 screenfuls.
- *                    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)
-    {
-    if( DistortedLibrary.getGLSL()>=310 )
-      {
-      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 Surface'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 Surface'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 Surface.
- * <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 the first occurrence of a specified child from the list of children of our Surface.
- * <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 Node to remove.
- */
-  public void detach(DistortedNode node)
-    {
-    mChildren.detach(node);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Removes all children Nodes.
- * <p>
- * We cannot do this mid-render - actual attachment will be done just before the next render, by the
- * InternalMaster (by calling doWork())
- */
-  public void detachAll()
-    {
-    mChildren.detachAll();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the width of this Surface.
- *
- * @return width of the Object, in pixels.
- */
-  public int getWidth()
-    {
-    return mWidth;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Return the height of this Surface.
- *
- * @return height of the Object, in pixels.
- */
-  public int getHeight()
-    {
-    return mHeight;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/main/InternalOutputSurface.kt b/src/main/java/org/distorted/library/main/InternalOutputSurface.kt
new file mode 100644
index 0000000..5d47cfc
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalOutputSurface.kt
@@ -0,0 +1,980 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 org.distorted.library.effect.EffectQuality;
+import org.distorted.library.effectqueue.EffectQueuePostprocess;
+import org.distorted.library.helpers.MatrixHelper;
+import org.distorted.library.mesh.MeshBase;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is not really part of the public API.
+ *
+ * @y.exclude
+ */
+public abstract class InternalOutputSurface extends InternalSurface implements InternalChildrenList.Parent
+{
+  public static final int NO_DEPTH_NO_STENCIL = 0;
+  public static final int DEPTH_NO_STENCIL    = 1;
+  public static final int BOTH_DEPTH_STENCIL  = 2;
+
+  static final float DEFAULT_FOV = 60.0f;
+  static final float DEFAULT_NEAR=  0.1f;
+
+  private float mFOV;
+  private final int mTmpFBO;
+
+  private long[] mTime;
+  private float mClearR, mClearG, mClearB, mClearA, mClearDepth;
+  private int mClear, mClearStencil;
+  private boolean mRenderWayOIT;
+  private final InternalChildrenList mChildren;
+
+  // Global buffers used for postprocessing
+  private final static DistortedFramebuffer[] mBuffer= new DistortedFramebuffer[EffectQuality.LENGTH];
+  private final boolean[] mBufferInitialized;
+
+  float mDistance, mNear, mMipmap;
+  float[] mProjectionMatrix;
+  int mDepthStencilCreated, mDepthStencil;
+  int[] mDepthStencilH, mFBOH;
+  int mRealWidth;   // the Surface can be backed up by a texture larger than the viewport we have to it.
+  int mRealHeight;  // mWidth,mHeight are the sizes of the Viewport, those - sizes of the backing up texture.
+  int mCurrFBO;     // internal current FBO (see DistortedLibrary.FBO_QUEUE_SIZE)
+  int mWidth, mHeight;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalOutputSurface(int width, int height, int createColor, int numfbos, int numcolors, int depthStencil, int fbo, int type, int storage)
+    {
+    super(createColor,numfbos,numcolors,type,storage);
+
+    mRenderWayOIT = false;
+    mCurrFBO      = 0;
+
+    mRealWidth = mWidth = width;
+    mRealHeight= mHeight= height;
+
+    mProjectionMatrix = new float[16];
+
+    mFOV = DEFAULT_FOV;
+    mNear= DEFAULT_NEAR;
+
+    mDepthStencilCreated= (depthStencil== NO_DEPTH_NO_STENCIL ? DONT_CREATE:NOT_CREATED_YET);
+    mDepthStencil = depthStencil;
+
+    mClearR = 0.0f;
+    mClearG = 0.0f;
+    mClearB = 0.0f;
+    mClearA = 0.0f;
+
+    mClearDepth = 1.0f;
+    mClearStencil = 0;
+    mClear = GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT;
+
+    mMipmap = 1.0f;
+
+    mChildren = new InternalChildrenList(this);
+
+    mTmpFBO = fbo;
+
+    mFBOH = new int[10];  // Crashlytics shows the library occasionally crashing in setAsOutput()
+    mTime = new long[10]; // when trying to read from 'null array' mFBOH. Probably sometimes a
+                          // a Framebuffer gets created in the wrong moment, just after we did a
+                          // round of create(), but before we start rendering.
+                          // Create an empty FBO and Time here so that setAsOutput() is always safe to call.
+
+    mBufferInitialized = new boolean[EffectQuality.LENGTH];
+
+    allocateStuffDependantOnNumFBOS();
+    createProjection();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void allocateStuffDependantOnNumFBOS()
+    {
+    if( mNumFBOs>0 )
+      {
+      mDepthStencilH   = new int[mNumFBOs];
+      mDepthStencilH[0]= 0;
+
+      mFBOH   = new int[mNumFBOs];
+      mFBOH[0]= mTmpFBO;
+
+      mTime = new long[mNumFBOs];
+      for(int i=0; i<mNumFBOs;i++) mTime[i]=0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createProjection()
+    {
+    if( mWidth>0 && mHeight>1 )
+      {
+      if( mFOV>0.0f )  // perspective projection
+        {
+        float a = 2.0f*(float)Math.tan(mFOV*Math.PI/360);
+        float q = mWidth*mNear;
+        float c = mHeight*mNear;
+
+        float left   = -q/2;
+        float right  =  q/2;
+        float bottom = -c/2;
+        float top    =  c/2;
+        float near   =  c/a;
+
+        mDistance    = mHeight/a;
+        float far    = 2*mDistance-near;
+
+        MatrixHelper.frustum(mProjectionMatrix, left, right, bottom, top, near, far);
+        }
+      else             // parallel projection
+        {
+        float left   = -mWidth/2.0f;
+        float right  =  mWidth/2.0f;
+        float bottom = -mHeight/2.0f;
+        float top    =  mHeight/2.0f;
+        float near   = mWidth+mHeight-mHeight*(1.0f-mNear);
+        mDistance    = mWidth+mHeight;
+        float far    = mWidth+mHeight+mHeight*(1.0f-mNear);
+
+        MatrixHelper.ortho(mProjectionMatrix, left, right, bottom, top, near, far);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createPostprocessingBuffers(int quality, int width, int height, float near)
+    {
+    final float CLEAR_R = 1.0f;
+    final float CLEAR_G = 1.0f;
+    final float CLEAR_B = 1.0f;
+    final float CLEAR_A = 0.0f;
+    final float CLEAR_D = 1.0f;
+    final int   CLEAR_S = 0;
+
+    final int queueSize = DistortedLibrary.getQueueSize();
+    float mipmap= EffectQuality.getMipmap(quality);
+
+    mBuffer[quality] = new DistortedFramebuffer(queueSize,2,BOTH_DEPTH_STENCIL,TYPE_SYST, STORAGE_COMMON, (int)(width*mipmap), (int)(height*mipmap) );
+    mBuffer[quality].mMipmap = mipmap;
+    mBuffer[quality].mNear = near;  // copy mNear as well (for blitting- see PostprocessEffect.apply() )
+    mBuffer[quality].glClearColor(CLEAR_R, CLEAR_G, CLEAR_B, CLEAR_A);
+
+    InternalStackFrameList.toDo(); // create the FBOs immediately. This is safe as we must be holding the OpenGL context now.
+
+    InternalRenderState.colorDepthStencilOn();
+    GLES30.glClearColor(CLEAR_R, CLEAR_G, CLEAR_B, CLEAR_A);
+    GLES30.glClearDepthf(CLEAR_D);
+    GLES30.glClearStencil(CLEAR_S);
+
+    for(int k=0; k<queueSize; k++)
+      {
+      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mBuffer[quality].mFBOH[k]);
+      GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mBuffer[quality].mColorH[2*k+1], 0);
+      GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_STENCIL_BUFFER_BIT);
+      GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mBuffer[quality].mColorH[2*k  ], 0);
+      GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+      }
+
+    InternalRenderState.colorDepthStencilRestore();
+
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void onPause()
+    {
+    for (int j=0; j<EffectQuality.LENGTH; j++)
+      if( mBuffer[j]!=null )
+        {
+        mBuffer[j].markForDeletion();
+        mBuffer[j] = null;
+        }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int blitWithDepth(long currTime, InternalOutputSurface buffer, int fbo)
+    {
+    GLES30.glViewport(0, 0, mWidth, mHeight);
+    setAsOutput(currTime);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo]);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mDepthStencilH[fbo]);
+
+    GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+    GLES30.glStencilMask(0x00);
+
+    DistortedLibrary.blitDepthPriv(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+
+    // clear buffers
+    GLES30.glStencilMask(0xff);
+    GLES30.glDepthMask(true);
+    GLES30.glColorMask(true,true,true,true);
+    GLES30.glClearColor(buffer.mClearR,buffer.mClearG,buffer.mClearB,buffer.mClearA);
+    GLES30.glClearDepthf(buffer.mClearDepth);
+    GLES30.glClearStencil(buffer.mClearStencil);
+
+    buffer.setAsOutput();
+    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo+1], 0);
+    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT|GLES30.GL_DEPTH_BUFFER_BIT|GLES30.GL_STENCIL_BUFFER_BIT);
+    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo  ], 0);
+    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void oitClear(InternalOutputSurface buffer)
+    {
+    int counter = DistortedLibrary.zeroOutAtomic();
+    DistortedLibrary.oitClear(buffer,counter);
+    GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT|GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int oitBuild(long time, InternalOutputSurface buffer, int fbo)
+    {
+    GLES30.glViewport(0, 0, mWidth, mHeight);
+    setAsOutput(time);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mColorH[2*fbo]);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, buffer.mDepthStencilH[fbo]);
+
+    InternalRenderState.colorDepthStencilOn();
+    InternalRenderState.enableDepthTest();
+
+    DistortedLibrary.oitBuild(this, buffer.getWidthCorrection(), buffer.getHeightCorrection() );
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+    GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
+    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+
+    InternalRenderState.colorDepthStencilRestore();
+    InternalRenderState.restoreDepthTest();
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// two phases: 1. collapse the SSBO 2. blend the ssbo's color
+
+  private int oitRender(long currTime, int fbo)
+    {
+    float corrW = getWidthCorrection();
+    float corrH = getHeightCorrection();
+
+    // Do the Collapse Pass only if we do have a Depth attachment.
+    // Otherwise there's no point (in fact we then would create a feedback loop!)
+
+    if( mDepthStencilH[fbo] != 0 )
+      {
+      GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
+      GLES30.glActiveTexture(GLES30.GL_TEXTURE1);
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mDepthStencilH[fbo]);
+      InternalRenderState.switchOffColorDepthStencil();
+      DistortedLibrary.oitCollapse(this, corrW, corrH);
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
+      }
+
+    setAsOutput(currTime);
+    InternalRenderState.switchColorDepthOnStencilOff();
+    DistortedLibrary.oitRender(this, corrW, corrH);
+    InternalRenderState.restoreColorDepthStencil();
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void clear()
+    {
+    InternalRenderState.colorDepthStencilOn();
+    GLES30.glClearColor(mClearR, mClearG, mClearB, mClearA);
+    GLES30.glClearDepthf(mClearDepth);
+    GLES30.glClearStencil(mClearStencil);
+    GLES30.glClear(mClear);
+    InternalRenderState.colorDepthStencilRestore();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setCurrFBO(int fbo)
+    {
+    mCurrFBO = fbo;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Render all children from the current bucket to the buffer, apply the postprocessing once to the
+// whole buffer (queue.postprocess) and merge it to 'this' (oitBuild or blitWithDepth depending on
+// the type of rendering)
+
+  private int accumulateAndBlit(EffectQueuePostprocess queue, InternalChildrenList children, DistortedFramebuffer buffer,
+                                int begIndex, int endIndex, boolean isFinal, long time, int fbo, boolean oit )
+    {
+    int numRenders = 0;
+
+    for(int j=begIndex; j<endIndex; j++)
+       {
+       DistortedNode node = children.getChild(j);
+
+       if( node.getSurface().setAsInput() )
+         {
+         buffer.setAsOutput();
+         numRenders += queue.preprocess( buffer, node, buffer.mDistance, buffer.mMipmap, buffer.mProjectionMatrix );
+         }
+       }
+    numRenders += queue.postprocess(buffer);
+
+    if( oit )
+      {
+      numRenders += oitBuild(time, buffer, fbo);
+      GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT | GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
+      buffer.clearBuffer(fbo);
+      }
+    else
+      {
+      numRenders += blitWithDepth(time, buffer, fbo);
+      if( !isFinal ) buffer.clearBuffer(fbo);
+      }
+
+    return numRenders;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int renderChildToThisOrToBuffer(DistortedNode child, DistortedFramebuffer buffer, long time, boolean oit, boolean toThis)
+    {
+    int numRenders;
+
+    if( toThis )
+      {
+      setAsOutput(time);
+
+      if( oit )
+        {
+        numRenders = child.drawOIT(time, this);
+        GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT | GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
+        }
+      else
+        {
+        numRenders = child.draw(time, this);
+        }
+      }
+    else
+      {
+      buffer.setAsOutput(time);
+      numRenders = child.drawNoBlend(time, buffer);
+      }
+
+    return numRenders;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// The postprocessing buffers mBuffer[] are generally speaking too large (there's just one static
+// set of them) so before we use them for output, we need to adjust the Viewport as if they were
+// smaller. That takes care of outputting pixels to them. When we use them as input, we have to
+// adjust the texture coords - see the get{Width|Height}Correction functions.
+//
+// Also, adjust the Buffers so their Projection is the same like the surface we are supposed to be
+// rendering to.
+
+  private void clonePostprocessingViewportAndProjection(InternalOutputSurface surface, InternalOutputSurface from)
+    {
+    if( surface.mWidth != from.mWidth || surface.mHeight != from.mHeight ||
+        surface.mFOV   != from.mFOV   || surface.mNear   != from.mNear    )
+      {
+      surface.mWidth  = (int)(from.mWidth *surface.mMipmap);
+      surface.mHeight = (int)(from.mHeight*surface.mMipmap);
+      surface.mFOV    = from.mFOV;
+      surface.mNear   = from.mNear;  // Near plane is independent of the mipmap level
+
+      surface.createProjection();
+
+      int maxw = Math.max(surface.mWidth , surface.mRealWidth );
+      int maxh = Math.max(surface.mHeight, surface.mRealHeight);
+
+      if (maxw > surface.mRealWidth || maxh > surface.mRealHeight)
+        {
+        surface.mRealWidth = maxw;
+        surface.mRealHeight = maxh;
+
+        surface.recreate();
+        surface.create();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private DistortedFramebuffer initializeBuffer(EffectQueuePostprocess queue, int fbo )
+    {
+    int currQuality = queue.getQuality();
+    if( mBuffer[currQuality]==null ) createPostprocessingBuffers(currQuality, mWidth, mHeight, mNear);
+    mBuffer[currQuality].setCurrFBO(fbo);
+
+    if( !mBufferInitialized[currQuality] )
+      {
+      mBufferInitialized[currQuality] = true;
+      clonePostprocessingViewportAndProjection(mBuffer[currQuality],this);
+      }
+
+    return mBuffer[currQuality];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Render all children, one by one. If there are no postprocessing effects, just render to THIS.
+// Otherwise, render to a buffer and on each change of Postprocessing Bucket, apply the postprocessing
+// to a whole buffer (lastQueue.postprocess) and merge it (this.oitBuild or blitWithDepth - depending
+// on the type of rendering)
+
+  int renderChildren(long time, int numChildren, InternalChildrenList children, int fbo, boolean oit)
+    {
+    int numRenders=0, bucketChange=0;
+    DistortedNode child;
+    DistortedFramebuffer buffer=null;
+    EffectQueuePostprocess lastQueue=null, currQueue;
+    long lastBucket=0, currBucket;
+    boolean toThis=false;
+
+    setCurrFBO(fbo);
+    if( numChildren==0 ) setAsOutput(time);
+    if( oit && numChildren>0 ) oitClear(this);
+    for(int i=0; i<EffectQuality.LENGTH; i++) mBufferInitialized[i]=false;
+
+    for(int i=0; i<numChildren; i++)
+      {
+      child = children.getChild(i);
+      currQueue = (EffectQueuePostprocess)child.getEffects().getQueues()[3];
+      currBucket= currQueue.getID();
+
+      if( currBucket!=0 && lastBucket!=currBucket )
+        {
+        buffer = initializeBuffer(currQueue,fbo);
+        if( lastBucket!=0 ) numRenders += accumulateAndBlit(lastQueue,children,buffer,bucketChange,i,false,time,fbo,oit);
+        bucketChange= i;
+        toThis = currQueue.getRenderDirectly();
+        }
+      numRenders += renderChildToThisOrToBuffer(child,buffer,time,oit,currBucket==0 || toThis);
+      if( currBucket!=0 && i==numChildren-1 ) numRenders += accumulateAndBlit(currQueue,children,buffer,bucketChange,numChildren,true,time,fbo,oit);
+
+      lastQueue = currQueue;
+      lastBucket= currBucket;
+      }
+
+    if( oit && numChildren>0 ) numRenders += oitRender(time, fbo);  // merge the OIT linked list
+
+    return numRenders;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of the public API.
+ *
+ * @y.exclude
+ */
+  public void adjustIsomorphism() { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of the Public API.
+ *
+ * @y.exclude
+ */
+  public float getWidthCorrection()
+    {
+    return (float)mWidth/mRealWidth;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Not part of the Public API.
+ *
+ * @y.exclude
+ */
+  public float getHeightCorrection()
+    {
+    return (float)mHeight/mRealHeight;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void clearBuffer(int fbo)
+    {
+    InternalRenderState.colorDepthStencilOn();
+
+    GLES30.glClearColor(mClearR, mClearG, mClearB, mClearA);
+    GLES30.glClearDepthf(mClearDepth);
+    GLES30.glClearStencil(mClearStencil);
+
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[fbo]);
+    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[2*fbo+1], 0);
+    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT|GLES30.GL_DEPTH_BUFFER_BIT|GLES30.GL_STENCIL_BUFFER_BIT);
+    GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, mColorH[2*fbo  ], 0);
+    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
+
+    InternalRenderState.colorDepthStencilRestore();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setAsOutput(long time)
+    {
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[mCurrFBO]);
+
+    if( mTime[mCurrFBO]!=time )
+      {
+      mTime[mCurrFBO] = time;
+      clear();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Draws all the attached children to this OutputSurface's 0th FBO.
+ * <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)
+    {
+    return render(time,0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * 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.
+ * @param fbo The surface can have many FBOs backing it up - render this to FBO number 'fbo'.
+ * @return Number of objects rendered.
+ */
+  public int render(long time, int fbo)
+    {
+    InternalMaster.toDo();
+    InternalStackFrameList.toDo();
+    InternalRenderState.reset();
+
+    int numRenders=0, numChildren = mChildren.getNumChildren();
+    DistortedNode node;
+    long oldBucket=0, newBucket;
+
+    for(int i=0; i<numChildren; i++)
+      {
+      node = mChildren.getChild(i);
+      newBucket = node.getBucket();
+      numRenders += node.renderRecursive(time);
+      if( newBucket<oldBucket ) mChildren.rearrangeByBuckets(i,newBucket);
+      else oldBucket=newBucket;
+      }
+
+    numRenders += renderChildren(time,numChildren,mChildren,fbo, mRenderWayOIT);
+
+    return numRenders;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Recursively print all the effect queues attached to the children Nodes and to this Node.
+ */
+  public void debug()
+    {
+    int numChildren = mChildren.getNumChildren();
+
+    for(int i=0; i<numChildren; i++)
+      {
+      DistortedNode node = mChildren.getChild(i);
+      node.debug(0);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Bind this Surface as a Framebuffer we can render to.
+ * <p>
+ * This version does not attempt to clear anything.
+ */
+  public void setAsOutput()
+    {
+    GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFBOH[mCurrFBO]);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the Near plane of the Projection included in the Surface.
+ *
+ * @return the Near plane.
+ */
+  public float getNear()
+    {
+    return mNear;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set mipmap level.
+ * <p>
+ * Trick for speeding up your renders - one can create a pyramid of OutputSurface objects, each next
+ * one some constant FACTOR smaller than the previous (0.5 is the common value), then set the Mipmap
+ * Level of the i-th object to be FACTOR^i (we start counting from 0). When rendering any scene into
+ * such prepared OutputSurface, the library will make sure to scale any Effects used so that the end
+ * scene will end up looking identical no matter which object we render to. Identical, that is, except
+ * for the loss of quality and gain in speed associated with rendering to a smaller Surface.
+ * <p>
+ * Example: if you create two FBOs, one 1000x1000 and another 500x500 in size, and set the second one
+ * mipmap to 0.5 (the first one's is 1.0 by default), define Effects to be a single move by (100,100),
+ * and render a skinned Mesh into both FBO, the end result will look proportionally the same, because
+ * in the second case the move vector (100,100) will be auto-scaled to (50,50).
+ *
+ * @param mipmap The mipmap level. Acceptable range: 0&lt;mipmap&lt;infinity, although mipmap&gt;1
+ *               does not make any sense (that would result in loss of speed and no gain in quality)
+ */
+  public void setMipmap(float mipmap)
+    {
+    mMipmap = mipmap;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Set the (R,G,B,A) values of GLES31.glClearColor() to set up color with which to clear
+ * this Surface at the beginning of each frame.
+ *
+ * @param r the Red component. Default: 0.0f
+ * @param g the Green component. Default: 0.0f
+ * @param b the Blue component. Default: 0.0f
+ * @param a the Alpha component. Default: 0.0f
+ */
+  public void glClearColor(float r, float g, float b, float a)
+    {
+    mClearR = r;
+    mClearG = g;
+    mClearB = b;
+    mClearA = a;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Uses glClearDepthf() to set up a value with which to clear
+ * the Depth buffer of our Surface at the beginning of each frame.
+ *
+ * @param d the Depth. Default: 1.0f
+ */
+  public void glClearDepthf(float d)
+    {
+    mClearDepth = d;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Uses glClearStencil() to set up a value with which to clear the
+ * Stencil buffer of our Surface at the beginning of each frame.
+ *
+ * @param s the Stencil. Default: 0
+ */
+  public void glClearStencil(int s)
+    {
+    mClearStencil = s;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Which buffers to Clear at the beginning of each frame?
+ * <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: GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT.
+ *
+ * @param mask bitwise OR of BUFFER_BITs to clear.
+ */
+  public void glClear(int mask)
+    {
+    mClear = mask;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Create new Projection matrix.
+ *
+ * @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;
+      }
+
+    for(int j=0; j<EffectQuality.LENGTH; j++)
+      {
+      if( mBuffer[j]!=null ) mBuffer[j].mNear = mNear;
+      }
+
+    createProjection();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the vertical field of view angle.
+ *
+ * @return Vertival Field of View Angle, in degrees.
+ */
+  public float getFOV()
+    {
+    return mFOV;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Resize the underlying Framebuffer.
+ * <p>
+ * This method can be safely called mid-render as it doesn't interfere with rendering.
+ *
+ * @param width The new width.
+ * @param height The new height.
+ */
+  public void resize(int width, int height)
+    {
+    if( mWidth!=width || mHeight!=height )
+      {
+      mWidth = mRealWidth = width;
+      mHeight= mRealHeight= height;
+
+      createProjection();
+
+      if( mColorCreated==CREATED )
+        {
+        markForCreation();
+        recreate();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return true if the Surface contains a DEPTH attachment.
+ *
+ * @return <bold>true</bold> if the Surface contains a DEPTH attachment.
+ */
+  public boolean hasDepth()
+    {
+    return mDepthStencilCreated==CREATED;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return true if the Surface contains a STENCIL attachment.
+ *
+ * @return <bold>true</bold> if the Surface contains a STENCIL attachment.
+ */
+  public boolean hasStencil()
+    {
+    return (mDepthStencilCreated==CREATED && mDepthStencil==BOTH_DEPTH_STENCIL);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, should we use the Order Independent Transparency render mode?
+ * <p>
+ * This feature requires OpenGL ES 3.1. If we are running on OpenGL 3.0, this will do nothing.
+ * Also, if you are running on a buggy driver ( Imagination GE8100/8300 driver build 1.8@4490469 )
+ * then do nothing.
+ *
+ * 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)
+    {
+    if( DistortedLibrary.getGLSL()>=310 )
+      {
+      mRenderWayOIT = oit;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, should we use the Order Independent Transparency render mode?
+ * <p>
+ * This feature requires OpenGL ES 3.1. If we are running on OpenGL 3.0, this will do nothing.
+ * Also, if you are running on a buggy driver ( Imagination GE8100/8300 driver build 1.8@4490469 )
+ * then do nothing.
+ *
+ * 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 screenfuls.
+ *                    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)
+    {
+    if( DistortedLibrary.getGLSL()>=310 )
+      {
+      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 Surface'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 Surface'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 Surface.
+ * <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 the first occurrence of a specified child from the list of children of our Surface.
+ * <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 Node to remove.
+ */
+  public void detach(DistortedNode node)
+    {
+    mChildren.detach(node);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Removes all children Nodes.
+ * <p>
+ * We cannot do this mid-render - actual attachment will be done just before the next render, by the
+ * InternalMaster (by calling doWork())
+ */
+  public void detachAll()
+    {
+    mChildren.detachAll();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the width of this Surface.
+ *
+ * @return width of the Object, in pixels.
+ */
+  public int getWidth()
+    {
+    return mWidth;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return the height of this Surface.
+ *
+ * @return height of the Object, in pixels.
+ */
+  public int getHeight()
+    {
+    return mHeight;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/library/main/InternalRenderState.java b/src/main/java/org/distorted/library/main/InternalRenderState.java
deleted file mode 100644
index ecaab47..0000000
--- a/src/main/java/org/distorted/library/main/InternalRenderState.java
+++ /dev/null
@@ -1,868 +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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Remember the OpenGL state.
- * <p>
- * This is a member of DistortedNode. Remembers the OpenGL state we want to set just before rendering
- * the Node.
- * <p>
- * Only for use by the library itself.
- *
- * @y.exclude
- */
-public class InternalRenderState
-{
-  // TODO: figure this out dynamically; this assumes 8 bit stencil buffer.
-  private static final int STENCIL_MASK = (1<<8)-1;
-
-  private static class RenderState
-    {
-    private int colorMaskR, colorMaskG, colorMaskB, colorMaskA;
-    private int depthMask;
-    private int stencilMask;
-    private int depthTest;
-    private int stencilTest;
-    private int stencilFuncFunc, stencilFuncRef, stencilFuncMask;
-    private int stencilOpSfail, stencilOpDpfail, stencilOpDppass;
-    private int depthFunc;
-    private int blend;
-    private int blendSrc, blendDst;
-    }
-
-  private final RenderState mState;          // state the current object wants to have
-  static private final RenderState cState = new RenderState();   // current OpenGL Stave
-  static private final RenderState sState = new RenderState();   // saved OpenGL state
-
-  private int mClear;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// default: color writes on, depth test and writes on, blending on, stencil off.
-
-  InternalRenderState()
-    {
-    mState = new RenderState();
-
-    mState.colorMaskR = 1;
-    mState.colorMaskG = 1;
-    mState.colorMaskB = 1;
-    mState.colorMaskA = 1;
-
-    mState.depthTest  = 1;
-    mState.depthMask  = 1;
-    mState.depthFunc  = GLES30.GL_LEQUAL;
-
-    mState.blend      = 1;
-    mState.blendSrc   = GLES30.GL_SRC_ALPHA;
-    mState.blendDst   = GLES30.GL_ONE_MINUS_SRC_ALPHA;
-
-    mState.stencilTest     = 0;
-    mState.stencilMask     = STENCIL_MASK;
-    mState.stencilFuncFunc = GLES30.GL_NEVER;
-    mState.stencilFuncRef  = 0;
-    mState.stencilFuncMask = STENCIL_MASK;
-    mState.stencilOpSfail  = GLES30.GL_KEEP;
-    mState.stencilOpDpfail = GLES30.GL_KEEP;
-    mState.stencilOpDppass = GLES30.GL_KEEP;
-
-    mClear = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// reset state of everything to a known state.
-
-  static void reset()
-    {
-    cState.colorMaskR = 1;
-    cState.colorMaskG = 1;
-    cState.colorMaskB = 1;
-    cState.colorMaskA = 1;
-    GLES30.glColorMask(true,true,true,true);
-
-    cState.depthTest = 1;
-    cState.depthMask = 1;
-    cState.depthFunc = GLES30.GL_LEQUAL;
-    GLES30.glEnable(GLES30.GL_DEPTH_TEST);
-    GLES30.glDepthMask(true);
-    GLES30.glDepthFunc(cState.depthFunc);
-
-    cState.stencilTest     = 0;
-    cState.stencilMask     = STENCIL_MASK;
-    cState.stencilFuncFunc = GLES30.GL_NEVER;
-    cState.stencilFuncRef  = 0;
-    cState.stencilFuncMask = STENCIL_MASK;
-    cState.stencilOpSfail  = GLES30.GL_KEEP;
-    cState.stencilOpDpfail = GLES30.GL_KEEP;
-    cState.stencilOpDppass = GLES30.GL_KEEP;
-    GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-    GLES30.glStencilMask(cState.stencilMask);
-
-    cState.blend      = 1;
-    cState.blendSrc   = GLES30.GL_SRC_ALPHA;
-    cState.blendDst   = GLES30.GL_ONE_MINUS_SRC_ALPHA;
-    GLES30.glEnable(GLES30.GL_BLEND);
-    GLES30.glBlendFunc(cState.blendSrc,cState.blendDst);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void colorDepthStencilOn()
-    {
-    sState.colorMaskR = cState.colorMaskR;
-    sState.colorMaskG = cState.colorMaskG;
-    sState.colorMaskB = cState.colorMaskB;
-    sState.colorMaskA = cState.colorMaskA;
-
-    if( cState.colorMaskR!=1 || cState.colorMaskG!=1 || cState.colorMaskB!=1 || cState.colorMaskA!=1 )
-      {
-      cState.colorMaskR = 1;
-      cState.colorMaskG = 1;
-      cState.colorMaskB = 1;
-      cState.colorMaskA = 1;
-      GLES30.glColorMask(true,true,true,true);
-      }
-
-    sState.depthMask = cState.depthMask;
-
-    if( cState.depthMask!=1 )
-      {
-      cState.depthMask = 1;
-      GLES30.glDepthMask(true);
-      }
-
-    sState.stencilMask = cState.stencilMask;
-
-    if( cState.stencilMask!= STENCIL_MASK )
-      {
-      cState.stencilMask = STENCIL_MASK;
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void colorDepthStencilRestore()
-    {
-    if( sState.colorMaskR!=cState.colorMaskR || sState.colorMaskG!=cState.colorMaskG || sState.colorMaskB!=cState.colorMaskB || sState.colorMaskA!=cState.colorMaskA)
-      {
-      cState.colorMaskR = sState.colorMaskR;
-      cState.colorMaskG = sState.colorMaskG;
-      cState.colorMaskB = sState.colorMaskB;
-      cState.colorMaskA = sState.colorMaskA;
-      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
-      }
-    if( sState.depthMask!=cState.depthMask )
-      {
-      cState.depthMask = sState.depthMask;
-      GLES30.glDepthMask(cState.depthMask==1);
-      }
-    if( sState.stencilMask!=cState.stencilMask )
-      {
-      cState.stencilMask = sState.stencilMask;
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void disableBlending()
-    {
-    sState.blend = cState.blend;
-
-    if (cState.blend != 0)
-      {
-      cState.blend = 0;
-      GLES30.glDisable(GLES30.GL_BLEND);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void restoreBlending()
-    {
-    if (sState.blend != cState.blend)
-      {
-      cState.blend = sState.blend;
-
-      if (cState.blend == 0)
-        {
-        GLES30.glDisable(GLES30.GL_BLEND);
-        }
-      else
-        {
-        GLES30.glEnable(GLES30.GL_BLEND);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void enableDepthTest()
-    {
-    sState.depthTest = cState.depthTest;
-
-    if (cState.depthTest != 1)
-      {
-      cState.depthTest = 1;
-      GLES30.glEnable(GLES30.GL_DEPTH_TEST);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void restoreDepthTest()
-    {
-    if (sState.depthTest != cState.depthTest)
-      {
-      cState.depthTest = sState.depthTest;
-
-      if (cState.depthTest == 0)
-        {
-        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-        }
-      else
-        {
-        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void switchOffDrawing()
-    {
-    GLES30.glEnable(GLES30.GL_SCISSOR_TEST);
-    GLES30.glScissor(0,0,0,0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void restoreDrawing()
-    {
-    GLES30.glDisable(GLES30.GL_SCISSOR_TEST);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void switchOffColorDepthStencil()
-    {
-    sState.stencilTest = cState.stencilTest;
-
-    if( cState.stencilTest!=0 )
-      {
-      cState.stencilTest = 0;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil test off");
-      GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-      }
-
-    sState.depthTest = cState.depthTest;
-
-    if( cState.depthTest!=0 )
-      {
-      cState.depthTest = 0;
-      //DistortedLibrary.logMessage("InternalRenderState: depth test off");
-      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-      }
-
-    sState.colorMaskR = cState.colorMaskR;
-    sState.colorMaskG = cState.colorMaskG;
-    sState.colorMaskB = cState.colorMaskB;
-    sState.colorMaskA = cState.colorMaskA;
-
-    if( cState.colorMaskR!=0 || cState.colorMaskG!=0 || cState.colorMaskB!=0 || cState.colorMaskA!=0 )
-      {
-      cState.colorMaskR = 0;
-      cState.colorMaskG = 0;
-      cState.colorMaskB = 0;
-      cState.colorMaskA = 0;
-      //DistortedLibrary.logMessage("InternalRenderState: switch off color writing");
-      GLES30.glColorMask(false,false,false,false);
-      }
-
-    sState.depthMask = cState.depthMask;
-
-    if( cState.depthMask!=0 )
-      {
-      cState.depthMask = 0;
-      //DistortedLibrary.logMessage("InternalRenderState: switch off depth writing");
-      GLES30.glDepthMask(false);
-      }
-
-    sState.stencilMask = cState.stencilMask;
-
-    if( cState.stencilMask!= 0x00 )
-      {
-      cState.stencilMask = 0x00;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil mask off");
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void switchColorDepthOnStencilOff()
-    {
-    sState.stencilTest = cState.stencilTest;
-
-    if( cState.stencilTest!=0 )
-      {
-      cState.stencilTest = 0;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil test off");
-      GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-      }
-
-    sState.depthTest = cState.depthTest;
-
-    if( cState.depthTest!=0 )
-      {
-      cState.depthTest = 0;
-      //DistortedLibrary.logMessage("InternalRenderState: depth test off");
-      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-      }
-
-    sState.colorMaskR = cState.colorMaskR;
-    sState.colorMaskG = cState.colorMaskG;
-    sState.colorMaskB = cState.colorMaskB;
-    sState.colorMaskA = cState.colorMaskA;
-
-    if( cState.colorMaskR!=1 || cState.colorMaskG!=1 || cState.colorMaskB!=1 || cState.colorMaskA!=1 )
-      {
-      cState.colorMaskR = 1;
-      cState.colorMaskG = 1;
-      cState.colorMaskB = 1;
-      cState.colorMaskA = 1;
-      //DistortedLibrary.logMessage("InternalRenderState: switch on color writing");
-      GLES30.glColorMask(true,true,true,true);
-      }
-
-    sState.depthMask = cState.depthMask;
-
-    if( cState.depthMask!=1 )
-      {
-      cState.depthMask = 1;
-      //DistortedLibrary.logMessage("InternalRenderState: switch on depth writing");
-      GLES30.glDepthMask(true);
-      }
-
-    sState.stencilMask = cState.stencilMask;
-
-    if( cState.stencilMask!= 0x00 )
-      {
-      cState.stencilMask = 0x00;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil mask off");
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void restoreColorDepthStencil()
-    {
-    if( sState.stencilTest!=cState.stencilTest )
-      {
-      cState.stencilTest = sState.stencilTest;
-
-      if (cState.stencilTest == 0)
-        {
-        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-        }
-      else
-        {
-        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
-        }
-      }
-    if( sState.depthTest!=cState.depthTest )
-      {
-      cState.depthTest = sState.depthTest;
-
-      if (cState.depthTest == 0)
-        {
-        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-        }
-      else
-        {
-        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
-        }
-      }
-    if( sState.colorMaskR!=cState.colorMaskR || sState.colorMaskG!=cState.colorMaskG || sState.colorMaskB!=cState.colorMaskB || sState.colorMaskA!=cState.colorMaskA)
-      {
-      cState.colorMaskR = sState.colorMaskR;
-      cState.colorMaskG = sState.colorMaskG;
-      cState.colorMaskB = sState.colorMaskB;
-      cState.colorMaskA = sState.colorMaskA;
-      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
-      }
-    if( sState.depthMask!=cState.depthMask )
-      {
-      cState.depthMask = sState.depthMask;
-      GLES30.glDepthMask(cState.depthMask==1);
-      }
-    if( sState.stencilMask!=cState.stencilMask )
-      {
-      cState.stencilMask = sState.stencilMask;
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void setUpStencilMark(boolean color)
-    {
-    sState.stencilTest = cState.stencilTest;
-
-    if( cState.stencilTest!=1 )
-      {
-      cState.stencilTest = 1;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil test on");
-      GLES30.glEnable(GLES30.GL_STENCIL_TEST);
-      }
-
-    sState.stencilFuncFunc = cState.stencilFuncFunc;
-    sState.stencilFuncRef  = cState.stencilFuncRef;
-    sState.stencilFuncMask = cState.stencilFuncMask;
-
-    if( cState.stencilFuncFunc!=GLES30.GL_ALWAYS || cState.stencilFuncRef!=1 || cState.stencilFuncMask!=STENCIL_MASK )
-      {
-      cState.stencilFuncFunc = GLES30.GL_ALWAYS;
-      cState.stencilFuncRef  = 1;
-      cState.stencilFuncMask = STENCIL_MASK;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil func on");
-      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
-      }
-
-    sState.stencilOpSfail = cState.stencilOpSfail;
-    sState.stencilOpDpfail= cState.stencilOpDpfail;
-    sState.stencilOpDppass= cState.stencilOpDppass;
-
-    if( cState.stencilOpSfail!=GLES30.GL_KEEP || cState.stencilOpDpfail!=GLES30.GL_KEEP || cState.stencilOpDppass!=GLES30.GL_REPLACE )
-      {
-      cState.stencilOpSfail = GLES30.GL_KEEP;
-      cState.stencilOpDpfail= GLES30.GL_KEEP;
-      cState.stencilOpDppass= GLES30.GL_REPLACE;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil op on");
-      GLES30.glStencilOp(cState.stencilOpSfail,cState.stencilOpDpfail,cState.stencilOpDppass);
-      }
-
-    sState.colorMaskR = cState.colorMaskR;
-    sState.colorMaskG = cState.colorMaskG;
-    sState.colorMaskB = cState.colorMaskB;
-    sState.colorMaskA = cState.colorMaskA;
-
-    int clr = color ? 1:0;
-
-    if( cState.colorMaskR!=clr || cState.colorMaskG!=clr || cState.colorMaskB!=clr || cState.colorMaskA!=clr )
-      {
-      cState.colorMaskR = clr;
-      cState.colorMaskG = clr;
-      cState.colorMaskB = clr;
-      cState.colorMaskA = clr;
-      //DistortedLibrary.logMessage("InternalRenderState: switch off color writing");
-      GLES30.glColorMask(color,color,color,color);
-      }
-
-    sState.depthMask = cState.depthMask;
-
-    if( cState.depthMask!=1 )
-      {
-      cState.depthMask = 1;
-      //DistortedLibrary.logMessage("InternalRenderState: switch on depth writing");
-      GLES30.glDepthMask(true);
-      }
-
-    sState.stencilMask = cState.stencilMask;
-
-    if( cState.stencilMask!= STENCIL_MASK )
-      {
-      cState.stencilMask = STENCIL_MASK;
-      //DistortedLibrary.logMessage("InternalRenderState: stencil mask on");
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void unsetUpStencilMark()
-    {
-    if( sState.stencilTest!=cState.stencilTest )
-      {
-      cState.stencilTest = sState.stencilTest;
-
-      if (cState.stencilTest == 0)
-        {
-        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-        }
-      else
-        {
-        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
-        }
-      }
-    if( sState.colorMaskR!=cState.colorMaskR || sState.colorMaskG!=cState.colorMaskG || sState.colorMaskB!=cState.colorMaskB || sState.colorMaskA!=cState.colorMaskA)
-      {
-      cState.colorMaskR = sState.colorMaskR;
-      cState.colorMaskG = sState.colorMaskG;
-      cState.colorMaskB = sState.colorMaskB;
-      cState.colorMaskA = sState.colorMaskA;
-      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
-      }
-    if( sState.depthMask!=cState.depthMask )
-      {
-      cState.depthMask = sState.depthMask;
-      GLES30.glDepthMask(cState.depthMask==1);
-      }
-    if( sState.stencilMask!=cState.stencilMask )
-      {
-      cState.stencilMask = sState.stencilMask;
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Only for use by the library itself.
- *
- * @y.exclude
- */
-  public static void useStencilMark()
-    {
-    sState.stencilMask = cState.stencilMask;
-
-    if( cState.stencilTest!=1 )
-      {
-      cState.stencilTest = 1;
-      GLES30.glEnable(GLES30.GL_STENCIL_TEST);
-      }
-
-    sState.stencilFuncFunc = cState.stencilFuncFunc;
-    sState.stencilFuncRef  = cState.stencilFuncRef;
-    sState.stencilFuncMask = cState.stencilFuncMask;
-
-    if( cState.stencilFuncFunc!=GLES30.GL_EQUAL || cState.stencilFuncRef!=1 || cState.stencilFuncMask!=STENCIL_MASK )
-      {
-      cState.stencilFuncFunc = GLES30.GL_EQUAL;
-      cState.stencilFuncRef  = 1;
-      cState.stencilFuncMask = STENCIL_MASK;
-      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
-      }
-
-    sState.stencilMask = cState.stencilMask;
-
-    if( cState.stencilMask!= 0x00 )
-      {
-      cState.stencilMask = 0x00;
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-
-    sState.depthMask = cState.depthMask;
-
-    if( cState.depthMask!=0 )
-      {
-      cState.depthMask = 0;
-      GLES30.glDepthMask(false);
-      }
-
-    sState.depthTest = cState.depthTest;
-
-    if( cState.depthTest!=0 )
-      {
-      cState.depthTest = 0;
-      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Only for use by the library itself.
- *
- * @y.exclude
- */
-  public static void unuseStencilMark()
-    {
-    if( sState.stencilTest!=cState.stencilTest )
-      {
-      cState.stencilTest = sState.stencilTest;
-
-      if (cState.stencilTest == 0)
-        {
-        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-        }
-      else
-        {
-        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
-        }
-      }
-    if( sState.stencilFuncFunc!=cState.stencilFuncFunc || sState.stencilFuncRef!=cState.stencilFuncRef || sState.stencilFuncMask!=cState.stencilFuncMask )
-      {
-      cState.stencilFuncFunc = sState.stencilFuncFunc;
-      cState.stencilFuncRef  = sState.stencilFuncRef ;
-      cState.stencilFuncMask = sState.stencilFuncMask;
-      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
-      }
-    if( sState.stencilMask!=cState.stencilMask )
-      {
-      cState.stencilMask = sState.stencilMask;
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-    if( sState.depthMask!=cState.depthMask )
-      {
-      cState.depthMask = sState.depthMask;
-      GLES30.glDepthMask(cState.depthMask==1);
-      }
-    if( sState.depthTest!=cState.depthTest )
-      {
-      cState.depthTest = sState.depthTest;
-
-      if (cState.depthTest == 0)
-        {
-        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-        }
-      else
-        {
-        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void apply()
-    {
-    //DistortedLibrary.logMessage("InternalRenderState: APPLYING STATE");
-
-    /////////////////////////////////////////////////////
-    // 1. Write to color buffer?
-    if( mState.colorMaskR!=cState.colorMaskR || mState.colorMaskG!=cState.colorMaskG || mState.colorMaskB!=cState.colorMaskB || mState.colorMaskA!=cState.colorMaskA)
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: setting color mask");
-      cState.colorMaskR = mState.colorMaskR;
-      cState.colorMaskG = mState.colorMaskG;
-      cState.colorMaskB = mState.colorMaskB;
-      cState.colorMaskA = mState.colorMaskA;
-      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
-      }
-
-    /////////////////////////////////////////////////////
-    // 2. Enable Depth test?
-    if( mState.depthTest!=cState.depthTest )
-      {
-      cState.depthTest = mState.depthTest;
-
-      if (cState.depthTest == 0)
-        {
-        //DistortedLibrary.logMessage("InternalRenderState: disabling depth test");
-        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
-        }
-      else
-        {
-        //DistortedLibrary.logMessage("InternalRenderState: enable depth test");
-        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
-        }
-      }
-
-    /////////////////////////////////////////////////////
-    // 3. Change Depth Function?
-    if( mState.depthFunc!=cState.depthFunc )
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: setting depth func");
-      cState.depthFunc = mState.depthFunc;
-      GLES30.glDepthFunc(cState.depthFunc);
-      }
-
-    /////////////////////////////////////////////////////
-    // 4. Write to Depth buffer?
-    if( mState.depthMask!=cState.depthMask )
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: setting depth mask");
-      cState.depthMask = mState.depthMask;
-      GLES30.glDepthMask(cState.depthMask==1);
-      }
-
-    /////////////////////////////////////////////////////
-    // 5. Enable Blending?
-    if( mState.blend!=cState.blend )
-      {
-      cState.blend = mState.blend;
-
-      if (cState.blend == 0)
-        {
-        //DistortedLibrary.logMessage("InternalRenderState: disabling blending");
-        GLES30.glDisable(GLES30.GL_BLEND);
-        }
-      else
-        {
-        //DistortedLibrary.logMessage("InternalRenderState: enabling blending");
-        GLES30.glEnable(GLES30.GL_BLEND);
-        }
-      }
-
-    /////////////////////////////////////////////////////
-    // 6. Change Blend function?
-    if( mState.blendSrc!=cState.blendSrc || mState.blendDst!=cState.blendDst )
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: setting blend function");
-      cState.blendSrc = mState.blendSrc;
-      cState.blendDst = mState.blendDst;
-      GLES30.glBlendFunc(cState.blendSrc,cState.blendDst);
-      }
-
-    /////////////////////////////////////////////////////
-    // 7. Enable/Disable Stencil Test?
-    if( mState.stencilTest!=cState.stencilTest )
-      {
-      cState.stencilTest = mState.stencilTest;
-
-      if (cState.stencilTest == 0)
-        {
-        //DistortedLibrary.logMessage("InternalRenderState: disabling stencil test");
-        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
-        }
-      else
-        {
-        //DistortedLibrary.logMessage("InternalRenderState: enabling stencil test");
-        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
-        }
-      }
-
-    /////////////////////////////////////////////////////
-    // 8. Adjust Stencil function?
-    if( mState.stencilFuncFunc!=cState.stencilFuncFunc || mState.stencilFuncRef!=cState.stencilFuncRef || mState.stencilFuncMask!=cState.stencilFuncMask )
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: setting stencil function");
-      cState.stencilFuncFunc = mState.stencilFuncFunc;
-      cState.stencilFuncRef  = mState.stencilFuncRef ;
-      cState.stencilFuncMask = mState.stencilFuncMask;
-      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
-      }
-
-    /////////////////////////////////////////////////////
-    // 9. Adjust Stencil operation?
-    if( mState.stencilOpSfail!=cState.stencilOpSfail || mState.stencilOpDpfail!=cState.stencilOpDpfail || mState.stencilOpDppass!=cState.stencilOpDppass )
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: setting stencil op");
-      cState.stencilOpSfail = mState.stencilOpSfail;
-      cState.stencilOpDpfail= mState.stencilOpDpfail;
-      cState.stencilOpDppass= mState.stencilOpDppass;
-      GLES30.glStencilOp(cState.stencilOpSfail,cState.stencilOpDpfail,cState.stencilOpDppass);
-      }
-
-    /////////////////////////////////////////////////////
-    // 10. Write to Stencil buffer?
-    if( mState.stencilMask!=cState.stencilMask )
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: setting stencil mask");
-      cState.stencilMask = mState.stencilMask;
-      GLES30.glStencilMask(cState.stencilMask);
-      }
-
-    /////////////////////////////////////////////////////
-    // 11. Clear buffers?
-    if( mClear!=0 )
-      {
-      //DistortedLibrary.logMessage("InternalRenderState: clearing buffer");
-      GLES30.glClear(mClear);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glColorMask(boolean r, boolean g, boolean b, boolean a)
-    {
-    mState.colorMaskR = (r ? 1:0);
-    mState.colorMaskG = (g ? 1:0);
-    mState.colorMaskB = (b ? 1:0);
-    mState.colorMaskA = (a ? 1:0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glDepthMask(boolean mask)
-    {
-    mState.depthMask = (mask ? 1:0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glStencilMask(int mask)
-    {
-    mState.stencilMask = mask;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glEnable(int test)
-    {
-         if( test==GLES30.GL_DEPTH_TEST   ) mState.depthTest   = 1;
-    else if( test==GLES30.GL_STENCIL_TEST ) mState.stencilTest = 1;
-    else if( test==GLES30.GL_BLEND        ) mState.blend       = 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glDisable(int test)
-    {
-         if( test==GLES30.GL_DEPTH_TEST   ) mState.depthTest   = 0;
-    else if( test==GLES30.GL_STENCIL_TEST ) mState.stencilTest = 0;
-    else if( test==GLES30.GL_BLEND        ) mState.blend       = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glStencilFunc(int func, int ref, int mask)
-    {
-    mState.stencilFuncFunc = func;
-    mState.stencilFuncRef  = ref;
-    mState.stencilFuncMask = mask;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glStencilOp(int sfail, int dpfail, int dppass)
-    {
-    mState.stencilOpSfail = sfail;
-    mState.stencilOpDpfail= dpfail;
-    mState.stencilOpDppass= dppass;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glDepthFunc(int func)
-    {
-    mState.depthFunc = func;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glBlendFunc(int src, int dst)
-    {
-    mState.blendSrc = src;
-    mState.blendDst = dst;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void glClear(int mask)
-    {
-    mClear = mask;
-    }
-}
diff --git a/src/main/java/org/distorted/library/main/InternalRenderState.kt b/src/main/java/org/distorted/library/main/InternalRenderState.kt
new file mode 100644
index 0000000..ecaab47
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalRenderState.kt
@@ -0,0 +1,868 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Remember the OpenGL state.
+ * <p>
+ * This is a member of DistortedNode. Remembers the OpenGL state we want to set just before rendering
+ * the Node.
+ * <p>
+ * Only for use by the library itself.
+ *
+ * @y.exclude
+ */
+public class InternalRenderState
+{
+  // TODO: figure this out dynamically; this assumes 8 bit stencil buffer.
+  private static final int STENCIL_MASK = (1<<8)-1;
+
+  private static class RenderState
+    {
+    private int colorMaskR, colorMaskG, colorMaskB, colorMaskA;
+    private int depthMask;
+    private int stencilMask;
+    private int depthTest;
+    private int stencilTest;
+    private int stencilFuncFunc, stencilFuncRef, stencilFuncMask;
+    private int stencilOpSfail, stencilOpDpfail, stencilOpDppass;
+    private int depthFunc;
+    private int blend;
+    private int blendSrc, blendDst;
+    }
+
+  private final RenderState mState;          // state the current object wants to have
+  static private final RenderState cState = new RenderState();   // current OpenGL Stave
+  static private final RenderState sState = new RenderState();   // saved OpenGL state
+
+  private int mClear;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// default: color writes on, depth test and writes on, blending on, stencil off.
+
+  InternalRenderState()
+    {
+    mState = new RenderState();
+
+    mState.colorMaskR = 1;
+    mState.colorMaskG = 1;
+    mState.colorMaskB = 1;
+    mState.colorMaskA = 1;
+
+    mState.depthTest  = 1;
+    mState.depthMask  = 1;
+    mState.depthFunc  = GLES30.GL_LEQUAL;
+
+    mState.blend      = 1;
+    mState.blendSrc   = GLES30.GL_SRC_ALPHA;
+    mState.blendDst   = GLES30.GL_ONE_MINUS_SRC_ALPHA;
+
+    mState.stencilTest     = 0;
+    mState.stencilMask     = STENCIL_MASK;
+    mState.stencilFuncFunc = GLES30.GL_NEVER;
+    mState.stencilFuncRef  = 0;
+    mState.stencilFuncMask = STENCIL_MASK;
+    mState.stencilOpSfail  = GLES30.GL_KEEP;
+    mState.stencilOpDpfail = GLES30.GL_KEEP;
+    mState.stencilOpDppass = GLES30.GL_KEEP;
+
+    mClear = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// reset state of everything to a known state.
+
+  static void reset()
+    {
+    cState.colorMaskR = 1;
+    cState.colorMaskG = 1;
+    cState.colorMaskB = 1;
+    cState.colorMaskA = 1;
+    GLES30.glColorMask(true,true,true,true);
+
+    cState.depthTest = 1;
+    cState.depthMask = 1;
+    cState.depthFunc = GLES30.GL_LEQUAL;
+    GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+    GLES30.glDepthMask(true);
+    GLES30.glDepthFunc(cState.depthFunc);
+
+    cState.stencilTest     = 0;
+    cState.stencilMask     = STENCIL_MASK;
+    cState.stencilFuncFunc = GLES30.GL_NEVER;
+    cState.stencilFuncRef  = 0;
+    cState.stencilFuncMask = STENCIL_MASK;
+    cState.stencilOpSfail  = GLES30.GL_KEEP;
+    cState.stencilOpDpfail = GLES30.GL_KEEP;
+    cState.stencilOpDppass = GLES30.GL_KEEP;
+    GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+    GLES30.glStencilMask(cState.stencilMask);
+
+    cState.blend      = 1;
+    cState.blendSrc   = GLES30.GL_SRC_ALPHA;
+    cState.blendDst   = GLES30.GL_ONE_MINUS_SRC_ALPHA;
+    GLES30.glEnable(GLES30.GL_BLEND);
+    GLES30.glBlendFunc(cState.blendSrc,cState.blendDst);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void colorDepthStencilOn()
+    {
+    sState.colorMaskR = cState.colorMaskR;
+    sState.colorMaskG = cState.colorMaskG;
+    sState.colorMaskB = cState.colorMaskB;
+    sState.colorMaskA = cState.colorMaskA;
+
+    if( cState.colorMaskR!=1 || cState.colorMaskG!=1 || cState.colorMaskB!=1 || cState.colorMaskA!=1 )
+      {
+      cState.colorMaskR = 1;
+      cState.colorMaskG = 1;
+      cState.colorMaskB = 1;
+      cState.colorMaskA = 1;
+      GLES30.glColorMask(true,true,true,true);
+      }
+
+    sState.depthMask = cState.depthMask;
+
+    if( cState.depthMask!=1 )
+      {
+      cState.depthMask = 1;
+      GLES30.glDepthMask(true);
+      }
+
+    sState.stencilMask = cState.stencilMask;
+
+    if( cState.stencilMask!= STENCIL_MASK )
+      {
+      cState.stencilMask = STENCIL_MASK;
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void colorDepthStencilRestore()
+    {
+    if( sState.colorMaskR!=cState.colorMaskR || sState.colorMaskG!=cState.colorMaskG || sState.colorMaskB!=cState.colorMaskB || sState.colorMaskA!=cState.colorMaskA)
+      {
+      cState.colorMaskR = sState.colorMaskR;
+      cState.colorMaskG = sState.colorMaskG;
+      cState.colorMaskB = sState.colorMaskB;
+      cState.colorMaskA = sState.colorMaskA;
+      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
+      }
+    if( sState.depthMask!=cState.depthMask )
+      {
+      cState.depthMask = sState.depthMask;
+      GLES30.glDepthMask(cState.depthMask==1);
+      }
+    if( sState.stencilMask!=cState.stencilMask )
+      {
+      cState.stencilMask = sState.stencilMask;
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void disableBlending()
+    {
+    sState.blend = cState.blend;
+
+    if (cState.blend != 0)
+      {
+      cState.blend = 0;
+      GLES30.glDisable(GLES30.GL_BLEND);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void restoreBlending()
+    {
+    if (sState.blend != cState.blend)
+      {
+      cState.blend = sState.blend;
+
+      if (cState.blend == 0)
+        {
+        GLES30.glDisable(GLES30.GL_BLEND);
+        }
+      else
+        {
+        GLES30.glEnable(GLES30.GL_BLEND);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void enableDepthTest()
+    {
+    sState.depthTest = cState.depthTest;
+
+    if (cState.depthTest != 1)
+      {
+      cState.depthTest = 1;
+      GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void restoreDepthTest()
+    {
+    if (sState.depthTest != cState.depthTest)
+      {
+      cState.depthTest = sState.depthTest;
+
+      if (cState.depthTest == 0)
+        {
+        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+        }
+      else
+        {
+        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void switchOffDrawing()
+    {
+    GLES30.glEnable(GLES30.GL_SCISSOR_TEST);
+    GLES30.glScissor(0,0,0,0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void restoreDrawing()
+    {
+    GLES30.glDisable(GLES30.GL_SCISSOR_TEST);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void switchOffColorDepthStencil()
+    {
+    sState.stencilTest = cState.stencilTest;
+
+    if( cState.stencilTest!=0 )
+      {
+      cState.stencilTest = 0;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil test off");
+      GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+      }
+
+    sState.depthTest = cState.depthTest;
+
+    if( cState.depthTest!=0 )
+      {
+      cState.depthTest = 0;
+      //DistortedLibrary.logMessage("InternalRenderState: depth test off");
+      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+      }
+
+    sState.colorMaskR = cState.colorMaskR;
+    sState.colorMaskG = cState.colorMaskG;
+    sState.colorMaskB = cState.colorMaskB;
+    sState.colorMaskA = cState.colorMaskA;
+
+    if( cState.colorMaskR!=0 || cState.colorMaskG!=0 || cState.colorMaskB!=0 || cState.colorMaskA!=0 )
+      {
+      cState.colorMaskR = 0;
+      cState.colorMaskG = 0;
+      cState.colorMaskB = 0;
+      cState.colorMaskA = 0;
+      //DistortedLibrary.logMessage("InternalRenderState: switch off color writing");
+      GLES30.glColorMask(false,false,false,false);
+      }
+
+    sState.depthMask = cState.depthMask;
+
+    if( cState.depthMask!=0 )
+      {
+      cState.depthMask = 0;
+      //DistortedLibrary.logMessage("InternalRenderState: switch off depth writing");
+      GLES30.glDepthMask(false);
+      }
+
+    sState.stencilMask = cState.stencilMask;
+
+    if( cState.stencilMask!= 0x00 )
+      {
+      cState.stencilMask = 0x00;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil mask off");
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void switchColorDepthOnStencilOff()
+    {
+    sState.stencilTest = cState.stencilTest;
+
+    if( cState.stencilTest!=0 )
+      {
+      cState.stencilTest = 0;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil test off");
+      GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+      }
+
+    sState.depthTest = cState.depthTest;
+
+    if( cState.depthTest!=0 )
+      {
+      cState.depthTest = 0;
+      //DistortedLibrary.logMessage("InternalRenderState: depth test off");
+      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+      }
+
+    sState.colorMaskR = cState.colorMaskR;
+    sState.colorMaskG = cState.colorMaskG;
+    sState.colorMaskB = cState.colorMaskB;
+    sState.colorMaskA = cState.colorMaskA;
+
+    if( cState.colorMaskR!=1 || cState.colorMaskG!=1 || cState.colorMaskB!=1 || cState.colorMaskA!=1 )
+      {
+      cState.colorMaskR = 1;
+      cState.colorMaskG = 1;
+      cState.colorMaskB = 1;
+      cState.colorMaskA = 1;
+      //DistortedLibrary.logMessage("InternalRenderState: switch on color writing");
+      GLES30.glColorMask(true,true,true,true);
+      }
+
+    sState.depthMask = cState.depthMask;
+
+    if( cState.depthMask!=1 )
+      {
+      cState.depthMask = 1;
+      //DistortedLibrary.logMessage("InternalRenderState: switch on depth writing");
+      GLES30.glDepthMask(true);
+      }
+
+    sState.stencilMask = cState.stencilMask;
+
+    if( cState.stencilMask!= 0x00 )
+      {
+      cState.stencilMask = 0x00;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil mask off");
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void restoreColorDepthStencil()
+    {
+    if( sState.stencilTest!=cState.stencilTest )
+      {
+      cState.stencilTest = sState.stencilTest;
+
+      if (cState.stencilTest == 0)
+        {
+        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+        }
+      else
+        {
+        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
+        }
+      }
+    if( sState.depthTest!=cState.depthTest )
+      {
+      cState.depthTest = sState.depthTest;
+
+      if (cState.depthTest == 0)
+        {
+        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+        }
+      else
+        {
+        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+        }
+      }
+    if( sState.colorMaskR!=cState.colorMaskR || sState.colorMaskG!=cState.colorMaskG || sState.colorMaskB!=cState.colorMaskB || sState.colorMaskA!=cState.colorMaskA)
+      {
+      cState.colorMaskR = sState.colorMaskR;
+      cState.colorMaskG = sState.colorMaskG;
+      cState.colorMaskB = sState.colorMaskB;
+      cState.colorMaskA = sState.colorMaskA;
+      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
+      }
+    if( sState.depthMask!=cState.depthMask )
+      {
+      cState.depthMask = sState.depthMask;
+      GLES30.glDepthMask(cState.depthMask==1);
+      }
+    if( sState.stencilMask!=cState.stencilMask )
+      {
+      cState.stencilMask = sState.stencilMask;
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void setUpStencilMark(boolean color)
+    {
+    sState.stencilTest = cState.stencilTest;
+
+    if( cState.stencilTest!=1 )
+      {
+      cState.stencilTest = 1;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil test on");
+      GLES30.glEnable(GLES30.GL_STENCIL_TEST);
+      }
+
+    sState.stencilFuncFunc = cState.stencilFuncFunc;
+    sState.stencilFuncRef  = cState.stencilFuncRef;
+    sState.stencilFuncMask = cState.stencilFuncMask;
+
+    if( cState.stencilFuncFunc!=GLES30.GL_ALWAYS || cState.stencilFuncRef!=1 || cState.stencilFuncMask!=STENCIL_MASK )
+      {
+      cState.stencilFuncFunc = GLES30.GL_ALWAYS;
+      cState.stencilFuncRef  = 1;
+      cState.stencilFuncMask = STENCIL_MASK;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil func on");
+      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
+      }
+
+    sState.stencilOpSfail = cState.stencilOpSfail;
+    sState.stencilOpDpfail= cState.stencilOpDpfail;
+    sState.stencilOpDppass= cState.stencilOpDppass;
+
+    if( cState.stencilOpSfail!=GLES30.GL_KEEP || cState.stencilOpDpfail!=GLES30.GL_KEEP || cState.stencilOpDppass!=GLES30.GL_REPLACE )
+      {
+      cState.stencilOpSfail = GLES30.GL_KEEP;
+      cState.stencilOpDpfail= GLES30.GL_KEEP;
+      cState.stencilOpDppass= GLES30.GL_REPLACE;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil op on");
+      GLES30.glStencilOp(cState.stencilOpSfail,cState.stencilOpDpfail,cState.stencilOpDppass);
+      }
+
+    sState.colorMaskR = cState.colorMaskR;
+    sState.colorMaskG = cState.colorMaskG;
+    sState.colorMaskB = cState.colorMaskB;
+    sState.colorMaskA = cState.colorMaskA;
+
+    int clr = color ? 1:0;
+
+    if( cState.colorMaskR!=clr || cState.colorMaskG!=clr || cState.colorMaskB!=clr || cState.colorMaskA!=clr )
+      {
+      cState.colorMaskR = clr;
+      cState.colorMaskG = clr;
+      cState.colorMaskB = clr;
+      cState.colorMaskA = clr;
+      //DistortedLibrary.logMessage("InternalRenderState: switch off color writing");
+      GLES30.glColorMask(color,color,color,color);
+      }
+
+    sState.depthMask = cState.depthMask;
+
+    if( cState.depthMask!=1 )
+      {
+      cState.depthMask = 1;
+      //DistortedLibrary.logMessage("InternalRenderState: switch on depth writing");
+      GLES30.glDepthMask(true);
+      }
+
+    sState.stencilMask = cState.stencilMask;
+
+    if( cState.stencilMask!= STENCIL_MASK )
+      {
+      cState.stencilMask = STENCIL_MASK;
+      //DistortedLibrary.logMessage("InternalRenderState: stencil mask on");
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void unsetUpStencilMark()
+    {
+    if( sState.stencilTest!=cState.stencilTest )
+      {
+      cState.stencilTest = sState.stencilTest;
+
+      if (cState.stencilTest == 0)
+        {
+        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+        }
+      else
+        {
+        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
+        }
+      }
+    if( sState.colorMaskR!=cState.colorMaskR || sState.colorMaskG!=cState.colorMaskG || sState.colorMaskB!=cState.colorMaskB || sState.colorMaskA!=cState.colorMaskA)
+      {
+      cState.colorMaskR = sState.colorMaskR;
+      cState.colorMaskG = sState.colorMaskG;
+      cState.colorMaskB = sState.colorMaskB;
+      cState.colorMaskA = sState.colorMaskA;
+      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
+      }
+    if( sState.depthMask!=cState.depthMask )
+      {
+      cState.depthMask = sState.depthMask;
+      GLES30.glDepthMask(cState.depthMask==1);
+      }
+    if( sState.stencilMask!=cState.stencilMask )
+      {
+      cState.stencilMask = sState.stencilMask;
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Only for use by the library itself.
+ *
+ * @y.exclude
+ */
+  public static void useStencilMark()
+    {
+    sState.stencilMask = cState.stencilMask;
+
+    if( cState.stencilTest!=1 )
+      {
+      cState.stencilTest = 1;
+      GLES30.glEnable(GLES30.GL_STENCIL_TEST);
+      }
+
+    sState.stencilFuncFunc = cState.stencilFuncFunc;
+    sState.stencilFuncRef  = cState.stencilFuncRef;
+    sState.stencilFuncMask = cState.stencilFuncMask;
+
+    if( cState.stencilFuncFunc!=GLES30.GL_EQUAL || cState.stencilFuncRef!=1 || cState.stencilFuncMask!=STENCIL_MASK )
+      {
+      cState.stencilFuncFunc = GLES30.GL_EQUAL;
+      cState.stencilFuncRef  = 1;
+      cState.stencilFuncMask = STENCIL_MASK;
+      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
+      }
+
+    sState.stencilMask = cState.stencilMask;
+
+    if( cState.stencilMask!= 0x00 )
+      {
+      cState.stencilMask = 0x00;
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+
+    sState.depthMask = cState.depthMask;
+
+    if( cState.depthMask!=0 )
+      {
+      cState.depthMask = 0;
+      GLES30.glDepthMask(false);
+      }
+
+    sState.depthTest = cState.depthTest;
+
+    if( cState.depthTest!=0 )
+      {
+      cState.depthTest = 0;
+      GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Only for use by the library itself.
+ *
+ * @y.exclude
+ */
+  public static void unuseStencilMark()
+    {
+    if( sState.stencilTest!=cState.stencilTest )
+      {
+      cState.stencilTest = sState.stencilTest;
+
+      if (cState.stencilTest == 0)
+        {
+        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+        }
+      else
+        {
+        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
+        }
+      }
+    if( sState.stencilFuncFunc!=cState.stencilFuncFunc || sState.stencilFuncRef!=cState.stencilFuncRef || sState.stencilFuncMask!=cState.stencilFuncMask )
+      {
+      cState.stencilFuncFunc = sState.stencilFuncFunc;
+      cState.stencilFuncRef  = sState.stencilFuncRef ;
+      cState.stencilFuncMask = sState.stencilFuncMask;
+      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
+      }
+    if( sState.stencilMask!=cState.stencilMask )
+      {
+      cState.stencilMask = sState.stencilMask;
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+    if( sState.depthMask!=cState.depthMask )
+      {
+      cState.depthMask = sState.depthMask;
+      GLES30.glDepthMask(cState.depthMask==1);
+      }
+    if( sState.depthTest!=cState.depthTest )
+      {
+      cState.depthTest = sState.depthTest;
+
+      if (cState.depthTest == 0)
+        {
+        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+        }
+      else
+        {
+        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void apply()
+    {
+    //DistortedLibrary.logMessage("InternalRenderState: APPLYING STATE");
+
+    /////////////////////////////////////////////////////
+    // 1. Write to color buffer?
+    if( mState.colorMaskR!=cState.colorMaskR || mState.colorMaskG!=cState.colorMaskG || mState.colorMaskB!=cState.colorMaskB || mState.colorMaskA!=cState.colorMaskA)
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: setting color mask");
+      cState.colorMaskR = mState.colorMaskR;
+      cState.colorMaskG = mState.colorMaskG;
+      cState.colorMaskB = mState.colorMaskB;
+      cState.colorMaskA = mState.colorMaskA;
+      GLES30.glColorMask(cState.colorMaskR==1,cState.colorMaskG==1,cState.colorMaskB==1,cState.colorMaskA==1);
+      }
+
+    /////////////////////////////////////////////////////
+    // 2. Enable Depth test?
+    if( mState.depthTest!=cState.depthTest )
+      {
+      cState.depthTest = mState.depthTest;
+
+      if (cState.depthTest == 0)
+        {
+        //DistortedLibrary.logMessage("InternalRenderState: disabling depth test");
+        GLES30.glDisable(GLES30.GL_DEPTH_TEST);
+        }
+      else
+        {
+        //DistortedLibrary.logMessage("InternalRenderState: enable depth test");
+        GLES30.glEnable(GLES30.GL_DEPTH_TEST);
+        }
+      }
+
+    /////////////////////////////////////////////////////
+    // 3. Change Depth Function?
+    if( mState.depthFunc!=cState.depthFunc )
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: setting depth func");
+      cState.depthFunc = mState.depthFunc;
+      GLES30.glDepthFunc(cState.depthFunc);
+      }
+
+    /////////////////////////////////////////////////////
+    // 4. Write to Depth buffer?
+    if( mState.depthMask!=cState.depthMask )
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: setting depth mask");
+      cState.depthMask = mState.depthMask;
+      GLES30.glDepthMask(cState.depthMask==1);
+      }
+
+    /////////////////////////////////////////////////////
+    // 5. Enable Blending?
+    if( mState.blend!=cState.blend )
+      {
+      cState.blend = mState.blend;
+
+      if (cState.blend == 0)
+        {
+        //DistortedLibrary.logMessage("InternalRenderState: disabling blending");
+        GLES30.glDisable(GLES30.GL_BLEND);
+        }
+      else
+        {
+        //DistortedLibrary.logMessage("InternalRenderState: enabling blending");
+        GLES30.glEnable(GLES30.GL_BLEND);
+        }
+      }
+
+    /////////////////////////////////////////////////////
+    // 6. Change Blend function?
+    if( mState.blendSrc!=cState.blendSrc || mState.blendDst!=cState.blendDst )
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: setting blend function");
+      cState.blendSrc = mState.blendSrc;
+      cState.blendDst = mState.blendDst;
+      GLES30.glBlendFunc(cState.blendSrc,cState.blendDst);
+      }
+
+    /////////////////////////////////////////////////////
+    // 7. Enable/Disable Stencil Test?
+    if( mState.stencilTest!=cState.stencilTest )
+      {
+      cState.stencilTest = mState.stencilTest;
+
+      if (cState.stencilTest == 0)
+        {
+        //DistortedLibrary.logMessage("InternalRenderState: disabling stencil test");
+        GLES30.glDisable(GLES30.GL_STENCIL_TEST);
+        }
+      else
+        {
+        //DistortedLibrary.logMessage("InternalRenderState: enabling stencil test");
+        GLES30.glEnable(GLES30.GL_STENCIL_TEST);
+        }
+      }
+
+    /////////////////////////////////////////////////////
+    // 8. Adjust Stencil function?
+    if( mState.stencilFuncFunc!=cState.stencilFuncFunc || mState.stencilFuncRef!=cState.stencilFuncRef || mState.stencilFuncMask!=cState.stencilFuncMask )
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: setting stencil function");
+      cState.stencilFuncFunc = mState.stencilFuncFunc;
+      cState.stencilFuncRef  = mState.stencilFuncRef ;
+      cState.stencilFuncMask = mState.stencilFuncMask;
+      GLES30.glStencilFunc(cState.stencilFuncFunc,cState.stencilFuncRef,cState.stencilFuncMask);
+      }
+
+    /////////////////////////////////////////////////////
+    // 9. Adjust Stencil operation?
+    if( mState.stencilOpSfail!=cState.stencilOpSfail || mState.stencilOpDpfail!=cState.stencilOpDpfail || mState.stencilOpDppass!=cState.stencilOpDppass )
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: setting stencil op");
+      cState.stencilOpSfail = mState.stencilOpSfail;
+      cState.stencilOpDpfail= mState.stencilOpDpfail;
+      cState.stencilOpDppass= mState.stencilOpDppass;
+      GLES30.glStencilOp(cState.stencilOpSfail,cState.stencilOpDpfail,cState.stencilOpDppass);
+      }
+
+    /////////////////////////////////////////////////////
+    // 10. Write to Stencil buffer?
+    if( mState.stencilMask!=cState.stencilMask )
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: setting stencil mask");
+      cState.stencilMask = mState.stencilMask;
+      GLES30.glStencilMask(cState.stencilMask);
+      }
+
+    /////////////////////////////////////////////////////
+    // 11. Clear buffers?
+    if( mClear!=0 )
+      {
+      //DistortedLibrary.logMessage("InternalRenderState: clearing buffer");
+      GLES30.glClear(mClear);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glColorMask(boolean r, boolean g, boolean b, boolean a)
+    {
+    mState.colorMaskR = (r ? 1:0);
+    mState.colorMaskG = (g ? 1:0);
+    mState.colorMaskB = (b ? 1:0);
+    mState.colorMaskA = (a ? 1:0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glDepthMask(boolean mask)
+    {
+    mState.depthMask = (mask ? 1:0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glStencilMask(int mask)
+    {
+    mState.stencilMask = mask;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glEnable(int test)
+    {
+         if( test==GLES30.GL_DEPTH_TEST   ) mState.depthTest   = 1;
+    else if( test==GLES30.GL_STENCIL_TEST ) mState.stencilTest = 1;
+    else if( test==GLES30.GL_BLEND        ) mState.blend       = 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glDisable(int test)
+    {
+         if( test==GLES30.GL_DEPTH_TEST   ) mState.depthTest   = 0;
+    else if( test==GLES30.GL_STENCIL_TEST ) mState.stencilTest = 0;
+    else if( test==GLES30.GL_BLEND        ) mState.blend       = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glStencilFunc(int func, int ref, int mask)
+    {
+    mState.stencilFuncFunc = func;
+    mState.stencilFuncRef  = ref;
+    mState.stencilFuncMask = mask;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glStencilOp(int sfail, int dpfail, int dppass)
+    {
+    mState.stencilOpSfail = sfail;
+    mState.stencilOpDpfail= dpfail;
+    mState.stencilOpDppass= dppass;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glDepthFunc(int func)
+    {
+    mState.depthFunc = func;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glBlendFunc(int src, int dst)
+    {
+    mState.blendSrc = src;
+    mState.blendDst = dst;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void glClear(int mask)
+    {
+    mClear = mask;
+    }
+}
diff --git a/src/main/java/org/distorted/library/main/InternalStackFrame.java b/src/main/java/org/distorted/library/main/InternalStackFrame.java
deleted file mode 100644
index 2efc825..0000000
--- a/src/main/java/org/distorted/library/main/InternalStackFrame.java
+++ /dev/null
@@ -1,406 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 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.EffectType;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Implements a single 'frame' of all variables needed to remember the internal state of the library.
- * Such a frame must be remembered in a list whenever current Activity using the library fires off
- * another Activity which also wants to use the library. When that happens, we create a new 'frame',
- * remember the old one. When the second Activity ends and we come back to the first, we destroy the
- * second frame and recall the first.
- * <p>
- * Not part of public API, do not document
- *
- * @y.exclude
- */
-public class InternalStackFrame
-{
-  private static class Job
-    {
-    InternalObject object;
-    int action;
-
-    Job(InternalObject o, int a)
-      {
-      object = o;
-      action = a;
-      }
-    }
-
-  private static final LinkedList<InternalObject> mCommonDoneList = new LinkedList<>(); //
-  private static final HashMap<Long,Job> mCommonToDoMap           = new HashMap<>();    // Common
-  private static long mCommonNextClientID                         = 0;                  // InternalObject
-  private static long mCommonNextSystemID                         = 0;                  // (postprocessing)
-
-  //////////////////////////////////////////////////////////////////
-  private final LinkedList<InternalObject> mDoneList;             //
-  private final HashMap<Long,Job> mToDoMap;                       //
-  private final long mTaskId;                                     //
-  private long mNextClientID;                                     // InternalObject
-  private long mNextSystemID;                                     //
-
-  //////////////////////////////////////////////////////////////////
-  private boolean mInitialized;                                   // DistortedLibrary
-
-  //////////////////////////////////////////////////////////////////
-  private final int[] mMax;                                       // EffectQueue
-
-  //////////////////////////////////////////////////////////////////
-  private long mNextEffectsID;                                    // DistortedEffects;
-
-  //////////////////////////////////////////////////////////////////
-  private long mNextEffectID;                                     // Effect;
-
-  //////////////////////////////////////////////////////////////////
-  private final HashMap<ArrayList<Long>, InternalNodeData> mMapNodeID;// InternalNodeData
-  private long mNextNodeID;
-
-  //////////////////////////////////////////////////////////////////
-  private final ArrayList<InternalMaster.Slave> mSlaves;          // InternalMaster
-
-  ////////////////////////////////////////////////////////////////// EffectQueue
-  private long mNextQueueID;                                      //
-  private final HashMap<ArrayList<Long>,Long> mMapID;             // maps lists of Effect IDs (longs)
-                                                                  // to a single long - the queue ID.
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalStackFrame(long taskID)
-    {
-    mDoneList     = new LinkedList<>();
-    mToDoMap      = new HashMap<>();
-    mMapNodeID    = new HashMap<>();
-    mSlaves       = new ArrayList<>();
-    mMapID        = new HashMap<>();
-    mNextEffectsID= 0;
-    mNextClientID = 0;
-    mNextSystemID = 0;
-    mNextNodeID   = 0;
-    mNextQueueID  = 1;
-    mTaskId       = taskID;
-    mMax          = new int[EffectType.LENGTH];
-
-    EffectType.reset(mMax);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  long getTaskId()
-    {
-    return mTaskId;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onPauseCommon()
-    {
-    onPauseGeneric(mCommonDoneList,mCommonToDoMap);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void onPause()
-    {
-    onPauseGeneric(mDoneList,mToDoMap);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onPauseGeneric(LinkedList<InternalObject> list, HashMap<Long,Job> map)
-    {
-    int num = list.size();
-
-    try
-      {
-      for (int i=0; i<num; i++)
-        {
-        InternalObject object = list.removeFirst();
-        Job job = new Job(object, InternalObject.JOB_CREATE);
-        map.put(object.getID(),job);
-        object.recreate();
-        }
-      }
-    catch( Exception ignored )
-      {
-      // something else removed an object in the meantime; ignore
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void toDo()
-    {
-    toDoGeneric(mDoneList,mToDoMap);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void toDoCommon()
-    {
-    toDoGeneric(mCommonDoneList,mCommonToDoMap);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void toDoGeneric(LinkedList<InternalObject> list, HashMap<Long,Job> map)
-    {
-    for(Long key: map.keySet())
-      {
-      Job job = map.get(key);
-      InternalObject object = job.object;
-
-      if( job.action==InternalObject.JOB_CREATE )
-        {
-        object.create();
-        list.add(object);
-        }
-      else if( job.action==InternalObject.JOB_DELETE )
-        {
-        object.delete();
-        }
-      }
-
-    map.clear();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void cleanCommon()
-    {
-    mCommonDoneList.clear();
-    mCommonToDoMap.clear();
-    mCommonNextClientID = 0;
-    mCommonNextSystemID = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  long generateID(int type, int storage)
-    {
-    if( storage==InternalObject.STORAGE_PRIVATE )
-      {
-      return type==InternalObject.TYPE_SYST ? --mNextSystemID : ++mNextClientID;
-      }
-    else
-      {
-      return type==InternalObject.TYPE_SYST ? --mCommonNextSystemID : ++mCommonNextClientID;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void addToDoneList(InternalObject obj, int storage)
-    {
-    if( storage==InternalObject.STORAGE_PRIVATE )
-      {
-      if( !mDoneList.contains(obj) )
-        {
-        mDoneList.add(obj);
-        }
-      }
-    else
-      {
-      if( !mCommonDoneList.contains(obj) ) mCommonDoneList.add(obj);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void removeFromDoneList(InternalObject obj, int storage)
-    {
-    if( storage==InternalObject.STORAGE_PRIVATE )
-      {
-      mDoneList.remove(obj);
-      }
-    else
-      {
-      mCommonDoneList.remove(obj);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void markFor(InternalObject obj, long id, int storage, int jobType)
-    {
-    if( storage==InternalObject.STORAGE_PRIVATE )
-      {
-      mDoneList.remove(obj);
-      mToDoMap.put(id, new Job(obj,jobType) );
-      }
-    else
-      {
-      mCommonDoneList.remove(obj);
-      mCommonToDoMap.put(id, new Job(obj,jobType) );
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean isInitialized()
-    {
-    return mInitialized;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setInitialized(boolean init)
-    {
-    mInitialized = init;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  long getNextEffectsID()
-    {
-    return ++mNextEffectsID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  long getNextEffectID()
-    {
-    return mNextEffectID++;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getMax(int index)
-    {
-    return mMax[index];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean setMax(int index, int max)
-    {
-    if( !mInitialized || max<mMax[index] )
-      {
-      mMax[index] = max;
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public HashMap<ArrayList<Long>,Long> getMap()
-    {
-    return mMapID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public long getNextQueueID()
-    {
-    mNextQueueID++;
-
-    return mNextQueueID-1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalNodeData getMapID(ArrayList<Long> key)
-    {
-    return mMapNodeID.get(key);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalNodeData putNewDataToMap(ArrayList<Long> key)
-    {
-    InternalNodeData data = new InternalNodeData(++mNextNodeID,key);
-    mMapNodeID.put(key,data);
-    return data;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void removeKeyFromMap(ArrayList<Long> key)
-    {
-    mMapNodeID.remove(key);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  ArrayList<InternalMaster.Slave> getSet()
-    {
-    return mSlaves;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void debugLists(String frameMarker)
-    {
-    debugListsGeneric(mDoneList,mToDoMap,frameMarker);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void debugCommonList()
-    {
-    debugListsGeneric(mCommonDoneList,mCommonToDoMap,"Common");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void debugListsGeneric(LinkedList<InternalObject> list, HashMap<Long,Job> map,String frameMarker)
-    {
-    DistortedLibrary.logMessage("InternalStackFrame: "+frameMarker);
-    DistortedLibrary.logMessage("InternalStackFrame:  Done list:");
-
-    for(InternalObject object : list)
-      {
-      object.print("  ");
-      }
-
-    DistortedLibrary.logMessage("InternalStackFrame: ToDo list:");
-
-    Job job;
-
-    for(Long key: map.keySet())
-      {
-      job = map.get(key);
-      job.object.print(job.action==InternalObject.JOB_CREATE ? " create":" delete");
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void debugMap(String frameMarker)
-    {
-    DistortedLibrary.logMessage("InternalStackFrame: "+frameMarker);
-    InternalNodeData tmp;
-
-    for(ArrayList<Long> key: mMapNodeID.keySet())
-      {
-      tmp = mMapNodeID.get(key);
-      DistortedLibrary.logMessage("InternalStackFrame: NodeID: "+tmp.ID+" <-- "+key);
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/library/main/InternalStackFrame.kt b/src/main/java/org/distorted/library/main/InternalStackFrame.kt
new file mode 100644
index 0000000..2efc825
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalStackFrame.kt
@@ -0,0 +1,406 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.EffectType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Implements a single 'frame' of all variables needed to remember the internal state of the library.
+ * Such a frame must be remembered in a list whenever current Activity using the library fires off
+ * another Activity which also wants to use the library. When that happens, we create a new 'frame',
+ * remember the old one. When the second Activity ends and we come back to the first, we destroy the
+ * second frame and recall the first.
+ * <p>
+ * Not part of public API, do not document
+ *
+ * @y.exclude
+ */
+public class InternalStackFrame
+{
+  private static class Job
+    {
+    InternalObject object;
+    int action;
+
+    Job(InternalObject o, int a)
+      {
+      object = o;
+      action = a;
+      }
+    }
+
+  private static final LinkedList<InternalObject> mCommonDoneList = new LinkedList<>(); //
+  private static final HashMap<Long,Job> mCommonToDoMap           = new HashMap<>();    // Common
+  private static long mCommonNextClientID                         = 0;                  // InternalObject
+  private static long mCommonNextSystemID                         = 0;                  // (postprocessing)
+
+  //////////////////////////////////////////////////////////////////
+  private final LinkedList<InternalObject> mDoneList;             //
+  private final HashMap<Long,Job> mToDoMap;                       //
+  private final long mTaskId;                                     //
+  private long mNextClientID;                                     // InternalObject
+  private long mNextSystemID;                                     //
+
+  //////////////////////////////////////////////////////////////////
+  private boolean mInitialized;                                   // DistortedLibrary
+
+  //////////////////////////////////////////////////////////////////
+  private final int[] mMax;                                       // EffectQueue
+
+  //////////////////////////////////////////////////////////////////
+  private long mNextEffectsID;                                    // DistortedEffects;
+
+  //////////////////////////////////////////////////////////////////
+  private long mNextEffectID;                                     // Effect;
+
+  //////////////////////////////////////////////////////////////////
+  private final HashMap<ArrayList<Long>, InternalNodeData> mMapNodeID;// InternalNodeData
+  private long mNextNodeID;
+
+  //////////////////////////////////////////////////////////////////
+  private final ArrayList<InternalMaster.Slave> mSlaves;          // InternalMaster
+
+  ////////////////////////////////////////////////////////////////// EffectQueue
+  private long mNextQueueID;                                      //
+  private final HashMap<ArrayList<Long>,Long> mMapID;             // maps lists of Effect IDs (longs)
+                                                                  // to a single long - the queue ID.
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalStackFrame(long taskID)
+    {
+    mDoneList     = new LinkedList<>();
+    mToDoMap      = new HashMap<>();
+    mMapNodeID    = new HashMap<>();
+    mSlaves       = new ArrayList<>();
+    mMapID        = new HashMap<>();
+    mNextEffectsID= 0;
+    mNextClientID = 0;
+    mNextSystemID = 0;
+    mNextNodeID   = 0;
+    mNextQueueID  = 1;
+    mTaskId       = taskID;
+    mMax          = new int[EffectType.LENGTH];
+
+    EffectType.reset(mMax);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long getTaskId()
+    {
+    return mTaskId;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onPauseCommon()
+    {
+    onPauseGeneric(mCommonDoneList,mCommonToDoMap);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void onPause()
+    {
+    onPauseGeneric(mDoneList,mToDoMap);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onPauseGeneric(LinkedList<InternalObject> list, HashMap<Long,Job> map)
+    {
+    int num = list.size();
+
+    try
+      {
+      for (int i=0; i<num; i++)
+        {
+        InternalObject object = list.removeFirst();
+        Job job = new Job(object, InternalObject.JOB_CREATE);
+        map.put(object.getID(),job);
+        object.recreate();
+        }
+      }
+    catch( Exception ignored )
+      {
+      // something else removed an object in the meantime; ignore
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void toDo()
+    {
+    toDoGeneric(mDoneList,mToDoMap);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void toDoCommon()
+    {
+    toDoGeneric(mCommonDoneList,mCommonToDoMap);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void toDoGeneric(LinkedList<InternalObject> list, HashMap<Long,Job> map)
+    {
+    for(Long key: map.keySet())
+      {
+      Job job = map.get(key);
+      InternalObject object = job.object;
+
+      if( job.action==InternalObject.JOB_CREATE )
+        {
+        object.create();
+        list.add(object);
+        }
+      else if( job.action==InternalObject.JOB_DELETE )
+        {
+        object.delete();
+        }
+      }
+
+    map.clear();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void cleanCommon()
+    {
+    mCommonDoneList.clear();
+    mCommonToDoMap.clear();
+    mCommonNextClientID = 0;
+    mCommonNextSystemID = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long generateID(int type, int storage)
+    {
+    if( storage==InternalObject.STORAGE_PRIVATE )
+      {
+      return type==InternalObject.TYPE_SYST ? --mNextSystemID : ++mNextClientID;
+      }
+    else
+      {
+      return type==InternalObject.TYPE_SYST ? --mCommonNextSystemID : ++mCommonNextClientID;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void addToDoneList(InternalObject obj, int storage)
+    {
+    if( storage==InternalObject.STORAGE_PRIVATE )
+      {
+      if( !mDoneList.contains(obj) )
+        {
+        mDoneList.add(obj);
+        }
+      }
+    else
+      {
+      if( !mCommonDoneList.contains(obj) ) mCommonDoneList.add(obj);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeFromDoneList(InternalObject obj, int storage)
+    {
+    if( storage==InternalObject.STORAGE_PRIVATE )
+      {
+      mDoneList.remove(obj);
+      }
+    else
+      {
+      mCommonDoneList.remove(obj);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void markFor(InternalObject obj, long id, int storage, int jobType)
+    {
+    if( storage==InternalObject.STORAGE_PRIVATE )
+      {
+      mDoneList.remove(obj);
+      mToDoMap.put(id, new Job(obj,jobType) );
+      }
+    else
+      {
+      mCommonDoneList.remove(obj);
+      mCommonToDoMap.put(id, new Job(obj,jobType) );
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean isInitialized()
+    {
+    return mInitialized;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setInitialized(boolean init)
+    {
+    mInitialized = init;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long getNextEffectsID()
+    {
+    return ++mNextEffectsID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  long getNextEffectID()
+    {
+    return mNextEffectID++;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getMax(int index)
+    {
+    return mMax[index];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean setMax(int index, int max)
+    {
+    if( !mInitialized || max<mMax[index] )
+      {
+      mMax[index] = max;
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public HashMap<ArrayList<Long>,Long> getMap()
+    {
+    return mMapID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long getNextQueueID()
+    {
+    mNextQueueID++;
+
+    return mNextQueueID-1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalNodeData getMapID(ArrayList<Long> key)
+    {
+    return mMapNodeID.get(key);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalNodeData putNewDataToMap(ArrayList<Long> key)
+    {
+    InternalNodeData data = new InternalNodeData(++mNextNodeID,key);
+    mMapNodeID.put(key,data);
+    return data;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void removeKeyFromMap(ArrayList<Long> key)
+    {
+    mMapNodeID.remove(key);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ArrayList<InternalMaster.Slave> getSet()
+    {
+    return mSlaves;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void debugLists(String frameMarker)
+    {
+    debugListsGeneric(mDoneList,mToDoMap,frameMarker);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void debugCommonList()
+    {
+    debugListsGeneric(mCommonDoneList,mCommonToDoMap,"Common");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void debugListsGeneric(LinkedList<InternalObject> list, HashMap<Long,Job> map,String frameMarker)
+    {
+    DistortedLibrary.logMessage("InternalStackFrame: "+frameMarker);
+    DistortedLibrary.logMessage("InternalStackFrame:  Done list:");
+
+    for(InternalObject object : list)
+      {
+      object.print("  ");
+      }
+
+    DistortedLibrary.logMessage("InternalStackFrame: ToDo list:");
+
+    Job job;
+
+    for(Long key: map.keySet())
+      {
+      job = map.get(key);
+      job.object.print(job.action==InternalObject.JOB_CREATE ? " create":" delete");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void debugMap(String frameMarker)
+    {
+    DistortedLibrary.logMessage("InternalStackFrame: "+frameMarker);
+    InternalNodeData tmp;
+
+    for(ArrayList<Long> key: mMapNodeID.keySet())
+      {
+      tmp = mMapNodeID.get(key);
+      DistortedLibrary.logMessage("InternalStackFrame: NodeID: "+tmp.ID+" <-- "+key);
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/library/main/InternalStackFrameList.java b/src/main/java/org/distorted/library/main/InternalStackFrameList.java
deleted file mode 100644
index b38a21f..0000000
--- a/src/main/java/org/distorted/library/main/InternalStackFrameList.java
+++ /dev/null
@@ -1,314 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 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.message.EffectMessageSender;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Implements a List of Frames (see InternalStackFrame)
- * <p>
- * Not part of public API, do not document
- *
- * @y.exclude
- */
-public class InternalStackFrameList
-{
-  private final static Object mLock = new Object();
-  private static boolean mToDo = false;
-  private static InternalStackFrame mCurrentFrame = null;
-  private static final ArrayList<InternalStackFrame> mFrameList = new ArrayList<>();
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onCreate(long taskId)
-    {
-    int num = mFrameList.size();
-    InternalStackFrame frame;
-    boolean found = false;
-
-    for(int i=0; i<num; i++)
-      {
-      frame = mFrameList.get(i);
-
-      if( frame.getTaskId() == taskId )
-        {
-        mCurrentFrame = frame;
-        found = true;
-        break;
-        }
-      }
-
-    if( !found )
-      {
-      synchronized(mLock)
-        {
-        mCurrentFrame = new InternalStackFrame(taskId);
-        mFrameList.add(mCurrentFrame);
-        }
-      }
-
-    mCurrentFrame.setInitialized(false);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onResume(long taskId)
-    {
-    int num = mFrameList.size();
-    InternalStackFrame frame;
-
-    for(int i=0; i<num; i++)
-      {
-      frame = mFrameList.get(i);
-
-      if( frame.getTaskId() == taskId )
-        {
-        mCurrentFrame = frame;
-        break;
-        }
-      }
-
-    mCurrentFrame.setInitialized(false);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onPause(long taskId)
-    {
-    int num = mFrameList.size();
-
-    for(int i=0; i<num; i++)
-      {
-      if( mFrameList.get(i).getTaskId() == taskId )
-        {
-        synchronized(mLock)
-          {
-          mCurrentFrame.onPause();
-          InternalStackFrame.onPauseCommon();
-          mToDo = true;
-          }
-
-        break;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void onDestroy(long taskId)
-    {
-    int num = mFrameList.size();
-
-    for(int i=0; i<num; i++)
-      {
-      if( mFrameList.get(i).getTaskId() == taskId )
-        {
-        synchronized(mLock)
-          {
-          mFrameList.remove(i);
-          if( num==1 ) InternalStackFrame.cleanCommon();
-          }
-
-        break;
-        }
-      }
-
-    setInitialized(false);
-
-    if( num<2 )
-      {
-      EffectMessageSender.stopSending();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @SuppressWarnings("unused")
-  static void debugLists()
-    {
-    int num = mFrameList.size();
-    InternalStackFrame frame;
-
-    InternalStackFrame.debugCommonList();
-
-    for(int i=0; i<num; i++)
-      {
-      frame = mFrameList.get(i);
-      frame.debugLists("frame "+i);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @SuppressWarnings("unused")
-  static void debugMap()
-    {
-    int num = mFrameList.size();
-    InternalStackFrame frame;
-
-    for(int i=0; i<num; i++)
-      {
-      frame = mFrameList.get(i);
-      frame.debugMap("frame "+i);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// must be called from a thread holding OpenGL Context
-
-  static boolean toDo()
-    {
-    if( mToDo )
-      {
-      mToDo = false;
-
-      synchronized(mLock)
-        {
-        mCurrentFrame.toDo();
-        InternalStackFrame.toDoCommon();
-        }
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void markFor(InternalObject obj, long id, int storage, int job)
-    {
-    synchronized(mLock)
-      {
-      mCurrentFrame.markFor(obj,id,storage,job);
-      mToDo = true;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void removeFromDone(InternalObject obj, int storage)
-    {
-    synchronized(mLock)
-      {
-      mCurrentFrame.removeFromDoneList(obj,storage);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static InternalStackFrame getCurrentFrame()
-    {
-    return mCurrentFrame;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static long getNextEffectsID()
-    {
-    return mCurrentFrame.getNextEffectsID();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void setInitialized(boolean init)
-    {
-    mCurrentFrame.setInitialized(init);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static InternalNodeData getMapID(ArrayList<Long> key)
-    {
-    return mCurrentFrame.getMapID(key);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static InternalNodeData putNewDataToMap(ArrayList<Long> key)
-    {
-    return mCurrentFrame.putNewDataToMap(key);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void removeKeyFromMap(ArrayList<Long> key)
-    {
-    mCurrentFrame.removeKeyFromMap(key);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static ArrayList<InternalMaster.Slave> getSet()
-    {
-    return mCurrentFrame.getSet();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static long getNextEffectID()
-    {
-    return mCurrentFrame.getNextEffectID();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static boolean isInitialized()
-    {
-    return mCurrentFrame.isInitialized();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static int getMax(int index)
-    {
-    return mCurrentFrame.getMax(index);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static boolean setMax(int index,int max)
-    {
-    return mCurrentFrame.setMax(index,max);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static HashMap<ArrayList<Long>,Long> getMap()
-    {
-    return mCurrentFrame.getMap();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static long getNextQueueID()
-    {
-    return mCurrentFrame.getNextQueueID();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-}
diff --git a/src/main/java/org/distorted/library/main/InternalStackFrameList.kt b/src/main/java/org/distorted/library/main/InternalStackFrameList.kt
new file mode 100644
index 0000000..b38a21f
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalStackFrameList.kt
@@ -0,0 +1,314 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.message.EffectMessageSender;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Implements a List of Frames (see InternalStackFrame)
+ * <p>
+ * Not part of public API, do not document
+ *
+ * @y.exclude
+ */
+public class InternalStackFrameList
+{
+  private final static Object mLock = new Object();
+  private static boolean mToDo = false;
+  private static InternalStackFrame mCurrentFrame = null;
+  private static final ArrayList<InternalStackFrame> mFrameList = new ArrayList<>();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onCreate(long taskId)
+    {
+    int num = mFrameList.size();
+    InternalStackFrame frame;
+    boolean found = false;
+
+    for(int i=0; i<num; i++)
+      {
+      frame = mFrameList.get(i);
+
+      if( frame.getTaskId() == taskId )
+        {
+        mCurrentFrame = frame;
+        found = true;
+        break;
+        }
+      }
+
+    if( !found )
+      {
+      synchronized(mLock)
+        {
+        mCurrentFrame = new InternalStackFrame(taskId);
+        mFrameList.add(mCurrentFrame);
+        }
+      }
+
+    mCurrentFrame.setInitialized(false);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onResume(long taskId)
+    {
+    int num = mFrameList.size();
+    InternalStackFrame frame;
+
+    for(int i=0; i<num; i++)
+      {
+      frame = mFrameList.get(i);
+
+      if( frame.getTaskId() == taskId )
+        {
+        mCurrentFrame = frame;
+        break;
+        }
+      }
+
+    mCurrentFrame.setInitialized(false);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onPause(long taskId)
+    {
+    int num = mFrameList.size();
+
+    for(int i=0; i<num; i++)
+      {
+      if( mFrameList.get(i).getTaskId() == taskId )
+        {
+        synchronized(mLock)
+          {
+          mCurrentFrame.onPause();
+          InternalStackFrame.onPauseCommon();
+          mToDo = true;
+          }
+
+        break;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void onDestroy(long taskId)
+    {
+    int num = mFrameList.size();
+
+    for(int i=0; i<num; i++)
+      {
+      if( mFrameList.get(i).getTaskId() == taskId )
+        {
+        synchronized(mLock)
+          {
+          mFrameList.remove(i);
+          if( num==1 ) InternalStackFrame.cleanCommon();
+          }
+
+        break;
+        }
+      }
+
+    setInitialized(false);
+
+    if( num<2 )
+      {
+      EffectMessageSender.stopSending();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @SuppressWarnings("unused")
+  static void debugLists()
+    {
+    int num = mFrameList.size();
+    InternalStackFrame frame;
+
+    InternalStackFrame.debugCommonList();
+
+    for(int i=0; i<num; i++)
+      {
+      frame = mFrameList.get(i);
+      frame.debugLists("frame "+i);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @SuppressWarnings("unused")
+  static void debugMap()
+    {
+    int num = mFrameList.size();
+    InternalStackFrame frame;
+
+    for(int i=0; i<num; i++)
+      {
+      frame = mFrameList.get(i);
+      frame.debugMap("frame "+i);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context
+
+  static boolean toDo()
+    {
+    if( mToDo )
+      {
+      mToDo = false;
+
+      synchronized(mLock)
+        {
+        mCurrentFrame.toDo();
+        InternalStackFrame.toDoCommon();
+        }
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void markFor(InternalObject obj, long id, int storage, int job)
+    {
+    synchronized(mLock)
+      {
+      mCurrentFrame.markFor(obj,id,storage,job);
+      mToDo = true;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void removeFromDone(InternalObject obj, int storage)
+    {
+    synchronized(mLock)
+      {
+      mCurrentFrame.removeFromDoneList(obj,storage);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static InternalStackFrame getCurrentFrame()
+    {
+    return mCurrentFrame;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static long getNextEffectsID()
+    {
+    return mCurrentFrame.getNextEffectsID();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void setInitialized(boolean init)
+    {
+    mCurrentFrame.setInitialized(init);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static InternalNodeData getMapID(ArrayList<Long> key)
+    {
+    return mCurrentFrame.getMapID(key);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static InternalNodeData putNewDataToMap(ArrayList<Long> key)
+    {
+    return mCurrentFrame.putNewDataToMap(key);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void removeKeyFromMap(ArrayList<Long> key)
+    {
+    mCurrentFrame.removeKeyFromMap(key);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static ArrayList<InternalMaster.Slave> getSet()
+    {
+    return mCurrentFrame.getSet();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static long getNextEffectID()
+    {
+    return mCurrentFrame.getNextEffectID();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static boolean isInitialized()
+    {
+    return mCurrentFrame.isInitialized();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getMax(int index)
+    {
+    return mCurrentFrame.getMax(index);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static boolean setMax(int index,int max)
+    {
+    return mCurrentFrame.setMax(index,max);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static HashMap<ArrayList<Long>,Long> getMap()
+    {
+    return mCurrentFrame.getMap();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static long getNextQueueID()
+    {
+    return mCurrentFrame.getNextQueueID();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/src/main/java/org/distorted/library/main/InternalSurface.java b/src/main/java/org/distorted/library/main/InternalSurface.java
deleted file mode 100644
index 211e0ca..0000000
--- a/src/main/java/org/distorted/library/main/InternalSurface.java
+++ /dev/null
@@ -1,82 +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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// common parent class of Texture & OutputSurface; so that we can store either in Nodes.
-
-abstract class InternalSurface extends InternalObject
-{
-  int mColorCreated;
-  int mNumColors;
-  int mNumFBOs;
-  int[] mColorH;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  InternalSurface(int create, int numfbos, int numcolors, int type, int storage)
-    {
-    super(type,storage);
-
-    mNumFBOs      = numfbos;
-    mNumColors    = numcolors;
-    mColorCreated = create;
-
-    allocateColor();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void allocateColor()
-    {
-    int total = mNumFBOs*mNumColors;
-
-    if( total>0 )
-      {
-      mColorH = new int[total];
-      for( int i=0; i<total; i++ )  mColorH[i] = 0;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// debugging only
-
-  String printDetails()
-    {
-    return getClass().getSimpleName();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean setAsInput()
-    {
-    if( mColorH[0]>0 )
-      {
-      GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
-      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[0]);
-      return true;
-      }
-
-    return false;
-    }
-}
diff --git a/src/main/java/org/distorted/library/main/InternalSurface.kt b/src/main/java/org/distorted/library/main/InternalSurface.kt
new file mode 100644
index 0000000..211e0ca
--- /dev/null
+++ b/src/main/java/org/distorted/library/main/InternalSurface.kt
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// common parent class of Texture & OutputSurface; so that we can store either in Nodes.
+
+abstract class InternalSurface extends InternalObject
+{
+  int mColorCreated;
+  int mNumColors;
+  int mNumFBOs;
+  int[] mColorH;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  InternalSurface(int create, int numfbos, int numcolors, int type, int storage)
+    {
+    super(type,storage);
+
+    mNumFBOs      = numfbos;
+    mNumColors    = numcolors;
+    mColorCreated = create;
+
+    allocateColor();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void allocateColor()
+    {
+    int total = mNumFBOs*mNumColors;
+
+    if( total>0 )
+      {
+      mColorH = new int[total];
+      for( int i=0; i<total; i++ )  mColorH[i] = 0;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// debugging only
+
+  String printDetails()
+    {
+    return getClass().getSimpleName();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean setAsInput()
+    {
+    if( mColorH[0]>0 )
+      {
+      GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
+      GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mColorH[0]);
+      return true;
+      }
+
+    return false;
+    }
+}
