commit 12f9e4bb027b2fb7d97872153561c8e36908e342
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Jul 5 13:39:54 2018 +0100

    a mix of two changes:
    
    1) remove the DistortedInputSurface interface (now every Surface is Input)
    2) make the OIT SSBO self-adjustable in size

diff --git a/src/main/java/org/distorted/library/main/Distorted.java b/src/main/java/org/distorted/library/main/Distorted.java
index 8698e7d..9927c15 100644
--- a/src/main/java/org/distorted/library/main/Distorted.java
+++ b/src/main/java/org/distorted/library/main/Distorted.java
@@ -83,7 +83,7 @@ public class Distorted
    * of FBO_QUEUE_SIZE FBOs. (otherwise we sometimes get a 'full pipeline flush' and the end result
    * might be missing part of the Objects)
    *
-   * This bug only exists on Mali driver r12. TODO: on other platforms, make this equal to 1.
+   * This bug only exists on Mali driver r12.
    *
    * https://community.arm.com/graphics/f/discussions/10285/opengl-es-3-1-on-mali-t880-flashes
    */
diff --git a/src/main/java/org/distorted/library/main/DistortedEffects.java b/src/main/java/org/distorted/library/main/DistortedEffects.java
index 02fb9f3..dbf9ffd 100644
--- a/src/main/java/org/distorted/library/main/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/main/DistortedEffects.java
@@ -76,12 +76,15 @@ public class DistortedEffects
 
   /// OIT SSBO BUFFER ///
   private static int[] mLinkedListSSBO = new int[1];
-  private static int[] mAtomicCounter = new int[1];
+  private static int[] mAtomicCounter = new int[Distorted.FBO_QUEUE_SIZE];
+  private static int   mCurrBuffer;
 
   static
     {
     mLinkedListSSBO[0]= -1;
-    mAtomicCounter[0] = -1;
+    mCurrBuffer       =  0;
+
+    for(int i=0; i<Distorted.FBO_QUEUE_SIZE; i++)  mAtomicCounter[i] = -1;
     }
 
   ///////////////////////////////////////////////////////////////
@@ -535,54 +538,112 @@ public class DistortedEffects
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// reset atomic counter to 0
 
-  static void zeroOutAtomic()
+  private static int printPreviousBuffer()
     {
-    if( mAtomicCounter[0]<0 )
+    int counter = 0;
+
+    ByteBuffer atomicBuf = (ByteBuffer)GLES31.glMapBufferRange( GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, 4,
+                                                                GLES31.GL_MAP_READ_BIT);
+    if( atomicBuf!=null )
+      {
+      IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
+      counter = atomicIntBuf.get(0);
+      }
+    else
       {
-      GLES31.glGenBuffers(1,mAtomicCounter,0);
-      GLES31.glBindBufferBase(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, mAtomicCounter[0]);
-      GLES31.glBindBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER, mAtomicCounter[0] );
-      GLES31.glBufferData(GLES31.GL_ATOMIC_COUNTER_BUFFER, 4, null, GLES31.GL_DYNAMIC_DRAW);
-      GLES31.glBindBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0);
+      android.util.Log.e("effects", "print: failed to map atomic buffer");
       }
 
-    GLES31.glBindBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER, mAtomicCounter[0] );
+    GLES31.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
 
+    return counter;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void zeroBuffer()
+    {
     ByteBuffer atomicBuf = (ByteBuffer)GLES31.glMapBufferRange( GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, 4,
-                                                                GLES31.GL_MAP_WRITE_BIT|GLES31.GL_MAP_INVALIDATE_BUFFER_BIT);
+        GLES31.GL_MAP_WRITE_BIT|GLES31.GL_MAP_INVALIDATE_BUFFER_BIT);
     if( atomicBuf!=null )
       {
       IntBuffer atomicIntBuf = atomicBuf.order(ByteOrder.nativeOrder()).asIntBuffer();
-
-      //int counter = atomicIntBuf.get(0);
-      //android.util.Log.e("counter", "now = "+counter+" w="+surface.mWidth+" h="+surface.mHeight
-      //                             +" diff="+(counter-surface.mWidth*surface.mHeight));
       atomicIntBuf.put(0,0);
       }
     else
       {
-      android.util.Log.e("effects", "failed to map atomic buffer");
+      android.util.Log.e("effects", "zero: failed to map atomic buffer");
       }
 
     GLES31.glUnmapBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER);
