commit 9d84590408d79becade5ada12f694288e3fe7d09
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Fri Jun 22 23:38:23 2018 +0100

    Finally properly fix the flashing on ARM Mali T880 GPU.
    
    The flashing is caused by a 'full pipeline flush' (see DarkPhoton, https://www.opengl.org/discussion_boards/showthread.php/200754-Flashes-on-ARM-Mali?p=1291679&viewfull=1#post1291679 ). In order to combat it, first introduce the possibility that a single DistortedOutputSurface is backed up by more than one FBO. Then make DistortedScreen be derived from DistortedFramebuffer (which itself is derived from DistortedOutputSurface) and make it contain 3 FBOs, render to them in a circular queue fashion, and blit from a given FBO to the system FBO. The 'more than 1 intermediate FBO' queue prevents the pipeline flush.
    
    Still some TODOs in DistortedFramebuffer remain (properly check for Framebuffer completness!)

diff --git a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java b/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
index ad84f4e..ec6be14 100644
--- a/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
+++ b/src/main/java/org/distorted/library/main/DistortedFramebuffer.java
@@ -36,77 +36,107 @@ public class DistortedFramebuffer extends DistortedOutputSurface implements Dist
 
   void create()
     {
+    //////////////////////////////////////////////////////////////
+    // COLOR
+
     if( mColorCreated==NOT_CREATED_YET )
       {
-      GLES31.glGenTextures( mNumColors, mColorH, 0);
-      GLES31.glGenFramebuffers(1, mFBOH, 0);
-      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[0]);
+      GLES31.glGenTextures( mNumFBOs*mNumColors, mColorH, 0);
+      GLES31.glGenFramebuffers(mNumFBOs, mFBOH, 0);
 
-      for(int i=0; i<mNumColors; i++)
+      for(int i=0; i<mNumFBOs; i++)
         {
-        GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mColorH[i]);
-        GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_S, GLES31.GL_REPEAT);
-        GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_T, GLES31.GL_REPEAT);
-        GLES31.glTexParameterf(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MIN_FILTER, GLES31.GL_NEAREST);
-        GLES31.glTexParameterf(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MAG_FILTER, GLES31.GL_LINEAR);
-        GLES31.glTexImage2D(GLES31.GL_TEXTURE_2D, 0, GLES31.GL_RGBA, mRealWidth, mRealHeight, 0, GLES31.GL_RGBA, GLES31.GL_UNSIGNED_BYTE, null);
+        GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[i]);
+
+        for(int j=0; j<mNumColors; j++)
+          {
+          GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mColorH[i*mNumColors+j]);
+          GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_S, GLES31.GL_REPEAT);
+          GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_T, GLES31.GL_REPEAT);
+          GLES31.glTexParameterf(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MIN_FILTER, GLES31.GL_NEAREST);
+          GLES31.glTexParameterf(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MAG_FILTER, GLES31.GL_LINEAR);
+          GLES31.glTexImage2D(GLES31.GL_TEXTURE_2D, 0, GLES31.GL_RGBA, mRealWidth, mRealHeight, 0, GLES31.GL_RGBA, GLES31.GL_UNSIGNED_BYTE, null);
+          }
+
+        GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_COLOR_ATTACHMENT0, GLES31.GL_TEXTURE_2D, mColorH[i*mNumColors], 0);
+        GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, 0);
         }
-      GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_COLOR_ATTACHMENT0, GLES31.GL_TEXTURE_2D, mColorH[0], 0);
-      GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, 0);
-      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
 
+      // TODO
       mColorCreated = checkStatus("color");
+      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
       }
