commit 425710569c4c49bdd6d50c0926553a21bbf2fbb7
Author: Leszek Koltunski <leszek@distoretedandroid.org>
Date:   Wed May 3 17:22:49 2017 +0100

    Preparation for Transfer Feedback: Convert the meshes from client-side to VBOs.

diff --git a/src/main/java/org/distorted/library/Distorted.java b/src/main/java/org/distorted/library/Distorted.java
index e6f05e5..6d563da 100644
--- a/src/main/java/org/distorted/library/Distorted.java
+++ b/src/main/java/org/distorted/library/Distorted.java
@@ -132,6 +132,7 @@ public class Distorted
     {
     DistortedSurface.onPause();
     DistortedNode.onPause();
+    MeshObject.onPause();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -143,10 +144,11 @@ public class Distorted
     {
     DistortedSurface.onDestroy();
     DistortedNode.onDestroy();
-    EffectQueue.onDestroy();
     DistortedEffects.onDestroy();
     DistortedEffectsPostprocess.onDestroy();
     DistortedMaster.onDestroy();
+    EffectQueue.onDestroy();
+    MeshObject.onDestroy();
     EffectMessageSender.stopSending();
 
     mInitialized = false;
diff --git a/src/main/java/org/distorted/library/DistortedEffects.java b/src/main/java/org/distorted/library/DistortedEffects.java
index ddd6b98..861b00c 100644
--- a/src/main/java/org/distorted/library/DistortedEffects.java
+++ b/src/main/java/org/distorted/library/DistortedEffects.java
@@ -303,10 +303,15 @@ public class DistortedEffects
     mM.send(surface,halfW,halfH,halfZ);
     mV.send(halfW,halfH,halfZ);
     mF.send(halfW,halfH);
-    GLES30.glVertexAttribPointer(mMainProgram.mAttribute[0], POSITION_DATA_SIZE, GLES30.GL_FLOAT, false, 0, mesh.mMeshPositions);
-    GLES30.glVertexAttribPointer(mMainProgram.mAttribute[1], NORMAL_DATA_SIZE  , GLES30.GL_FLOAT, false, 0, mesh.mMeshNormals);
-    GLES30.glVertexAttribPointer(mMainProgram.mAttribute[2], TEX_DATA_SIZE     , GLES30.GL_FLOAT, false, 0, mesh.mMeshTexture);
+
+    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mesh.mPosVBO[0]);
+    GLES30.glVertexAttribPointer(mMainProgram.mAttribute[0], POSITION_DATA_SIZE, GLES30.GL_FLOAT, false, 0, 0);
+    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mesh.mNorVBO[0]);
+    GLES30.glVertexAttribPointer(mMainProgram.mAttribute[1], NORMAL_DATA_SIZE  , GLES30.GL_FLOAT, false, 0, 0);
+    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mesh.mTexVBO[0]);
+    GLES30.glVertexAttribPointer(mMainProgram.mAttribute[2], TEX_DATA_SIZE     , GLES30.GL_FLOAT, false, 0, 0);
     GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mesh.dataLength);
+    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0 );
 
     /// DEBUG ONLY //////
     // displayBoundingRect(halfInputW, halfInputH, halfZ, df, mM.getMVP(), mesh.getBoundingVertices() );