-    GLES31.glBindBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// reset atomic counter to 0
+
+  static int zeroOutAtomic()
+    {
+    int counter = 0;
+
+    if( mAtomicCounter[0]<0 )
+      {
+      GLES31.glGenBuffers(Distorted.FBO_QUEUE_SIZE,mAtomicCounter,0);
+
+      for(int i=0; i<Distorted.FBO_QUEUE_SIZE; i++)
+        {
+        GLES31.glBindBuffer(GLES31.GL_ATOMIC_COUNTER_BUFFER, mAtomicCounter[i]);
+        GLES31.glBufferData(GLES31.GL_ATOMIC_COUNTER_BUFFER, 4, null, GLES31.GL_DYNAMIC_DRAW);
+        zeroBuffer();
+        }
+      }
+
+    // reading the value of the buffer on every frame would slow down rendering by
+    // about 3%; doing it only once every 5 frames affects speed by less than 1%.
+    if( mCurrBuffer==0 )
+      {
+      GLES31.glBindBufferBase(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, mAtomicCounter[mCurrBuffer]);
+      counter = printPreviousBuffer();
+      }
+
+    if( ++mCurrBuffer>=Distorted.FBO_QUEUE_SIZE ) mCurrBuffer = 0;
+
+    GLES31.glBindBufferBase(GLES31.GL_ATOMIC_COUNTER_BUFFER, 0, mAtomicCounter[mCurrBuffer]);
+    zeroBuffer();
+
+    return counter;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Pass1 of the OIT algorithm. Clear per-pixel head-poiners.
 
-  static void oitClear(DistortedOutputSurface surface)
+  static void oitClear(DistortedOutputSurface surface, int counter)
     {
     if( mLinkedListSSBO[0]<0 )
       {
-      int size = (int)(surface.mWidth*surface.mHeight*(3*mBufferSize+1)*4);
-
       GLES31.glGenBuffers(1,mLinkedListSSBO,0);
+
+      int size = (int)(surface.mWidth*surface.mHeight*(3*mBufferSize+1)*4);
       GLES31.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, mLinkedListSSBO[0]);
       GLES31.glBufferData(GLES31.GL_SHADER_STORAGE_BUFFER, size, null, GLES31.GL_DYNAMIC_READ|GLES31.GL_DYNAMIC_DRAW);
+      GLES31.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, 0);
+
       GLES31.glBindBufferBase(GLES31.GL_SHADER_STORAGE_BUFFER, 1, mLinkedListSSBO[0]);
+      }
+
+    // See if we have overflown the SSBO in one of the previous frames.
+    // If yes, assume we need to make the SSBO larger.
+    float overflow = counter/(mBufferSize*surface.mWidth*surface.mHeight);
+
+    if( overflow>1.0f )
+      {
+      //android.util.Log.e("effects", "previous frame rendered "+counter+
+      //                   " fragments, but there was only "+(mBufferSize*surface.mWidth*surface.mHeight)+" space");
+
+      mBufferSize *= (int)(overflow+1.0f);
+      int size = (int)(surface.mWidth*surface.mHeight*(3*mBufferSize+1)*4);
+      GLES31.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, mLinkedListSSBO[0]);
+      GLES31.glBufferData(GLES31.GL_SHADER_STORAGE_BUFFER, size, null, GLES31.GL_DYNAMIC_READ|GLES31.GL_DYNAMIC_DRAW);
       GLES31.glBindBuffer(GLES31.GL_SHADER_STORAGE_BUFFER, 0);
       }
 
@@ -665,7 +726,8 @@ public class DistortedEffects
   static void onPause()
     {
     mLinkedListSSBO[0]= -1;
-    mAtomicCounter[0] = -1;
+
+    for(int i=0; i<Distorted.FBO_QUEUE_SIZE; i++) mAtomicCounter[i] = -1;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -675,6 +737,13 @@ public class DistortedEffects
     mNextID =  0;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void setSSBOSize(float size)
+    {
+    mBufferSize = size;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java b/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
index d29e25a..3f5eee9 100644
--- a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
+++ b/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
@@ -28,7 +28,7 @@ import android.opengl.GLES31;
  * User is able to create offscreen FBOs and both a) render to them b) use their COLOR0 attachment as
  * an input texture. Attaching Depths and/or Stencils is also possible.
  */