+
+    //////////////////////////////////////////////////////////////
+    // DEPTH / STENCIL
+
     if( mDepthStencilCreated==NOT_CREATED_YET ) // we need to create a new DEPTH or STENCIL attachment
       {
-      GLES31.glGenTextures(1, mDepthStencilH, 0);
-      GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mDepthStencilH[0]);
-      GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_S, GLES31.GL_REPEAT);
-      GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_T, GLES31.GL_REPEAT);
-      GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MIN_FILTER, GLES31.GL_NEAREST);
-      GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MAG_FILTER, GLES31.GL_NEAREST);
-
-      if( mDepthStencil==DEPTH_NO_STENCIL )
-        {
-        GLES31.glTexImage2D(GLES31.GL_TEXTURE_2D, 0, GLES31.GL_DEPTH_COMPONENT, mRealWidth, mRealHeight, 0, GLES31.GL_DEPTH_COMPONENT, GLES31.GL_UNSIGNED_INT, null);
-        }
-      else if( mDepthStencil==BOTH_DEPTH_STENCIL )
+      GLES31.glGenTextures(mNumFBOs, mDepthStencilH, 0);
+
+      for(int i=0; i<mNumFBOs; i++)
         {
-        GLES31.glTexImage2D(GLES31.GL_TEXTURE_2D, 0, GLES31.GL_DEPTH24_STENCIL8, mRealWidth, mRealHeight, 0, GLES31.GL_DEPTH_STENCIL, GLES31.GL_UNSIGNED_INT_24_8, null);
+        GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mDepthStencilH[i]);
+        GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_S, GLES31.GL_REPEAT);
+        GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_T, GLES31.GL_REPEAT);
+        GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MIN_FILTER, GLES31.GL_NEAREST);
+        GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MAG_FILTER, GLES31.GL_NEAREST);
+
+        if (mDepthStencil == DEPTH_NO_STENCIL)
+          {
+          GLES31.glTexImage2D(GLES31.GL_TEXTURE_2D, 0, GLES31.GL_DEPTH_COMPONENT, mRealWidth, mRealHeight, 0, GLES31.GL_DEPTH_COMPONENT, GLES31.GL_UNSIGNED_INT, null);
+          }
+        else if (mDepthStencil == BOTH_DEPTH_STENCIL)
+          {
+          GLES31.glTexImage2D(GLES31.GL_TEXTURE_2D, 0, GLES31.GL_DEPTH24_STENCIL8, mRealWidth, mRealHeight, 0, GLES31.GL_DEPTH_STENCIL, GLES31.GL_UNSIGNED_INT_24_8, null);
+          }
         }
-
       GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, 0);
-      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[0]);
 
-      if( mDepthStencil==DEPTH_NO_STENCIL )
-        {
-        GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_ATTACHMENT, GLES31.GL_TEXTURE_2D, mDepthStencilH[0], 0);
-        }
-      else if( mDepthStencil==BOTH_DEPTH_STENCIL )
+      for(int i=0; i<mNumFBOs; i++)
         {
-        GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_STENCIL_ATTACHMENT, GLES31.GL_TEXTURE_2D, mDepthStencilH[0], 0);
+        GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[i]);
+
+        if (mDepthStencil == DEPTH_NO_STENCIL)
+          {
+          GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_ATTACHMENT, GLES31.GL_TEXTURE_2D, mDepthStencilH[i], 0);
+          }
+        else if (mDepthStencil == BOTH_DEPTH_STENCIL)
+          {
+          GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_STENCIL_ATTACHMENT, GLES31.GL_TEXTURE_2D, mDepthStencilH[i], 0);
+          }
         }
 
-      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
-
+      // TODO
       mDepthStencilCreated = checkStatus("depth");
+      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
       }
+
+    //////////////////////////////////////////////////////////////
+    // DETACH
+
+    // TODO
     if( mDepthStencilCreated==DONT_CREATE && mDepthStencilH[0]>0 ) // we need to detach and recreate the DEPTH attachment.
       {
       // OpenGL ES 3.0.5 spec, chapter 4.4.2.4 :
       // "Note that the texture image is specifically not detached from any other framebuffer objects.
       //  Detaching the texture image from any other framebuffer objects is the responsibility of the application."
-      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[0]);
-      GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_ATTACHMENT        , GLES31.GL_TEXTURE_2D, 0, 0);
-      GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_STENCIL_ATTACHMENT, GLES31.GL_TEXTURE_2D, 0, 0);
-      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
 