diff --git a/src/main/java/org/distorted/library/DistortedOutputSurface.java b/src/main/java/org/distorted/library/DistortedOutputSurface.java
index 8bf150c..3f99e25 100644
--- a/src/main/java/org/distorted/library/DistortedOutputSurface.java
+++ b/src/main/java/org/distorted/library/DistortedOutputSurface.java
@@ -234,12 +234,11 @@ if( !sNew.equals(sOld) )
     {
     // change tree topology (attach and detach children)
 /*
-    boolean changed =
+    boolean changed1 =
 */
     DistortedMaster.toDo();
 /*
-    // debugging only
-    if( changed )
+    if( changed1 )
       {
       for(int i=0; i<mNumChildren; i++)
         {
@@ -254,13 +253,26 @@ if( !sNew.equals(sOld) )
     // with OpenGL resources. That's because changing Tree
     // can result in additional Framebuffers that would need
     // to be created immediately, before the calls to drawRecursive()
+/*
+    boolean changed2 =
+*/
     toDo();
 /*
-    // debugging only
-    if( changed )
+    if( changed2 )
       {
       DistortedSurface.debugLists();
       }
+*/
+    // create and delete all Meshes (we need to create Vertex Buffer Objects)
+/*
+    boolean changed3 =
+*/
+    MeshObject.toDo();
+/*
+    if( changed3 )
+      {
+      MeshObject.debugLists();
+      }
 */
     // mark OpenGL state as unknown
     DistortedRenderState.reset();
diff --git a/src/main/java/org/distorted/library/DistortedSurface.java b/src/main/java/org/distorted/library/DistortedSurface.java
index b6952e1..ef505ca 100644
--- a/src/main/java/org/distorted/library/DistortedSurface.java
+++ b/src/main/java/org/distorted/library/DistortedSurface.java
@@ -77,7 +77,7 @@ abstract class DistortedSurface
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // must be called from a thread holding OpenGL Context
 
-  static synchronized void toDo()
+  static synchronized boolean toDo()
     {
     if( mToDo )
       {
@@ -104,7 +104,10 @@ abstract class DistortedSurface
 
       mToDoMap.clear();
       mToDo = false;
+      return true;
       }
+
+    return false;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/library/MeshObject.java b/src/main/java/org/distorted/library/MeshObject.java
index 73adb14..079a020 100644
--- a/src/main/java/org/distorted/library/MeshObject.java
+++ b/src/main/java/org/distorted/library/MeshObject.java
@@ -19,7 +19,11 @@
 
 package org.distorted.library;
 
+import android.opengl.GLES30;
+
 import java.nio.FloatBuffer;
+import java.util.HashMap;
+import java.util.LinkedList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
@@ -36,8 +40,35 @@ public abstract class MeshObject
    static final int NORMAL_DATA_SIZE  = 3; // Size of the normal data in elements.
    static final int TEX_DATA_SIZE     = 2; // Size of the texture coordinate data in elements.
 
+   ///// CREATING/DELETING Vertex Buffer Objects ///////////////////////////
+   private static final int JOB_CREATE = 0;
+   private static final int JOB_DELETE = 1;
+
+   private class Job
+     {
+     MeshObject mesh;
+     int action;
+
+     Job(MeshObject o, int a)
+       {
+       mesh   = o;
+       action = a;
+       }
+     }
+
+   private static boolean mToDo = false;
+   private static LinkedList<MeshObject> mDoneList = new LinkedList<>();
+   private static HashMap<Long,Job> mToDoMap = new HashMap<>();
+   /////////////////////////////////////////////////////////////////////////
+
+   private static long mNextID = 0;
+   private long mID;
+
    int dataLength;
    FloatBuffer mMeshPositions, mMeshNormals, mMeshTexture;
+   int[] mPosVBO = new int[1];
+   int[] mNorVBO = new int[1];
+   int[] mTexVBO = new int[1];
 
    final float zFactor; // strange workaround for the fact that we need to somehow store the 'depth'
                         // of the Mesh. Used in DistortedEffects. See DistortedTexture.getDepth().
@@ -47,9 +78,182 @@ public abstract class MeshObject
    MeshObject(float factor)
      {
      zFactor = factor;
+     mID     = mNextID++;
+
+     recreate();
+
+     mToDoMap.put(mID, new Job(this,JOB_CREATE) );
+     mToDo = true;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context
+
+   static synchronized boolean toDo()
+     {
+     if( mToDo )
+       {
+       Job job;
+       MeshObject mesh;
+
+       for(Long key: mToDoMap.keySet())
+         {
+         job = mToDoMap.get(key);
+         mesh = job.mesh;
+
+         //android.util.Log.d("MESH", "  ---> need to "+(job.action==JOB_CREATE ? "create":"delete") );
+
+         if( job.action==JOB_CREATE )
+           {
+           mesh.create();
+           mDoneList.add(mesh);
+           }
+         else if( job.action==JOB_DELETE )
+           {
+           mesh.delete();
+           }
+         }
+
+       mToDoMap.clear();
+       mToDo = false;
+       return true;
+       }
+
+     return false;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context
+//
+// Do NOT release mMeshPositions etc as we will need them when we need to re-create the buffers after
+// a loss of OpenGL context!
+
+   private void create()
+     {
+     if( mPosVBO[0]<0 )
+       {
+       GLES30.glGenBuffers(1, mPosVBO, 0);
+       GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mPosVBO[0]);
+       GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, dataLength*POSITION_DATA_SIZE*BYTES_PER_FLOAT, mMeshPositions, GLES30.GL_STATIC_READ);
+       }
+     if( mNorVBO[0]<0 )
+       {
+       GLES30.glGenBuffers(1, mNorVBO, 0);
+       GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mNorVBO[0]);
+       GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, dataLength*  NORMAL_DATA_SIZE*BYTES_PER_FLOAT, mMeshNormals  , GLES30.GL_STATIC_READ);
+       }
+     if( mTexVBO[0]<0 )
+       {
+       GLES30.glGenBuffers(1, mTexVBO, 0);
+       GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mTexVBO[0]);
+       GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, dataLength*    TEX_DATA_SIZE*BYTES_PER_FLOAT, mMeshTexture  , GLES30.GL_STATIC_READ);
+       }
+
+     GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// must be called from a thread holding OpenGL Context
+
+   private void delete()
+     {
+     if( mPosVBO[0]>=0 )
+       {
+       GLES30.glDeleteBuffers(1, mPosVBO, 0);
+       mPosVBO[0] = -1;
+       }
+     if( mNorVBO[0]>=0 )
+       {
+       GLES30.glDeleteBuffers(1, mNorVBO, 0);
+       mNorVBO[0] = -1;
+       }
+     if( mTexVBO[0]>=0 )
+       {
+       GLES30.glDeleteBuffers(1, mTexVBO, 0);
+       mTexVBO[0] = -1;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void recreate()
+     {
+     mPosVBO[0] = -1;
+     mNorVBO[0] = -1;
+     mTexVBO[0] = -1;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void onPause()
+    {
+    MeshObject mesh;
+    int num = mDoneList.size();
+
+    for(int i=0; i<num; i++)
+      {
+      mesh = mDoneList.removeFirst();
+      mToDoMap.put(mesh.getID(), mesh.new Job(mesh,JOB_CREATE) );
+      mesh.recreate();
+      }
+
+    mToDo = true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static synchronized void onDestroy()
+    {
+    mToDoMap.clear();
+    mDoneList.clear();
+
+    mToDo = true;
+    mNextID = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @SuppressWarnings("unused")
+  static void debugLists()
+    {
+    android.util.Log.e("Mesh", "Done list:");
+
+    MeshObject mesh;
+    int num = mDoneList.size();
+
+    for(int i=0; i<num; i++)
+      {
+      mesh = mDoneList.get(i);
+      mesh.print(i, "");
+      }
+
+    android.util.Log.e("Mesh", "ToDo list:");
+
+    Job job;
+    int i=0;
+
+    for(Long key: mToDoMap.keySet())
+      {
+      job = mToDoMap.get(key);
+      job.mesh.print(i++, job.action==JOB_CREATE ? " create":" delete");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void print(int i, String extra)
+    {
+    String str;
+
+         if( this instanceof MeshFlat ) str = (i+": MeshFlat  ");
+    else if( this instanceof MeshCubes) str = (i+": MeshCubes ");
+    else                                str = (i+": UNKNOWN   ");
+
+    str += ( "dataLength: "+dataLength+" meshID:"+getID());
+
+    android.util.Log.e("Mesh", str+extra);
+    }
+///////////////////////////////////////////////////////////////////////////////////////////////////
 /**
  * Get the minimal set of Vertices which have the same convex hull as the whole set.
  * <p>
@@ -59,4 +263,35 @@ public abstract class MeshObject
  * This is used to be able to quickly compute, in window coordinates, the Mesh'es bounding rectangle.
  */
    abstract float[] getBoundingVertices();
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   synchronized void markForCreation()
+     {
+     mDoneList.remove(this);
+     mToDoMap.put(mID, new Job(this,JOB_CREATE) );
+     mToDo = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Mark the underlying OpenGL object for deletion. Actual deletion will take place on the next render.
+ */
+   synchronized public void markForDeletion()
+     {
+     mDoneList.remove(this);
+     mToDoMap.put(mID, new Job(this,JOB_DELETE) );
+     mToDo = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return unique ID of this Mesh.
+ */
+   public long getID()
+    {
+    return mID;
+    }
    }