-public class DistortedFramebuffer extends DistortedOutputSurface implements DistortedInputSurface
+public class DistortedFramebuffer extends DistortedOutputSurface
   {
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -250,24 +250,6 @@ public class DistortedFramebuffer extends DistortedOutputSurface implements Dist
     super(width,height,NOT_CREATED_YET,1,numcolors,depthStencil,NOT_CREATED_YET,TYPE_USER);
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Bind the underlying rectangle of pixels as a OpenGL Texture.
- *
- * @return <code>true</code> if successful.
- */
-  public boolean setAsInput()
-    {
-    if( mColorH[0]>0 )
-      {
-      GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
-      GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mColorH[0]);
-      return true;
-      }
-
-    return false;
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Bind the underlying rectangle of pixels as a OpenGL Texture.
diff --git a/src/main/java/org/distorted/library/main/DistortedInputSurface.java b/src/main/java/org/distorted/library/main/DistortedInputSurface.java
deleted file mode 100644
index a0da9f3..0000000
--- a/src/main/java/org/distorted/library/main/DistortedInputSurface.java
+++ /dev/null
@@ -1,50 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2016 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.library.main;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * A Surface that we can set as an Input (take its rectangle of pixels and skin a Mesh with it).
- */
-
-// This really ought to be an abstract class instead, but effing Java has no multiple inheritance...
-
-public interface DistortedInputSurface
-{
-/**
- * Do not document this as public API
- * @y.exclude
- */
-  long getID();
-/**
- * Do not document this as public API
- * @y.exclude
- */
-  int getWidth();
-/**
- * Do not document this as public API
- * @y.exclude
- */
-  int getHeight();
-/**
- * Take the underlying rectangle of pixels and bind this texture to OpenGL.
- */
-  boolean setAsInput();
-}
diff --git a/src/main/java/org/distorted/library/main/DistortedNode.java b/src/main/java/org/distorted/library/main/DistortedNode.java
index f0b8019..f8b1e59 100644
--- a/src/main/java/org/distorted/library/main/DistortedNode.java
+++ b/src/main/java/org/distorted/library/main/DistortedNode.java
@@ -68,7 +68,7 @@ public class DistortedNode implements DistortedMaster.Slave
   private DistortedOutputSurface mSurfaceParent;
   private MeshObject mMesh;
   private DistortedEffects mEffects;
-  private DistortedInputSurface mSurface;
+  private DistortedSurface mSurface;
   private DistortedRenderState mState;
   private NodeData mData;
   private int mFboW, mFboH, mFboDepthStencil;
@@ -243,7 +243,7 @@ public class DistortedNode implements DistortedMaster.Slave
 
   int markStencilAndDepth(long currTime, DistortedOutputSurface surface, EffectQueuePostprocess queue)
     {
-    DistortedInputSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
 
     if( input.setAsInput() )
       {
@@ -261,7 +261,7 @@ public class DistortedNode implements DistortedMaster.Slave
 
   int drawNoBlend(long currTime, DistortedOutputSurface surface)
     {
-    DistortedInputSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
 
     if( input.setAsInput() )
       {
@@ -280,7 +280,7 @@ public class DistortedNode implements DistortedMaster.Slave
 
   int drawOIT(long currTime, DistortedOutputSurface surface)
     {
-    DistortedInputSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
 
     if( input.setAsInput() )
       {
@@ -297,7 +297,7 @@ public class DistortedNode implements DistortedMaster.Slave
 
   int draw(long currTime, DistortedOutputSurface surface)
     {
-    DistortedInputSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
+    DistortedSurface input = mNumChildren[0]==0 ? mSurface : mData.mFBO;
 
     if( input.setAsInput() )
       {
@@ -392,7 +392,7 @@ public class DistortedNode implements DistortedMaster.Slave
  * @param effects DistortedEffects to put into the new Node.
  * @param mesh MeshObject to put into the new Node.
  */
-  public DistortedNode(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
+  public DistortedNode(DistortedSurface surface, DistortedEffects effects, MeshObject mesh)
     {
     mSurface       = surface;
     mEffects       = effects;
@@ -526,6 +526,30 @@ public class DistortedNode implements DistortedMaster.Slave
     mRenderWayOIT = oit;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+  /**
+   * When rendering this Node, should we use the Order Independent Transparency render more?
+   * <p>
+   * There are two modes of rendering: the fast 'normal' way, which however renders transparent
+   * fragments in different ways depending on which fragments get rendered first, or the slower
+   * 'oit' way, which renders transparent fragments correctly regardless of their order.
+   *
+   * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
+   * @param initialSize Initial number of transparent fragments we expect, in screenfulls.
+   *                    I.e '1.0' means 'the scene we are going to render contains about 1 screen
+   *                    worth of transparent fragments'. Valid values: 0.0 &lt; initialSize &lt; 10.0
+   *                    Even if you get this wrong, the library will detect that there are more
+   *                    transparent fragments than it has space for and readjust its internal buffers,
+   *                    but only after a few frames during which one will probably see missing objects.
+   */
+  public void setOrderIndependentTransparency(boolean oit, float initialSize)
+    {
+    mRenderWayOIT = oit;
+
+    if( initialSize>0.0f && initialSize<10.0f )
+      DistortedEffects.setSSBOSize(initialSize);
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Adds a new child to the last position in the list of our Node's children.
@@ -553,7 +577,7 @@ public class DistortedNode implements DistortedMaster.Slave
  * @param mesh MeshObject to initialize our child Node with.
  * @return the newly constructed child Node, or null if we couldn't allocate resources.
  */
-  public DistortedNode attach(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
+  public DistortedNode attach(DistortedSurface surface, DistortedEffects effects, MeshObject mesh)
     {
     DistortedNode node = new DistortedNode(surface,effects,mesh);
     mJobs.add(new Job(ATTACH,node));
@@ -717,7 +741,7 @@ public class DistortedNode implements DistortedMaster.Slave
  *
  * @return The DistortedInputSurface contained in the Node.
  */
-  public DistortedInputSurface getSurface()
+  public DistortedSurface getSurface()
     {
     return mSurface;
     }
diff --git a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
index fd05bab..14a584b 100644
--- a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
@@ -311,8 +311,8 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
   private static void oitClear(DistortedOutputSurface buffer)
     {
-    DistortedEffects.zeroOutAtomic();
-    DistortedEffects.oitClear(buffer);
+    int counter = DistortedEffects.zeroOutAtomic();
+    DistortedEffects.oitClear(buffer,counter);
     GLES31.glMemoryBarrier(GLES31.GL_SHADER_STORAGE_BARRIER_BIT|GLES31.GL_ATOMIC_COUNTER_BARRIER_BIT);
     }
 
@@ -881,6 +881,30 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
     mRenderWayOIT = oit;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * When rendering this Node, should we use the Order Independent Transparency render more?
+ * <p>
+ * There are two modes of rendering: the fast 'normal' way, which however renders transparent
+ * fragments in different ways depending on which fragments get rendered first, or the slower
+ * 'oit' way, which renders transparent fragments correctly regardless of their order.
+ *
+ * @param oit True if we want to render more slowly, but in a way which accounts for transparency.
+ * @param initialSize Initial number of transparent fragments we expect, in screenfuls.
+ *                    I.e '1.0' means 'the scene we are going to render contains about 1 screen
+ *                    worth of transparent fragments'. Valid values: 0.0 &lt; initialSize &lt; 10.0
+ *                    Even if you get this wrong, the library will detect that there are more
+ *                    transparent fragments than it has space for and readjust its internal buffers,
+ *                    but only after a few frames during which one will probably see missing objects.
+ */
+public void setOrderIndependentTransparency(boolean oit, float initialSize)
+  {
+  mRenderWayOIT = oit;
+
+  if( initialSize>0.0f && initialSize<10.0f )
+    DistortedEffects.setSSBOSize(initialSize);
+  }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Adds a new child to the last position in the list of our Surface's children.
@@ -908,7 +932,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
  * @param mesh MeshObject to initialize our child Node with.
  * @return the newly constructed child Node, or null if we couldn't allocate resources.
  */
-  public DistortedNode attach(DistortedInputSurface surface, DistortedEffects effects, MeshObject mesh)
+  public DistortedNode attach(DistortedSurface surface, DistortedEffects effects, MeshObject mesh)
     {
     DistortedNode node = new DistortedNode(surface,effects,mesh);
     mJobs.add(new Job(ATTACH,node));
diff --git a/src/main/java/org/distorted/library/main/DistortedSurface.java b/src/main/java/org/distorted/library/main/DistortedSurface.java
index 538138a..20cb682 100644
--- a/src/main/java/org/distorted/library/main/DistortedSurface.java
+++ b/src/main/java/org/distorted/library/main/DistortedSurface.java
@@ -19,9 +19,15 @@
 
 package org.distorted.library.main;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+import android.opengl.GLES31;
 
-abstract class DistortedSurface extends DistortedObject
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * This is not really part of the public API.
+ *
+ * @y.exclude
+ */
+public abstract class DistortedSurface extends DistortedObject
 {
   int mColorCreated;
   int mNumColors;
@@ -96,4 +102,22 @@ abstract class DistortedSurface extends DistortedObject
     {
     return mesh==null ? 0 : (int)(mWidth*mesh.zFactor);
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Bind the underlying rectangle of pixels as a OpenGL Texture.
+ *
+ * @return <code>true</code> if successful.
+ */
+  public boolean setAsInput()
+    {
+    if( mColorH[0]>0 )
+      {
+      GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
+      GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mColorH[0]);
+      return true;
+      }
+
+    return false;
+    }
 }
diff --git a/src/main/java/org/distorted/library/main/DistortedTexture.java b/src/main/java/org/distorted/library/main/DistortedTexture.java
index b5592fc..7d1b4d8 100644
--- a/src/main/java/org/distorted/library/main/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/main/DistortedTexture.java
@@ -32,7 +32,7 @@ import android.opengl.GLUtils;
  * <p>
  * Create a Texture of arbitrary size and feed some pixels to it via the setTexture() method.
  */
-public class DistortedTexture extends DistortedSurface implements DistortedInputSurface
+public class DistortedTexture extends DistortedSurface
   {
   private Bitmap mBmp;
 