-      GLES31.glDeleteTextures(1, mDepthStencilH, 0);
-      mDepthStencilH[0]=0;
+      for(int i=0; i<mNumFBOs; i++)
+        {
+        GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[i]);
+        GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_ATTACHMENT, GLES31.GL_TEXTURE_2D, 0, 0);
+        GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_DEPTH_STENCIL_ATTACHMENT, GLES31.GL_TEXTURE_2D, 0, 0);
+        mDepthStencilH[i]=0;
+        }
+
+      GLES31.glDeleteTextures(mNumFBOs, mDepthStencilH, 0);
+      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
       }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
 
   private int checkStatus(String message)
     {
@@ -134,20 +164,22 @@ public class DistortedFramebuffer extends DistortedOutputSurface implements Dist
     {
     if( mColorH[0]>0 )
       {
-      GLES31.glDeleteTextures(1, mColorH, 0);
-      mColorH[0] = 0;
+      GLES31.glDeleteTextures(mNumFBOs*mNumColors, mColorH, 0);
       mColorCreated = NOT_CREATED_YET;
+
+      for(int i=0; i<mNumFBOs*mNumColors; i++) mColorH[i] = 0;
       }
 
     if( mDepthStencilH[0]>0 )
       {
-      GLES31.glDeleteTextures(1, mDepthStencilH, 0);
-      mDepthStencilH[0]=0;
+      GLES31.glDeleteTextures(mNumFBOs, mDepthStencilH, 0);
       mDepthStencilCreated = NOT_CREATED_YET;
+
+      for(int i=0; i<mNumFBOs; i++) mDepthStencilH[i] = 0;
       }
 
-    GLES31.glDeleteFramebuffers(1, mFBOH, 0);
-    mFBOH[0] = 0;
+    GLES31.glDeleteFramebuffers(mNumFBOs, mFBOH, 0);
+    for(int i=0; i<mNumFBOs; i++) mFBOH[i] = 0;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -167,6 +199,20 @@ public class DistortedFramebuffer extends DistortedOutputSurface implements Dist
       }
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean setAsInput(int fbo, int texture)
+    {
+    if( texture>=0 && texture<mNumColors && fbo>=0 && fbo<mNumFBOs && mColorH[mNumColors*fbo + texture]>0 )
+      {
+      GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
+      GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, mColorH[mNumColors*fbo + texture]);
+      return true;
+      }
+
+    return false;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // create SYSTEM or TREE framebuffers (those are just like normal FBOs, just hold information
 // that they were autocreated only for the Library's internal purposes (SYSTEM) or for using
@@ -175,7 +221,15 @@ public class DistortedFramebuffer extends DistortedOutputSurface implements Dist
 
   DistortedFramebuffer(int numcolors, int depthStencil, int type, int width, int height)
     {
-    super(width,height,NOT_CREATED_YET,numcolors,depthStencil,NOT_CREATED_YET, type);
+    super(width,height,NOT_CREATED_YET,1,numcolors,depthStencil,NOT_CREATED_YET, type);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// create a multi-framebuffer (1 object containing multiple FBOs)
+
+  DistortedFramebuffer(int numfbos, int numcolors, int depthStencil, int type, int width, int height)
+    {
+    super(width,height,NOT_CREATED_YET,numfbos,numcolors,depthStencil,NOT_CREATED_YET, type);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -193,7 +247,7 @@ public class DistortedFramebuffer extends DistortedOutputSurface implements Dist
   @SuppressWarnings("unused")
   public DistortedFramebuffer(int width, int height, int numcolors, int depthStencil)
     {
-    super(width,height,NOT_CREATED_YET,numcolors,depthStencil,NOT_CREATED_YET,TYPE_USER);
+    super(width,height,NOT_CREATED_YET,1,numcolors,depthStencil,NOT_CREATED_YET,TYPE_USER);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/main/DistortedNode.java b/src/main/java/org/distorted/library/main/DistortedNode.java
index 7f3d728..39e9fc8 100644
--- a/src/main/java/org/distorted/library/main/DistortedNode.java
+++ b/src/main/java/org/distorted/library/main/DistortedNode.java
@@ -341,7 +341,7 @@ public class DistortedNode implements DistortedMaster.Slave
         DistortedEffects.blitPriv(mData.mFBO);
         }
 
-      numRenders += mData.mFBO.renderChildren(currTime,mNumChildren[0],mChildren);
+      numRenders += mData.mFBO.renderChildren(currTime,mNumChildren[0],mChildren,0);
       }
 
     return numRenders;
diff --git a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
index 9946c2d..c2c8a63 100644
--- a/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/main/DistortedOutputSurface.java
@@ -72,15 +72,15 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
   // Global buffers used for postprocessing.
   private static DistortedOutputSurface[] mBuffer = null;
 
-  private long mTime;
   private float mFOV;
   float mDistance, mNear;
   float[] mProjectionMatrix;
 
   int mDepthStencilCreated;
   int mDepthStencil;
-  int[] mDepthStencilH = new int[1];
-  int[] mFBOH          = new int[1];
+  int[] mDepthStencilH;
+  int[] mFBOH;
+  private long[] mTime;
 
   private float mClearR, mClearG, mClearB, mClearA;
   private float mClearDepth;
@@ -95,9 +95,15 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  DistortedOutputSurface(int width, int height, int createColor, int numcolors, int depthStencil, int fbo, int type)
+  DistortedOutputSurface(int width, int height, int createColor, int numfbos, int numcolors, int depthStencil, int fbo, int type)
     {
-    super(width,height,createColor,numcolors,type);
+    super(width,height,createColor,numfbos,numcolors,type);
+
+    mDepthStencilH = new int[numfbos];
+    mFBOH          = new int[numfbos];
+
+    mTime = new long[numfbos];
+    for(int i=0; i<mNumFBOs;i++) mTime[i]=0;
 
     mRealWidth = width;
     mRealHeight= height;
@@ -113,8 +119,6 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
     mFBOH[0]         = fbo;
     mDepthStencilH[0]= 0;
 
-    mTime = 0;
-
     mClearR = 0.0f;
     mClearG = 0.0f;
     mClearB = 0.0f;
@@ -260,10 +264,10 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private int blitWithDepth(long currTime, DistortedOutputSurface buffer)
+  private int blitWithDepth(long currTime, DistortedOutputSurface buffer,int fbo)
     {
     GLES31.glViewport(0, 0, mWidth, mHeight);
-    setAsOutput(currTime);
+    setAsOutput(currTime,fbo);
     GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
     GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, buffer.mColorH[0]);
     GLES31.glActiveTexture(GLES31.GL_TEXTURE1);
@@ -312,7 +316,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 // 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.blitWithDepth).
 
-  int renderChildren(long time, int numChildren, ArrayList<DistortedNode> children)
+  int renderChildren(long time, int numChildren, ArrayList<DistortedNode> children, int fbo)
     {
     int quality=0, internalQuality = 0, numRenders = 0, bucketChange = 0;
     DistortedNode child1, child2;
@@ -327,7 +331,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
 
       if( currBucket==0 )
         {
-        GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[0]);
+        setAsOutput(time,fbo);
         numRenders += child1.draw(time,this);
         }
       else
@@ -349,7 +353,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
               }
 
             numRenders += lastQueue.postprocess(mBuffer);
-            numRenders += blitWithDepth(time, mBuffer[quality]);
+            numRenders += blitWithDepth(time, mBuffer[quality],fbo);
 
             mBuffer[quality].setAsOutputAndClear(time);
             }
@@ -371,7 +375,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
             }
 
           numRenders += currQueue.postprocess(mBuffer);
-          numRenders += blitWithDepth(time, mBuffer[quality]);
+          numRenders += blitWithDepth(time, mBuffer[quality],fbo);
           }
         } // end postprocessed child case
 
@@ -423,26 +427,60 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
     {
     GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[0]);
 
-    mTime = time;    // have to do this otherwise on the next setAsOutput() we would clear
+    mTime[0] = time;    // have to do this otherwise on the next setAsOutput() we would clear
     DistortedRenderState.colorDepthStencilOn();
     GLES31.glClearColor(mClearR, mClearG, mClearB, mClearA);
     GLES31.glClear(GLES31.GL_COLOR_BUFFER_BIT);
     DistortedRenderState.colorDepthStencilRestore();
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setAsOutput(long time, int fbo)
+    {
+    if( fbo>=0 && fbo<mNumFBOs )
+      {
+      GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[fbo]);
+
+      if (mTime[fbo] != time)
+        {
+        mTime[fbo] = time;
+        clear();
+        }
+      }
+    else
+      {
+      android.util.Log.e("surface", "error in setAsOutput, fbo="+fbo);
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // 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)
+  public int render(long time, int fbo)
     {
     // change tree topology (attach and detach children)
 /*
@@ -485,8 +523,7 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
       numRenders += mChildren.get(i).renderRecursive(time);
       }
 
-    setAsOutput(time);
-    numRenders += renderChildren(time,mNumChildren,mChildren);
+    numRenders += renderChildren(time,mNumChildren,mChildren,fbo);
 
     return numRenders;
     }
@@ -504,9 +541,9 @@ public abstract class DistortedOutputSurface extends DistortedSurface implements
     {
     GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, mFBOH[0]);
 
-    if( mTime!=time )
+    if( mTime[0]!=time )
       {
-      mTime = time;
+      mTime[0] = time;
       clear();
       }
     }
diff --git a/src/main/java/org/distorted/library/main/DistortedScreen.java b/src/main/java/org/distorted/library/main/DistortedScreen.java
index 56e1719..f302dc5 100644
--- a/src/main/java/org/distorted/library/main/DistortedScreen.java
+++ b/src/main/java/org/distorted/library/main/DistortedScreen.java
@@ -55,6 +55,9 @@ public class DistortedScreen extends DistortedFramebuffer
   private static MatrixEffectMove mMoveEffect = new MatrixEffectMove( new Static3D(5,5,0) );
   ///// END DEBUGGING //////////////////////////
 
+  private int mCurrFBO;
+  private static final int NUM_FBO = 3;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -65,8 +68,9 @@ public class DistortedScreen extends DistortedFramebuffer
  */
   public DistortedScreen()
     {
-    super(1,1,1,BOTH_DEPTH_STENCIL);
+    super(NUM_FBO,1,BOTH_DEPTH_STENCIL, TYPE_SYST, 1,1);
     mShowFPS = false;
+    mCurrFBO = 0;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -100,11 +104,10 @@ public class DistortedScreen extends DistortedFramebuffer
       lastTime = time;
       }
 
-    int numrender = super.render(time);
+    int numrender = super.render(time,mCurrFBO);
 
     GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
-    clear();
-    setAsInput();
+    setAsInput(mCurrFBO,0);
     GLES31.glColorMask(true,true,true,true);
     GLES31.glDepthMask(false);
     GLES31.glDisable(GLES31.GL_STENCIL_TEST);
@@ -118,6 +121,9 @@ public class DistortedScreen extends DistortedFramebuffer
       fpsEffects.drawPriv(fpsW / 2.0f, fpsH / 2.0f, fpsMesh, this, time, 0);
       }
 
+    mCurrFBO++;
+    if( mCurrFBO>=NUM_FBO ) mCurrFBO=0;
+
     return numrender+1;
     }
 
diff --git a/src/main/java/org/distorted/library/main/DistortedSurface.java b/src/main/java/org/distorted/library/main/DistortedSurface.java
index ed982b7..538138a 100644
--- a/src/main/java/org/distorted/library/main/DistortedSurface.java
+++ b/src/main/java/org/distorted/library/main/DistortedSurface.java
@@ -25,24 +25,28 @@ abstract class DistortedSurface extends DistortedObject
 {
   int mColorCreated;
   int mNumColors;
+  int mNumFBOs;
   int[] mColorH;
   int mWidth, mHeight;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  DistortedSurface(int width, int height, int create, int numcolors, int type)
+  DistortedSurface(int width, int height, int create, int numfbos, int numcolors, int type)
     {
     super(create,type);
 
+    mNumFBOs      = numfbos;
     mNumColors    = numcolors;
     mWidth        = width ;
     mHeight       = height;
     mColorCreated = create;
 
-    if( mNumColors>0 )
+    int total = mNumFBOs*mNumColors;
+
+    if( total>0 )
       {
-      mColorH = new int[mNumColors];
-      for( int i=0; i<mNumColors; i++ )  mColorH[i] = 0;
+      mColorH = new int[total];
+      for( int i=0; i<total; i++ )  mColorH[i] = 0;
       }
     }
 
diff --git a/src/main/java/org/distorted/library/main/DistortedTexture.java b/src/main/java/org/distorted/library/main/DistortedTexture.java
index 7d52b82..b5592fc 100644
--- a/src/main/java/org/distorted/library/main/DistortedTexture.java
+++ b/src/main/java/org/distorted/library/main/DistortedTexture.java
@@ -34,7 +34,7 @@ import android.opengl.GLUtils;
  */
 public class DistortedTexture extends DistortedSurface implements DistortedInputSurface
   {
-  private Bitmap mBmp= null;
+  private Bitmap mBmp;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // We have to flip vertically every single Bitmap that we get fed with.
@@ -115,7 +115,7 @@ public class DistortedTexture extends DistortedSurface implements DistortedInput
 
   public DistortedTexture(int width, int height, int type)
     {
-    super(width,height,NOT_CREATED_YET,1,type);
+    super(width,height,NOT_CREATED_YET,1,1,type);
     mBmp= null;
     }
 
@@ -127,7 +127,7 @@ public class DistortedTexture extends DistortedSurface implements DistortedInput
  */
   public DistortedTexture(int width, int height)
     {
-    super(width,height,NOT_CREATED_YET,1,TYPE_USER);
+    super(width,height,NOT_CREATED_YET,1,1,TYPE_USER);
     mBmp= null;
     }
 
